> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rootly.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Problems API

> Create, update, transition, and delete problems with the Rootly API, and link the incidents a problem explains.

Use these endpoints to create and track [problems](https://docs.rootly.com/glossary/incident-vs-problem.md), the underlying causes behind one or more incidents, and to link the incidents each one explains.

> [!NOTE]
>
> **Beta** — Problem Management is enabled per organization. If it isn't enabled for yours, every endpoint on this page returns `404`. Contact your account team to request access.

Call these endpoints with an [API key](https://docs.rootly.com/api-reference/overview.md#how-to-generate-an-api-key). OAuth 2.0 access tokens are denied whatever their scope, including `ir.all` and `all`.

## Overview

A **problem** records the underlying cause behind one or more incidents and tracks the work to remove it. The Problems API covers the full lifecycle:

| Operation | Endpoint |
| - | - |
| List problems | `GET /v1/problems` |
| Create a problem | `POST /v1/problems` |
| Read one problem | `GET /v1/problems/{id}` |
| Update or transition a problem | `PUT /v1/problems/{id}` |
| Delete a problem | `DELETE /v1/problems/{id}` |
| Link incidents | `POST /v1/problems/{id}/incidents` |
| Unlink an incident | `DELETE /v1/problems/{id}/incidents/{incident_id}` |

Requests and responses use JSON:API with `Content-Type: application/vnd.api+json` and the `problems` resource type. Authentication, pagination, and filter syntax match the rest of the API; see the [API overview](https://docs.rootly.com/api-reference/overview.md). Action items and role assignments on a problem have their own endpoints; see [Problem Action Items](https://docs.rootly.com/api-reference/problem-management/action-items.md) and [Problem Roles](https://docs.rootly.com/api-reference/problem-management/roles.md).

Your role's **Problems** permissions control access: `read` to list and view, `create` to create, `update` to edit, transition, and link incidents, and `delete` to delete.

***

## Creating a Problem

`POST /v1/problems` creates a problem. Only `title` is required. The authenticated user is recorded as the creator and, when your organization has an enabled **Problem Manager** role, is assigned that role on the new problem.

```bash theme={null}
curl --request POST \
--header 'Content-Type: application/vnd.api+json' \
--header 'Authorization: Bearer YOUR-API-KEY' \
--url https://api.rootly.com/v1/problems \
--data '{
  "data": {
    "type": "problems",
    "attributes": {
      "title": "Checkout requests time out under peak load",
      "priority": "P1",
      "incident_ids": ["INCIDENT_ID"]
    }
  }
}'
```

The response is `201` with the new problem, including `display_id` (for example `PROB-12`) and `url`, a link to the problem in Rootly.

### Fields

**title** `string` *required*

The problem's title.

**description** `string`

Longer description of the problem.

**priority** `string`

One of `P0`, `P1`, `P2`, or `P3`.

**due\_date** `string`

When the problem should be resolved, as an ISO 8601 date (`YYYY-MM-DD`).

**scope** `string`

What the investigation covers. Some statuses can require this field before a problem moves into them; see [Changing Status](#changing-status).

**exit\_criteria** `string`

What must be true for the problem to be considered resolved. Some statuses can require this field before a problem moves into them; see [Changing Status](#changing-status).

**impact\_so\_far** `string`

The impact the problem has caused so far. Some statuses can require this field before a problem moves into them; see [Changing Status](#changing-status).

**root\_cause** `string`

The underlying cause, once identified. Some statuses can require this field before a problem moves into them; see [Changing Status](#changing-status).

**resolution** `string`

How the problem was resolved. Some statuses can require this field before a problem moves into them; see [Changing Status](#changing-status).

**incident\_ids** `string[]`

Incidents to link when the problem is created. Every ID must be an incident the caller can read; test incidents can't be linked. One unknown or inaccessible ID fails the request with `404`.

**form\_field\_selections** `object[]`

Values for your organization's custom problem fields, one entry per field. Each entry needs a `form_field_id` and either a `value` or the `selected_*_ids` array that matches the field:

* `value` is for text, textarea, rich text, number, date, datetime, checkbox, and tags fields. Date and datetime values are ISO 8601 strings, numbers are numeric strings, checkboxes are `"true"` or `"false"`, and tags are an array of strings.
* Select, multi-select, and record fields use `selected_option_ids`, `selected_user_ids` (integers), `selected_group_ids`, `selected_service_ids`, `selected_functionality_ids`, `selected_catalog_entity_ids`, `selected_environment_ids`, `selected_cause_ids`, or `selected_incident_type_ids`.

```json theme={null}
"form_field_selections": [
  { "form_field_id": "FORM_FIELD_ID", "value": "us-east-1" },
  { "form_field_id": "FORM_FIELD_ID", "selected_option_ids": ["OPTION_ID"] }
]
```

***

## Updating a Problem

`PUT /v1/problems/{id}` changes only the attributes you send. Omitted attributes keep their current values, and an explicit `null` clears a nullable attribute. The nullable attributes are `description`, `priority`, `due_date`, `scope`, `exit_criteria`, `impact_so_far`, `root_cause`, `resolution`, `deferral_reason`, `cancellation_reason`, and `next_review_at`.

```bash theme={null}
curl --request PUT \
--header 'Content-Type: application/vnd.api+json' \
--header 'Authorization: Bearer YOUR-API-KEY' \
--url https://api.rootly.com/v1/problems/PROBLEM_ID \
--data '{
  "data": {
    "type": "problems",
    "attributes": {
      "root_cause": "Connection pool exhausted during retries",
      "due_date": null
    }
  }
}'
```

### Updating Custom Field Values

Send `form_field_selections` on `PUT` to change custom field values. Rootly matches each entry to the field's existing value by `form_field_id`, so you don't need the selection `id`. Fields you leave out keep their values.

* To change a value, send the field with its new `value` or `selected_*_ids`.
* To clear a value, send a blank `value` or set `_destroy` to `true`.

```json theme={null}
"form_field_selections": [
  { "form_field_id": "FORM_FIELD_ID", "value": "eu-west-1" },
  { "form_field_id": "OTHER_FORM_FIELD_ID", "_destroy": true }
]
```

### Changing Status

A problem moves through `created`, `in_progress`, `completed`, `deferred`, and `cancelled`. To change it, send the target `status` in the same `PUT` request:

```json theme={null}
{
  "data": {
    "type": "problems",
    "attributes": {
      "status": "completed",
      "expected_status": "in_progress",
      "resolution": "Raised pool size and added jittered retries"
    }
  }
}
```

* Your organization's status forms can require fields before a problem enters a status, for example a `resolution` before it is completed. Send those fields in the same request.
* Other attributes in the request are saved together with the status change. If the transition is rejected, the API returns `422` and none of the request is saved.
* `expected_status` is optional. When you send it, the transition is rejected unless the problem is currently in that status, so two clients can't both move the same problem.

These attributes describe why a problem was deferred or cancelled. Like the investigation fields, your status forms can require them for the matching status.

**deferral\_reason** `string`

Why the problem is deferred and the risk you're accepting by not fixing it now.

**next\_review\_at** `string`

When a deferred problem should be looked at again, as an ISO 8601 date and time, for example `2026-12-01T09:00:00Z`. A date without a time is rejected. A timestamp without an offset is read in the caller's time zone. Seconds are dropped, and the result must be later than the current time, otherwise the request fails with `422`. When the time arrives, the problem moves back to `created` automatically.

**cancellation\_reason** `string`

Why the problem is cancelled.

***

## Linking Incidents

`POST /v1/problems/{id}/incidents` links one or more incidents. Send `incident_id` for one incident or `incident_ids` for several:

```json theme={null}
{
  "data": {
    "type": "problems",
    "attributes": {
      "incident_ids": ["INCIDENT_ID_1", "INCIDENT_ID_2"]
    }
  }
}
```

Sending neither returns `400`. An incident the caller can't read, or a test incident, returns `404`.

`DELETE /v1/problems/{id}/incidents/{incident_id}` removes one incident from the problem and returns `404` if it isn't linked.

***

## Reading Custom Field Values

Problem responses include a `form_field_selections` array with every custom field value saved on the problem. Cleared values are left out. Each entry includes the `form_field` it belongs to, so you can match values by field `slug` instead of ID:

```json theme={null}
"form_field_selections": [
  {
    "id": "SELECTION_ID",
    "form_field_id": "FORM_FIELD_ID",
    "form_field": {
      "id": "FORM_FIELD_ID",
      "slug": "region",
      "name": "Region",
      "kind": "custom",
      "input_kind": "text",
      "value_kind": "inherit"
    },
    "value": "us-east-1",
    "selected_option_ids": [],
    "selected_user_ids": []
  }
]
```

Each entry also has every `selected_*_ids` array, empty when it doesn't apply. Checkbox values come back as `"1"` (checked) or `"0"` (unchecked).

***

## Setting Up Problem Fields and Forms

Custom problem fields and the forms they appear on are managed with the same endpoints as incident fields. Pass `problem` as the `resource_type`:

| Task | Endpoint |
| - | - |
| Create a problem field | [`POST /v1/form_fields`](https://docs.rootly.com/api-reference/formfields/creates-a-form-field) with `resource_type: "problem"` |
| Place a field on a problem form | [`POST /v1/form_fields/{form_field_id}/placements`](https://docs.rootly.com/api-reference/formfieldplacements/creates-a-form-field-placement) |
| Group problem forms into a form set | [`POST /v1/form_sets`](https://docs.rootly.com/api-reference/formsets/creates-a-form-set) with `resource_type: "problem"` |

A problem field's `kind` is `custom`, or one of the built-in problem kinds: `problem_services`, `problem_environments`, `problem_functionalities`, or `problem_teams`. Posting a built-in kind your organization already has returns the existing field with `200`. `problem_environments` and `problem_functionalities` need the matching environments or functionalities field turned on for your organization.

Problem fields go on these forms, which a `problem` form set accepts exclusively:

| Form | Shown when |
| - | - |
| `web_new_problem_form` | Creating a problem |
| `web_update_problem_form` | Editing a problem |
| `web_problem_in_progress_form` | Moving a problem to `in_progress` |
| `web_problem_completed_form` | Moving a problem to `completed` |
| `web_problem_deferred_form` | Moving a problem to `deferred` |
| `web_problem_cancelled_form` | Moving a problem to `cancelled` |

The four status forms hold the fields Rootly asks for on each transition. See [Changing Status](#changing-status).

If your organization has no default problem form set yet, creating a problem field or a `problem` form set sets one up with the standard fields, provided the API key can create form sets. Without Problem Management enabled, creating a problem field returns `422`.

The Workflows API doesn't manage problem workflows. They are left out of workflow lists, and requests for one return `403`.

***

## Listing Problems

`GET /v1/problems` is paginated with `page[number]` and `page[size]`, and sorted newest first by default.

| Filter | Matches |
| - | - |
| `filter[search]` | Text search |
| `filter[status][eq]`, `[not_eq]`, `[in]`, `[not_in]` | Status |
| `filter[priority][eq]`, `[not_eq]`, `[in]`, `[not_in]` | Priority |
| `filter[created_by_user_id]` | Creator |
| `filter[created_at][gt]`, `[gte]`, `[lt]`, `[lte]` | Creation date range |
| `filter[due_date][gt]`, `[gte]`, `[lt]`, `[lte]` | Due date range |

`sort` accepts `title`, `status`, `priority`, `sequential_id`, `due_date`, `incidents_count`, `action_items_count`, `subscribers_count`, `users_assigned_count`, `created_at`, and `updated_at`. Prefix with `-` for descending order. Sorting by `incidents_count` requires permission to read incidents and returns `400` without it.

Without permission to read incidents, the response also leaves out a problem's linked incidents.

***

## Errors

Errors use the JSON:API `errors` array:

```json theme={null}
{
  "errors": [
    { "title": "Not found or unauthorized", "status": "404" }
  ]
}
```

| Status | When |
| - | - |
| `400` | `POST /v1/problems/{id}/incidents` without an incident ID, or an `incidents_count` sort without incident read permission |
| `401` | The API key is missing or invalid |
| `404` | The problem doesn't exist, the caller can't access it, or Problem Management isn't enabled. The API returns `404` rather than `403` so it doesn't reveal whether a problem exists. |
| `422` | A field fails validation, or a status transition is rejected |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.