# Errors

> HTTP 401 is missing auth. HTTP 422 is a GraphQL error. Mutation payloads also return errors: [String!]! for domain failures.

Source: https://constructable.ai/api-docs/errors

Constructable uses two error channels: HTTP / GraphQL execution errors, and per-mutation `errors` arrays.

## HTTP status

| Status | Meaning                                                                                                                          |
| ------ | -------------------------------------------------------------------------------------------------------------------------------- |
| `200`  | GraphQL executed without execution errors. Still read `data` and any mutation `errors` arrays.                                   |
| `401`  | Missing or invalid Bearer token. GraphQL also sends `WWW-Authenticate` with `scope="api"` and the GraphQL resource metadata URL. |
| `422`  | GraphQL returned an `errors` array (validation, missing record, authorization, invalid document, query limit).                   |

The JSON body for a GraphQL response is `{ "data": ..., "errors": ... }`. `errors` is omitted on a clean `200`.

## Execution errors

These appear in the top-level `errors` array. Typical messages:

- `Organization not found` — `organization(id:)` on a mutation does not resolve for this user
- `Project not found` — nested `project(id:)` on a mutation does not resolve
- Permission failures from `Permit` ("You do not have permission…")
- Unknown field or argument (the document does not match the schema)

Example:

```json
{
  "data": { "organization": null },
  "errors": [
    {
      "message": "Project not found",
      "locations": [{ "line": 4, "column": 5 }],
      "path": ["organization", "project"]
    }
  ]
}
```

Treat any top-level `errors` entry as a failed request, even if `data` is partially present.

## Query limits

The server checks every document against two limits before running it. A document over either limit does not execute and returns HTTP `422` with one top-level error.

| Limit | Maximum                   | Error message                                           |
| ----- | ------------------------- | ------------------------------------------------------- |
| Depth | 15 levels of fields       | `Query has depth of <n>, which exceeds max depth of 15` |
| Size  | 5,000 tokens per document | `This query is too large to execute.`                   |

Depth counts every nested field selection, including `organization`, `project`, `nodes`, and the leaf field. The RFI list in [Examples](https://constructable.ai/api-docs/examples#list-rfis-on-a-project) is 6 levels deep: `organization` → `project` → `rfis` → `nodes` → `managerUser` → `id`. Following a relation back to its parent, such as `issues { nodes { project { ... } } }`, adds 3 levels each time. The standard introspection query fits within the limit.

Every name, value, punctuation mark, and comment counts as a token. Aliasing the same field thousands of times in one document reaches the size limit.

If you hit a limit, split the work into smaller requests. For example, load record ids in one query, then fetch nested details for those ids in another.

## Mutation payload errors

Writes return a payload with `errors: [String!]!`. Domain problems (validation, conflict, empty required field) usually land here instead of the top-level array, and HTTP stays `200`.

```graphql
mutation OpenRfi($organizationId: ID!, $projectId: ID!, $input: OpenRfiInput!) {
  organization(id: $organizationId) {
    project(id: $projectId) {
      openRfi(input: $input) {
        rfi {
          id
          status
          number
        }
        errors
      }
    }
  }
}
```

Handle it as:

1. If the HTTP status is `401` or `422`, inspect top-level `errors`.
2. If `data.organization.project.openRfi` is `null`, the nested mutation did not run.
3. If `errors` on the payload is non-empty, the write did not apply. The returned `rfi` (or other record) may be `null`.
4. If `errors` is `[]` and the record is present, the write succeeded.

## Idempotent creates

Many create mutations no-op when a record with the given `id` already exists, returning the existing record or a null record with empty `errors` rather than raising. If you retry, use the same client-generated `id`.

## Partial data

GraphQL can return `data` alongside `errors` when a sibling field fails. Check both. Do not assume `data.organization` is complete if `errors` is present.
