> For the complete documentation index, see [llms.txt](https://developer.celigo.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.celigo.com/api/using-the-api/errors.md).

# Errors & status codes

The API uses standard HTTP status codes and returns a consistent JSON error envelope on failure.

## Error envelope

```json
{
  "errors": [
    {
      "code": "invalid_field",
      "message": "`adaptorType` is required when `type` is `http`",
      "field": "adaptorType"
    }
  ]
}
```

* `errors` is **always** an array, even when there's only one error.
* `code` is a stable machine-readable identifier. Switch on it.
* `message` is for humans. It may change between releases; don't pattern-match it.
* `field` (optional) points at the path that caused the failure, useful for form UIs.

A single request can return multiple errors in the array — for example, a validation failure across three fields comes back as three entries, not three round-trips.

## Status codes

| Status                                    | When you see it                                                                 | What to do                                                                                                                              |
| ----------------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `200 OK`                                  | Successful `GET`, `PUT`, `PATCH`, or `DELETE`.                                  | —                                                                                                                                       |
| `201 Created`                             | Successful `POST` that created a resource.                                      | Use `_id` from response body.                                                                                                           |
| `204 No Content`                          | Successful `DELETE` with no body.                                               | —                                                                                                                                       |
| `400 Bad Request`                         | Malformed JSON, missing required header.                                        | Fix the request. Check `Content-Type: application/json`.                                                                                |
| `401 Unauthorized`                        | Missing, malformed, expired, or revoked token.                                  | Rotate the token. See [Authentication](/api/getting-started/authentication.md).                                                         |
| `403 Forbidden`                           | Token is valid but does not carry the capability for this endpoint or resource. | Issue a new token with broader scopes, or switch to an account owner's token.                                                           |
| `404 Not Found`                           | Resource doesn't exist, or you don't have access and the API is hiding it.      | Double-check the `_id`. `404 Not Found` vs `403 Forbidden` is intentional — the API returns `404 Not Found` to avoid leaking existence. |
| `409 Conflict`                            | Write conflict (concurrent update, unique constraint).                          | `GET` the current state, re-apply your changes, retry.                                                                                  |
| `415 Unsupported Media Type`              | Wrong content type on a write.                                                  | Set `Content-Type: application/json`.                                                                                                   |
| `422 Unprocessable Entity`                | Body is well-formed JSON but fails schema/business validation.                  | Read `errors[].field` and `errors[].message`; fix and retry.                                                                            |
| `429 Too Many Requests`                   | Rate limited.                                                                   | Honor `Retry-After`. See [Rate limits](/api/using-the-api/rate-limits.md).                                                              |
| `500 Internal Server Error`               | Unexpected server error.                                                        | Retry with exponential back-off. Report if persistent.                                                                                  |
| `502 Bad Gateway` / `504 Gateway Timeout` | Upstream gateway error.                                                         | Transient. Retry 2-3 times with back-off.                                                                                               |
| `503 Service Unavailable`                 | Service unavailable (maintenance or overload).                                  | Retry with back-off. Check Celigo status page.                                                                                          |

## Retrying safely

| Code                    | Safe to retry?                       | How                                   |
| ----------------------- | ------------------------------------ | ------------------------------------- |
| `429`                   | Yes                                  | Honor `Retry-After` then retry.       |
| `500`/`502`/`503`/`504` | Yes, with care                       | Exponential back-off, max 5 attempts. |
| `409`                   | Yes, after GET-modify-PUT round-trip | Refresh, reapply, retry.              |
| `4xx` other             | **No.** The request itself is wrong. | Fix and resend once.                  |

For `POST`, remember that the request is **not** idempotent. If you retry after a timeout you may create a duplicate. Gate retries on a `GET` if duplication would be a problem.

## Common error codes

| `errors[].code`            | Typical cause                                                                                          |
| -------------------------- | ------------------------------------------------------------------------------------------------------ |
| `invalid_token`            | Expired or revoked; rotate.                                                                            |
| `insufficient_permissions` | Custom-scoped token missing the capability for this endpoint.                                          |
| `invalid_field`            | A field is missing, has the wrong type, or an invalid value.                                           |
| `duplicate_name`           | A resource with the same `name` within the same parent already exists.                                 |
| `resource_in_use`          | Trying to delete something another resource depends on (e.g., a connection referenced by a live flow). |
| `rate_limited`             | Hit the per-account bucket. See [Rate limits](/api/using-the-api/rate-limits.md).                      |

Codes marked `invalid_field` always come with `field`, which is the JSON pointer into your request body (e.g., `http.auth.basic.password`). Use it to attach the error to the right form field in a UI.

## Debugging a failing call

1. **Check the status code**, not the body. `200 OK` with an error-looking body is never a real failure; `4xx` with a happy-looking body is still a failure.
2. **Dump `errors[].field`** — that almost always points at the fix.
3. **Re-send with `-v`** (curl) or your client's verbose mode; confirm the headers you think you're sending are what actually hit the wire.
4. **Hit `/v1/tokenInfo` with the same token** — if that returns `401 Unauthorized`, your token is the problem and the rest of the error is noise.
5. **Reproduce with the CLI**. `celigo <cmd> --verbose` prints the exact HTTP request being sent. If the CLI succeeds on the same input, compare its request to yours.

## Example: handling a 422

{% tabs %}
{% tab title="Node.js" %}

```javascript
const res = await fetch(`${BASE}/v1/connections`, {
  method: "POST",
  headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" },
  body: JSON.stringify({ name: "", type: "http" })
});

if (!res.ok) {
  const { errors } = await res.json();
  for (const e of errors) {
    console.error(`${e.code} on ${e.field ?? "<body>"}: ${e.message}`);
  }
  throw new Error(`Celigo API ${res.status}`);
}
```

{% endtab %}

{% tab title="Python" %}

```python
res = requests.post(
    f"{BASE}/v1/connections",
    headers={"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"},
    json={"name": "", "type": "http"},
    timeout=30,
)

if not res.ok:
    for e in res.json().get("errors", []):
        field = e.get("field", "<body>")
        print(f"{e['code']} on {field}: {e['message']}")
    res.raise_for_status()
```

{% endtab %}

{% tab title="cURL" %}

```bash
response=$(curl -s -w "\n%{http_code}" -X POST "$BASE/v1/connections" \
  -H "Authorization: Bearer $CELIGO_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"","type":"http"}')

body=$(echo "$response" | sed '$d')
code=$(echo "$response" | tail -n1)
echo "status=$code"
echo "$body"
```

{% endtab %}
{% endtabs %}

Output:

```
invalid_field on name: `name` is required and must be a non-empty string
invalid_field on http.baseURI: `http.baseURI` is required when `type` is `http`
```
