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

# Errors

> Work out what went wrong and what to do about it: the error body, the status codes the API returns, and how to handle each in the SDK and over MCP.

Every failed request returns a JSON body with an `error` field and an HTTP status code:

```json theme={null}
{ "error": "run already in a terminal state" }
```

The status code tells you the class of failure; the `error` string is a short, human-readable
reason.

## Success codes

```sh theme={null}
curl -sS -o /dev/null -w "%{http_code}\n" -X POST "$DURATON_URL/events" \
  -H "Authorization: Bearer $DURATON_API_KEY" \
  -d '{"name":"ticket.created","app":"support-app","data":{"ticketId":"T-421"}}'
# 202
```

| Status           | Returned by                                                                                                                                                                                                                                             |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200 OK`         | Every read, the control writes (`cancel`, `pause`, `resume`, `replay`, `retry-from-step`, `bulk-replay`), an approval decision, a `PATCH` on a webhook endpoint/source, and `POST /webhook-source-deliveries/{id}/replay` (returns the replay outcome). |
| `201 Created`    | `POST /webhook-endpoints`, `POST /webhook-sources`.                                                                                                                                                                                                     |
| `202 Accepted`   | `POST /events` and `POST /webhooks/{token}` - the event is recorded; the runs it starts are asynchronous.                                                                                                                                               |
| `204 No Content` | `DELETE /webhook-endpoints/{id}`, `DELETE /webhook-sources/{id}`, `POST /webhook-deliveries/{id}/redeliver`.                                                                                                                                            |

## Status codes

| Status                      | Meaning                                                                                                                                                                                                                                                                                                                                                                                 |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400 Bad Request`           | The request body is malformed, or a field is invalid - an event names a workflow that isn't registered, an event carries an [unsupported character](/reference/api/events#unsupported-characters), an approval decision isn't `approved`/`denied`, or a `GET /webhook-source-deliveries?status=` value is outside the [accepted set](/reference/api/webhooks#the-inbound-delivery-log). |
| `401 Unauthorized`          | The API key is missing or unknown.                                                                                                                                                                                                                                                                                                                                                      |
| `403 Forbidden`             | A read-only (public) key attempted a write, or the project is suspended after its workspace reached the plan limit.                                                                                                                                                                                                                                                                     |
| `404 Not Found`             | The addressed resource - run, step, event, approval, or webhook source/endpoint/delivery - does not exist.                                                                                                                                                                                                                                                                              |
| `409 Conflict`              | The request conflicts with current state (see below).                                                                                                                                                                                                                                                                                                                                   |
| `501 Not Implemented`       | The requested capability isn't configured for this project (for example, failure explanations when no model is wired up).                                                                                                                                                                                                                                                               |
| `502 Bad Gateway`           | The status reserved for a runner-not-registered condition. Note that triggering a run against an app with no live [runner](/core/runners) does **not** return this: `POST /events` is still accepted (`202`) and the run [parks and retries](/core/runners#when-no-runner-is-registered), failing terminally only after the bounded no-runner wait elapses.                             |
| `500 Internal Server Error` | An unexpected fault. The body is a generic `{ "error": "internal error" }`; the detail is logged server-side and never returned.                                                                                                                                                                                                                                                        |

## Conflicts (409)

A `409` means the resource is in a state that doesn't allow the operation. The common cases:

| `error`                                                                       | Cause                                                                                            |
| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `run already in a terminal state`                                             | Pausing, resuming, or cancelling a run that already succeeded, failed, or was cancelled.         |
| `run is not in a terminal state`                                              | Replaying a run that hasn't finished.                                                            |
| `run is not paused`                                                           | Resuming a run that isn't paused.                                                                |
| `run is not awaiting a decision`                                              | Deciding an approval on a run that isn't waiting for one.                                        |
| `approval is not open`                                                        | Approving or denying an approval that was already decided.                                       |
| `step attempt already recorded`                                               | A duplicate step result (a runner retried after Duraton already recorded the step).              |
| `webhook token already in use`                                                | Creating an inbound source whose issued receive URL token collides (rare - the token is random). |
| `webhook source name already in use` / `webhook endpoint name already in use` | A source/endpoint name isn't unique in the project.                                              |
| `webhook endpoint url already subscribed`                                     | The outbound URL is already registered in the project.                                           |
| `webhook delivery is in flight`                                               | Redelivering a webhook whose current attempt hasn't settled.                                     |

Conflicts are safe to surface to the caller and, where they reflect a race (a duplicate step, an
in-flight delivery), safe to ignore.

## Handling errors

<Tabs>
  <Tab title="REST">
    Check the status code and read the `error` field:

    ```sh theme={null}
    curl -sS -o /tmp/body -w "%{http_code}" \
      -X POST "$DURATON_URL/runs/$RUN_ID/resume" \
      -H "Authorization: Bearer $DURATON_API_KEY"
    # 409
    cat /tmp/body
    # { "error": "run is not paused" }
    ```
  </Tab>

  <Tab title="SDK">
    The client throws `DuratonApiError`, which carries the `status` and raw `body` and has helpers for the
    common classes:

    ```ts theme={null}
    import { createClient, DuratonApiError } from "@duraton/sdk/client";

    const duraton = createClient({
      url: process.env.DURATON_URL!,
      apiKey: process.env.DURATON_API_KEY!,
    });

    try {
      await duraton.runs.resume(runId);
    } catch (err) {
      if (err instanceof DuratonApiError) {
        if (err.isConflict()) {
          // 409: the run wasn't paused - already resumed or finished
        } else if (err.isNotFound()) {
          // 404
        } else {
          console.error(err.status, err.body);
        }
        // also: err.isBadRequest(), err.isUnauthorized(), err.isForbidden()
      }
    }
    ```
  </Tab>

  <Tab title="MCP">
    When a tool call fails, the [MCP server](/integrations/mcp-server) returns the same reason as the tool result's
    error - for example, calling `resume_run` on a run that isn't paused returns `run is not paused`.
    Unexpected server faults are masked to `internal error`, exactly as over REST, and a write tool called
    with a read-only key returns `this tool requires a full-access (secret) API key`.
  </Tab>
</Tabs>
