# Error handling

The MK.IO APIs return the same error body for every failure. Once your client handles that one shape, the rest of error handling is consistent across the platform: inspect the HTTP status, branch on the machine-readable code, log the human-readable detail, and keep the request reference for follow-up.

## The standard error body

When an operation fails, the API returns a JSON object with this shape:

```json
{
  "error": {
    "code": "<error_code>",
    "detail": "<error_message>",
    "extraDetail": {}
  },
  "status": 400,
  "ref": "<request_reference>"
}
```

Each field has a distinct job:

<table>
  <thead>
    <tr>
      <th style="text-align: left">Field</th>
      <th style="text-align: left">Use it for</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code>error.code</code></td>
      <td style="text-align: left">Programmatic branching in your application.</td>
    </tr>
    <tr>
      <td style="text-align: left"><code>error.detail</code></td>
      <td style="text-align: left">Logs, dashboards, and operator-facing messages.</td>
    </tr>
    <tr>
      <td style="text-align: left"><code>error.extraDetail</code></td>
      <td style="text-align: left">Any additional context the API includes.</td>
    </tr>
    <tr>
      <td style="text-align: left"><code>status</code></td>
      <td style="text-align: left">The HTTP status code, repeated in the body.</td>
    </tr>
    <tr>
      <td style="text-align: left"><code>ref</code></td>
      <td style="text-align: left">Support follow-up and request tracing.</td>
    </tr>
  </tbody>
</table>

If you keep only one field beyond the status line, keep `ref`. It is the quickest way to identify a failing request afterwards.

## Status codes across the platform

The exact response set varies by endpoint, but these codes appear throughout the APIs.

Success responses:

<table>
  <thead>
    <tr>
      <th style="text-align: left">Code</th>
      <th style="text-align: left">Meaning</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code>200</code></td>
      <td style="text-align: left">The request succeeded and returned a body.</td>
    </tr>
    <tr>
      <td style="text-align: left"><code>201</code></td>
      <td style="text-align: left">The resource was created.</td>
    </tr>
    <tr>
      <td style="text-align: left"><code>202</code></td>
      <td style="text-align: left">The request was accepted and continues asynchronously.</td>
    </tr>
    <tr>
      <td style="text-align: left"><code>204</code></td>
      <td style="text-align: left">The request succeeded with no response body.</td>
    </tr>
  </tbody>
</table>

Client-side problems:

<table>
  <thead>
    <tr>
      <th style="text-align: left">Code</th>
      <th style="text-align: left">Meaning</th>
      <th style="text-align: left">What to do next</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code>400</code></td>
      <td style="text-align: left">Bad Request</td>
      <td style="text-align: left">Recheck the path, query parameters, and body format.</td>
    </tr>
    <tr>
      <td style="text-align: left"><code>401</code></td>
      <td style="text-align: left">Unauthorized</td>
      <td style="text-align: left">Recheck the bearer token and <code>Authorization</code> header.</td>
    </tr>
    <tr>
      <td style="text-align: left"><code>403</code></td>
      <td style="text-align: left">Forbidden</td>
      <td style="text-align: left">Recheck organization, project, and operation access for the token's user.</td>
    </tr>
    <tr>
      <td style="text-align: left"><code>404</code></td>
      <td style="text-align: left">Not Found</td>
      <td style="text-align: left">Recheck names and path parameters, especially project-scoped names.</td>
    </tr>
    <tr>
      <td style="text-align: left"><code>409</code></td>
      <td style="text-align: left">Conflict</td>
      <td style="text-align: left">Recheck the resource state, or whether the operation conflicts with an existing reference.</td>
    </tr>
    <tr>
      <td style="text-align: left"><code>429</code></td>
      <td style="text-align: left">Too Many Requests</td>
      <td style="text-align: left">Back off and retry later. See <a href="/mkio/api-guides/understanding/rate-limits">Rate limits</a>.</td>
    </tr>
  </tbody>
</table>

Server-side problems:

<table>
  <thead>
    <tr>
      <th style="text-align: left">Code</th>
      <th style="text-align: left">Meaning</th>
      <th style="text-align: left">What to do next</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code>500</code></td>
      <td style="text-align: left">Internal Server Error</td>
      <td style="text-align: left">Retry carefully and keep the <code>ref</code> value.</td>
    </tr>
    <tr>
      <td style="text-align: left"><code>503</code></td>
      <td style="text-align: left">Service Unavailable</td>
      <td style="text-align: left">Treat it as temporary and keep the <code>ref</code> value if it persists.</td>
    </tr>
  </tbody>
</table>

## A handling order that works

When a request fails:

1. Record the HTTP status code.
2. Parse `error.code` and `error.detail`.
3. Record `ref`.
4. Decide whether to fix the request, retry it, or surface it to an operator.

As a guide to that decision:

- `400`, `401`, `403`, and many `404` responses mean the request needs correcting.
- `409` usually means the operation is valid, but the target resource is not in the right state yet, or is still referenced elsewhere.
- `429`, `500`, and `503` are the cases where careful retry logic pays off.

## See the status and body together

When you debug from the command line, print the transport status alongside the body so a log line captures both:

```bash
curl -sS \
  -H "Authorization: Bearer <YOUR_TOKEN>" \
  -H "Accept: application/json" \
  -w "\nHTTP Status: %{http_code}\n" \
  "https://app.mk.io/api/v1/projects/<PROJECT_NAME>/media/assets/nonexistent"
```
