# Error format

URL: https://submitator.com/docs/error-format

> Every 4xx and 5xx response has one shape. Branch on the code, show the message, log the request id.

Every error, an unknown path and a body that is not JSON included, returns the same envelope:

```json
{
  "error": {
    "code": "not_ready",
    "message": "Add the missing items, then launch again. Nothing was spent.",
    "errors": [
      {
        "param": "contact_email",
        "code": "required",
        "message": "Add a contact email. Some directories register the listing to it."
      }
    ],
    "doc_url": "https://submitator.com/docs/errors#not_ready",
    "request_id": "6b0f2c9e-6a41-4c55-9d2a-0f0d3c1a8e77"
  }
}
```

## Fields

| Field | Always there | What it holds |
| --- | --- | --- |
| `code` | Yes | The stable code. Branch on it; [Error codes](https://submitator.com/docs/errors) lists all 32. |
| `message` | Yes | A sentence for people that says what happened and what to do. It can change, so never parse it. |
| `doc_url` | Yes | The section of the error codes page for this code. |
| `request_id` | Yes | The request's id, the same as the `X-Request-Id` header. Quote it when you write to support. |
| `param` | No | The parameter at fault, when there is one. |
| `errors` | No | Every field to fix, on `validation_failed`, `not_ready` and `invalid_directories`. |
| `existing` | No | The project in the way, on `project_exists` and `already_launched`. |

## Field errors

Each item of `errors` names one field in `param`, as a path such as `tagline`, `competitors[2].url` or `directories.include[0]`. Its `code` comes from this closed list:

| Code | Meaning |
| --- | --- |
| `required` | The field is missing or empty. |
| `invalid` | The value is not allowed here; `message` says why. |
| `invalid_enum` | The value is not one of the listed values. |
| `invalid_url` | Not an `http` or `https` URL. |
| `not_public_url` | Live mode only: the host is an IP address or a reserved name such as `.test`. |
| `invalid_email` | Not an email address. |
| `too_short` | Shorter than the minimum length. |
| `too_long` | Longer than the maximum length. |
| `too_many` | The list has more items than allowed. |
| `immutable` | The field cannot change any more, such as `contact_email` after launch. |
| `file_too_large` | The image is larger than 5 MB. |
| `unsupported_file_type` | The file is not a PNG, JPEG, GIF or WebP image. |
| `import_failed` | We could not download an image from the URL. |

## Handle errors in your code

- **4xx:** the request was wrong. Fix it before you send it again; a retry with the same body gets the same answer.
- **409 `idempotency_key_in_use`:** the first request with this `Idempotency-Key` is still running. Retry in 1 second with the same key.
- **429:** wait the seconds in `Retry-After`, then send the same request again.
- **500 and 503:** retry with backoff, for example after 1, 2, 4 and 8 seconds. Send a `POST` again with the same `Idempotency-Key`, so it runs at most once. If it repeats, write to support with the `request_id`.

Show `message` to the person who can fix the problem, and keep `code` and `request_id` in your logs. A failed launch spends nothing, and its message says so.

## HTTP statuses

| Status | Codes |
| --- | --- |
| 400 | `invalid_json`, `invalid_query`, `unknown_parameter`, `invalid_cursor` |
| 401 | `api_key_missing`, `api_key_invalid` |
| 402 | `no_launches_left` |
| 403 | `account_suspended`, `permission_denied`, `test_mode_only` |
| 404 | `not_found`, `route_not_found` |
| 405 | `method_not_allowed` |
| 409 | `already_launched`, `project_exists`, `idempotency_key_in_use` |
| 410 | `gone`, `cursor_expired` |
| 415 | `unsupported_media_type` |
| 422 | `validation_failed`, `not_ready`, `nothing_to_submit`, `invalid_directories`, `allowance_exhausted`, `badge_not_found`, `image_unreachable`, `endpoint_url_invalid`, `idempotency_key_reused`, `quota_exceeded` |
| 429 | `rate_limited` |
| 500 | `internal_error` |
| 503 | `unavailable` |
