> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tied.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> The documented shape, the shapes you'll actually see, and why they differ.

The OpenAPI schema for this reference declares one error shape for almost every failure response:

```json theme={null}
{
  "description": "string",
  "errors": [
    { "field": "string", "message": "string", "code": "string" }
  ]
}
```

That is the declared contract. **No endpoint in the running application actually produces it.** Every error this API returns is raised by one of four generic handlers in `RequestExceptionHandler`, and each of those renders a narrower shape. The declared schema is a leftover; the shapes below are what your parser will meet.

## The shapes you'll actually see

<Tabs>
  <Tab title="Not found / unauthorized / forbidden">
    A single `error` string, no array:

    ```json theme={null}
    { "error": "Resource could not be found" }
    ```

    This is what a missing record, a missing or invalid token, or a hand-checked permission failure returns — not the `description`/`errors` shape above.
  </Tab>

  <Tab title="Validation failure">
    A `message` string plus the list of attributes that failed, no `field`/`code` breakdown per error:

    ```json theme={null}
    { "message": "Subscriptions Invalid events", "attributes": ["subscriptions"] }
    ```

    This is what you get from any endpoint that validates by raising on `save!` or `update!` — creating a webhook with a bad event name, for instance.
  </Tab>

  <Tab title="Not signed in">
    An `errors` array of **plain strings** — not objects with `field`/`message`/`code`:

    ```json theme={null}
    { "errors": ["You need to sign in or sign up before continuing."] }
    ```

    This is the one you get when the `api_access_token` header is **missing entirely**, because the request never reaches the token check — Devise rejects it first. Send a token that exists but is wrong and you get `{ "error": "Invalid Access Token" }` instead, from the first tab. Both are `401`.
  </Tab>
</Tabs>

<Warning>
  **Don't hard-parse `errors[0].field`.** Nothing returns it. Where an `errors` key does appear it holds plain strings, so `errors[0]` is a sentence, not an object. Read `error`, then `message`, then `errors[0]`, and treat each as a string. All three shapes above are live responses from this API, captured against a running server.
</Warning>

## Status codes in use

| Code | Meaning | Typical cause |
| - | - | - |
| `400` | Bad request | Malformed query params or a body Tied couldn't parse into the shape it expected. |
| `401` | Unauthorized | Missing or invalid `api_access_token` — and also where a policy-level authorization failure lands, see below. |
| `403` | Forbidden | A small number of endpoints hand-check for `administrator` and reject everyone else explicitly. |
| `404` | Not found | The resource doesn't exist, **or exists in a different account than the one your token belongs to.** |
| `422` | Unprocessable | The request was well-formed but failed validation — an invalid webhook subscription name, a duplicate URL, a required field left blank. |
| `429` | Too many requests | You tripped a throttle. Plain-text body, not either shape above — see [Rate limits](/api-reference/rate-limits). |

<Warning>
  **401 and 403 don't split the way you'd expect.** Most authorization failures — a valid token whose role just doesn't permit the action — return `401`, not `403`, because the app's generic policy-failure handler renders them as unauthorized regardless of whether the token itself was valid. A literal `403` mostly shows up on a handful of specific endpoints that check `administrator` by hand and reject with their own message. Don't treat `401` as proof the token itself is bad — check the message.
</Warning>

<Warning>
  **404 is also the cross-tenant response.** Every account-scoped resource is looked up through that account's own association, never by a bare global ID. Requesting `/api/v1/accounts/1/conversations/999` with a token that belongs to account `2` returns `404`, identical to conversation 999 not existing at all. That's deliberate — it avoids confirming a resource exists somewhere you can't reach — but it means a `404` here doesn't always mean "never existed."
</Warning>

There's no documented `5xx` contract. If you get one, it isn't validation feedback of any kind — treat it as transient and retry with backoff rather than trying to parse it as either error shape above.

## A real example

```bash theme={null}
curl -X POST https://app.tied.com/api/v1/accounts/1/webhooks \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"webhook": {"url": "https://example.com/hook", "subscriptions": ["not_a_real_event"]}}'
```

```json theme={null}
{ "message": "Subscriptions Invalid events", "attributes": ["subscriptions"] }
```

## Next

<CardGroup cols={2}>
  <Card title="Idempotency" icon="repeat-2" href="/api-reference/idempotency">
    Which of these are safe to trigger again after a failure.
  </Card>

  <Card title="Rate limits" icon="gauge" href="/api-reference/rate-limits">
    The one status code with a body shape that's different again.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.