# Errors & retries

## Error body

Errors return a JSON body with an `error` string. Most also carry a `message` with more detail, and some add fields that help you fix the request (for example `usage` on a `429`):

```json
{
  "error": "Missing API key",
  "message": "Provide API key in X-API-Key header"
}
```

Branch on the HTTP status first, then on `error`. Treat `message` as human-readable text that can change.

## Status codes

| Status | Meaning | What to do |
|---|---|---|
| `200` | OK | |
| `201` | Created | The body is the new record |
| `202` | Accepted | Work continues in the background, for example a measurement. Poll the status URL in the body |
| `204` | No content | Success with no body, for example a delete |
| `400` | Bad request | A required field is missing or a value is invalid. The body says which. Don't retry unchanged |
| `401` | Unauthorized | The key is missing, wrong or revoked ([Authentication](authentication.md#authentication-errors)) |
| `402` | Payment required | The workspace's billing is past due or inactive |
| `403` | Forbidden | The key lacks a permission, or the plan doesn't include this feature |
| `404` | Not found | The record doesn't exist **in this key's workspace**. Records in other workspaces always look like `404` |
| `409` | Conflict | The change conflicts with the record's current state, for example a duplicate |
| `413` | Too large | The request body or upload is over the size limit |
| `422` | Unprocessable | The request is well-formed but can't be done, for example an address that can't be found |
| `429` | Too many requests | The key reached its call cap, or you're sending too fast. Back off ([Limits & billing](limits-and-billing.md)) |
| `500` | Server error | Our fault. Safe to retry with backoff |
| `502` `503` `504` | Upstream unavailable | A dependency such as an imagery provider is slow or down. Retry with backoff |

A `404` for a path that doesn't exist at all returns `{"error": "Resource not found"}`. If you see that on a documented endpoint, check the method and the path, including `/api/v1`.

## Retries

Retry on network errors, `429`, `500`, `502`, `503` and `504`, with exponential backoff and jitter: wait about 1, 2, 4, 8 seconds, up to five tries. Honor `Retry-After` when it's present.

Don't retry `400`, `401`, `402`, `403`, `404`, `409` or `422` without changing something.

| Request | Safe to retry? |
|---|---|
| `GET` | Yes, always |
| `PATCH` | Yes: sending the same fields twice gives the same result |
| `DELETE` | Yes: a second delete returns `404`, which you can treat as done |
| `POST` that creates a record | Use an external reference so a retry doesn't make a duplicate, and look the record up by that reference before retrying ([Jobs](jobs.md#syncing-with-your-own-system)) |
| `POST /detect/*` | Each accepted call starts a new measurement and counts as one. If a start call times out, check your recent results before trying again ([Measure a site](measure-a-site.md)) |

## Timeouts

Use a client timeout of about 30 seconds for normal calls and 60 seconds for downloads. Measurements don't need long timeouts: the start call returns quickly with a `job_id` and you poll for the result.

## Getting help with an error

Email [support@trutec.ai](mailto:support@trutec.ai) with the method and path, the time of the call (with time zone), the status and response body, and your key's 11-character prefix. Never send the full key.
