# 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`         | Successful `GET`, `PUT`, `PATCH`, or `DELETE`.                                  | —                                                                                                         |
| `201`         | Successful `POST` that created a resource.                                      | Use `_id` from response body.                                                                             |
| `204`         | Successful `DELETE` with no body.                                               | —                                                                                                         |
| `400`         | Malformed JSON, missing required header.                                        | Fix the request. Check `Content-Type: application/json`.                                                  |
| `401`         | Missing, malformed, expired, or revoked token.                                  | Rotate the token. See [Authentication](/api/getting-started/authentication.md).                           |
| `403`         | 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`         | Resource doesn't exist, or you don't have access and the API is hiding it.      | Double-check the `_id`. `404` vs `403` is intentional — the API returns `404` to avoid leaking existence. |
| `409`         | Write conflict (concurrent update, unique constraint).                          | `GET` the current state, re-apply your changes, retry.                                                    |
| `415`         | Wrong content type on a write.                                                  | Set `Content-Type: application/json`.                                                                     |
| `422`         | Body is well-formed JSON but fails schema/business validation.                  | Read `errors[].field` and `errors[].message`; fix and retry.                                              |
| `429`         | Rate limited.                                                                   | Honor `Retry-After`. See [Rate limits](/api/using-the-api/rate-limits.md).                                |
| `500`         | Unexpected server error.                                                        | Retry with exponential back-off. Report if persistent.                                                    |
| `502` / `504` | Upstream gateway error.                                                         | Transient. Retry 2-3 times with back-off.                                                                 |
| `503`         | 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` 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 401s, 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`
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://developer.celigo.com/api/using-the-api/errors.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
