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

# Errors

> HTTP status codes and error response format for the Grain API.

The Grain API uses standard HTTP status codes. Error responses include a JSON body with a `message` field describing what went wrong.

## Error response format

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "message": "You do not have access to this organization."
}
```

Some validation errors may include additional fields depending on the endpoint.

## Common status codes

| Status | Meaning              | Typical cause                                                 |
| ------ | -------------------- | ------------------------------------------------------------- |
| `400`  | Bad request          | Missing `X-Organization-Id` header or invalid request body    |
| `401`  | Unauthorized         | Missing or expired Bearer token                               |
| `403`  | Forbidden            | User lacks permission for the action or organization          |
| `404`  | Not found            | Resource ID does not exist in the organization                |
| `409`  | Conflict             | Duplicate record (for example donor with same name and email) |
| `422`  | Unprocessable entity | Validation failed on one or more fields                       |
| `429`  | Too many requests    | Rate limit exceeded                                           |
| `500`  | Server error         | Unexpected error; retry with backoff                          |

## Authentication errors

<AccordionGroup>
  <Accordion title="401 — Authentication required">
    ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
    {
      "message": "Authentication required. Provide a valid Bearer token."
    }
    ```

    Refresh your access token or re-run the OAuth flow.
  </Accordion>

  <Accordion title="400 — Missing organization header">
    ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
    {
      "message": "X-Organization-Id header is required for all API requests."
    }
    ```

    Add the header on every request. See [Organizations](/organizations).
  </Accordion>

  <Accordion title="403 — Organization access denied">
    ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
    {
      "message": "You do not have access to this organization."
    }
    ```

    The authenticated user is not a member of the organization in `X-Organization-Id`.
  </Accordion>
</AccordionGroup>

## Handling errors in your integration

<Tip>
  Log the status code and `message` field for debugging. Do not log full Bearer tokens.
</Tip>

<CodeGroup>
  ```javascript Node.js theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const response = await fetch(url, { headers });

  if (!response.ok) {
    const error = await response.json().catch(() => ({}));
    throw new Error(`Grain API ${response.status}: ${error.message ?? response.statusText}`);
  }
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  response = requests.get(url, headers=headers)

  if not response.ok:
      error = response.json() if response.content else {}
      raise RuntimeError(
          f"Grain API {response.status_code}: {error.get('message', response.reason)}"
      )
  ```
</CodeGroup>

## Rate limiting

When you exceed the per-organization rate limit, the API returns `429` with rate limit headers. Wait until `X-RateLimit-Reset` before retrying. See [Introduction](/intro#rate-limits) for default limits.
