---
title: "Using the API"
description: "Idempotent incident creation, error handling, pagination, and a worked create-then-update example."
canonical: https://firefight.app/docs/api/using-the-api/
---

# Using the API

This page covers the mechanics you need for reliable integrations. It assumes you already have a key from [API overview](https://firefight.app/docs/api/overview.md).

## Idempotent incident creation

`POST /api/v1/incidents` requires an `idempotency_key` in the request body. It is any string you choose that uniquely identifies this creation attempt, for example your alert's ID or a UUID you generate.

The key protects you from duplicates when a request times out and you retry.

| Situation | Response |
|---|---|
| First request with a key | `201 Created`, a new incident |
| Repeated request with the same key | `200 OK`, the incident created the first time |

Idempotency keys expire after 24 hours. After that, reusing the same key creates a new incident, so treat keys as protection for retries, not as a long-term deduplication mechanism.

:::tip
Derive the key from the triggering event, such as `pagerduty-alert-8842`. Retries of the same event then dedupe naturally, no matter which of your workers sends them.
:::

## Errors

Every error is JSON with a single `error` object. The `request_id` identifies the request, so include it when you contact support.

```json
{
  "error": {
    "type": "validation_error",
    "message": "Validation failed: Name can't be blank",
    "request_id": "9f2c1d4e-8a7b-4c3d-b5e6-0a1b2c3d4e5f",
    "errors": [
      { "field": "name", "message": "can't be blank" }
    ]
  }
}
```

The `errors` array appears only on validation errors and lists each failing field.

| Status | `type` | When |
|---|---|---|
| 400 | `bad_request` | A required parameter is missing |
| 401 | `unauthorized` | The token is missing, invalid, expired, or deactivated |
| 403 | `forbidden` | The token lacks the permission this endpoint checks |
| 404 | `not_found` | The resource does not exist in your workspace |
| 422 | `validation_error` | The request was well-formed but the data is invalid |
| 429 | `rate_limit_exceeded` | You exceeded 1,000 requests per minute for this key |

## Pagination

List endpoints that can grow large, such as incidents and catalog entries, accept two query parameters.

| Parameter | Default | Maximum |
|---|---|---|
| `page` | 1 | none |
| `per_page` | 25 | 100 |

Every paginated response includes a `pagination` object.

```json
{ "pagination": { "page": 2, "per_page": 25, "total": 132, "total_pages": 6 } }
```

The incident list also accepts filters as query parameters. Use `severity_id`, `status_id`, or `lifecycle_stage` with one of `triage`, `active`, `closed`, or `canceled`. Look up the IDs from `GET /api/v1/severities` and `GET /api/v1/statuses`.

## Worked example: declare, then resolve

First fetch the reference data you need. Severities, statuses, and types are specific to your workspace, so their IDs are too.

```bash
curl https://app.firefight.app/api/v1/severities \
  -H "Authorization: Bearer ff_4kWm2xPqR8vNcT6yBhJd3fLzGaU9sEnQoXwA"
```

```json
{
  "severities": [
    { "id": "c7e2f9a1-3b4d-4c6e-8f0a-5d6b7c8e9f01", "name": "SEV1", "slug": "sev1", "rank": 1, "position": 1, "is_default": false },
    { "id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "name": "SEV2", "slug": "sev2", "rank": 2, "position": 2, "is_default": true }
  ]
}
```

Now declare the incident. Only `name`, `severity_id`, and `idempotency_key` are required. Omitting `status_id` uses your workspace's default status. You can also pass `incident_type_id`, `declared_by_id` with a member's ID, `visibility` as `public` or `private`, `custom_fields` as a key-value object, and a free-form `source` string that tells responders where the incident came from.

```bash
curl -X POST https://app.firefight.app/api/v1/incidents \
  -H "Authorization: Bearer ff_4kWm2xPqR8vNcT6yBhJd3fLzGaU9sEnQoXwA" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotency_key": "datadog-monitor-771204-1626601964",
    "name": "Checkout latency spike in eu-west",
    "summary": "p99 latency on the checkout service exceeded 4s.",
    "severity_id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "source": "datadog"
  }'
```

The response is `201 Created` with the full incident.

```json
{
  "incident": {
    "id": "0d9b2c1e-7f3a-4b8e-9c5d-2a6e8f1b4d70",
    "identifier": "INC-42",
    "name": "Checkout latency spike in eu-west",
    "summary": "p99 latency on the checkout service exceeded 4s.",
    "status": { "id": "b1f6a2d4-8c3e-4f5a-9b7d-1e2c3a4b5d6f", "name": "Investigating", "lifecycle_stage": "active" },
    "severity": { "id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "name": "SEV2", "rank": 2 },
    "type": null,
    "lead": null,
    "source": "datadog",
    "declared_by": null,
    "declared_at": "2026-07-18T09:12:44Z",
    "detected_at": null,
    "resolved_at": null,
    "created_at": "2026-07-18T09:12:44Z",
    "updated_at": "2026-07-18T09:12:44Z",
    "custom_fields": {},
    "visibility": "public"
  }
}
```

Update it with `PATCH /api/v1/incidents/:id`. You can change `name`, `summary`, `status_id`, `severity_id`, `incident_type_id`, or assign a lead with `lead_id`. Moving the incident to a status in the `closed` stage resolves it, and moving a closed incident back to an `active`-stage status reopens it.

```bash
curl -X PATCH https://app.firefight.app/api/v1/incidents/0d9b2c1e-7f3a-4b8e-9c5d-2a6e8f1b4d70 \
  -H "Authorization: Bearer ff_4kWm2xPqR8vNcT6yBhJd3fLzGaU9sEnQoXwA" \
  -H "Content-Type: application/json" \
  -d '{ "status_id": "f0e1d2c3-b4a5-4968-8776-655443322110", "summary": "Rolled back the 14:02 deploy. Latency recovered." }'
```

The response is `200 OK` with the updated incident, including its `resolved_at` timestamp.

:::note
Incidents declared through the API behave exactly like incidents declared from Slack. Channels, workflows, and [webhooks](https://firefight.app/docs/api/webhooks.md) all fire the same way.
:::
