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

# API errors

> HTTP status codes and error response formats for the CrazyGoldFish APIs, with code to read error messages from every API family.

When the API can't complete a request, it responds with an error: a status code, such as `404`, and a message that explains what went wrong. The status code tells you the kind of problem, and the message tells you what to fix.

The exact format of the message depends on the API family. To tell the families apart, see [Conventions](/api-reference/all-apis#conventions).

## Status codes

| Code  | Lesson Plan, Worksheet, Metadata, Action Plans, ClassTrack                         | Exam, Assignment, Rubrics, Webhooks                                                     |
| ----- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| `400` | Invalid field value, a step already ran, or a job failed                           | Invalid request. Also returned when the daily limit for question extraction is reached. |
| `401` | Missing, invalid, or expired token                                                 | Not used                                                                                |
| `403` | Not used                                                                           | Missing, invalid, or expired token                                                      |
| `404` | Resource not found, or a job hasn't started yet                                    | Resource not found                                                                      |
| `422` | Request body or path parameter doesn't match the schema, or a total doesn't add up | Not used                                                                                |
| `429` | Daily limit reached                                                                | Daily limit for evaluating answer sheets reached                                        |
| `500` | Something went wrong on our side. Retry, and contact CrazyGoldFish if it persists. | Something went wrong on our side. The body is not always JSON.                          |

## Error formats

<Tabs>
  <Tab title="Lesson Plan, Worksheet, Metadata">
    Most errors put messages in a `detail` array:

    ```json theme={null}
    { "detail": [{ "msg": "Worksheet not found." }] }
    ```

    When a background job failed, the item also has the job `id` and `status`:

    ```json theme={null}
    { "detail": [{ "id": "152764ca-1685-427c-aba7-4b92dcc0d60f", "status": "Failed", "msg": "No content found" }] }
    ```

    Schema validation errors (`422`) add the location of the invalid field:

    ```json theme={null}
    { "detail": [{ "type": "missing", "loc": ["body", "number_of_questions"], "msg": "Field required", "input": null }] }
    ```

    Authentication errors (`401`) use a different shape:

    ```json theme={null}
    { "message": "Unauthorized User", "data": {}, "status": "" }
    ```
  </Tab>

  <Tab title="Exam, Assignment, Rubrics, Webhooks">
    Most errors put messages in `error.message`, keyed by field. Errors that aren't about one field use `non_field_errors`:

    ```json theme={null}
    { "error": { "code": 400, "message": { "examName": ["This field is required."] } } }
    ```

    ```json theme={null}
    { "error": { "code": 400, "message": { "non_field_errors": ["Exam is not processed yet"] } } }
    ```

    Authentication errors (`403`) use a `detail` string:

    ```json theme={null}
    { "detail": "Token has expired" }
    ```
  </Tab>

  <Tab title="Token endpoint">
    `POST /token` returns `401` for bad credentials:

    ```json theme={null}
    { "message": "Incorrect username or password", "data": {}, "status": "", "status_code": 401 }
    ```
  </Tab>
</Tabs>

## Read any error message

This helper returns a readable message from any CrazyGoldFish error response:

<CodeGroup>
  ```python Python theme={null}
  def error_message(response):
      """Return a readable message from a CrazyGoldFish error response."""
      try:
          body = response.json()
      except ValueError:
          return f"HTTP {response.status_code}"  # Some 500 errors return HTML
      if "message" in body:  # 401 errors and the token endpoint
          return body["message"]
      if "error" in body:  # Exam, Assignment, Rubrics, Webhooks
          return "; ".join(
              f"{field}: {' '.join(map(str, msgs)) if isinstance(msgs, list) else msgs}"
              for field, msgs in body["error"]["message"].items()
          )
      detail = body.get("detail")
      if isinstance(detail, str):  # 403 errors from Exam, Assignment, Rubrics, Webhooks
          return detail
      if isinstance(detail, list):  # Lesson Plan, Worksheet, Metadata
          return "; ".join(item.get("msg", "") for item in detail)
      return str(body)
  ```

  ```javascript JavaScript theme={null}
  async function errorMessage(response) {
    let body;
    try {
      body = await response.json();
    } catch {
      return `HTTP ${response.status}`; // Some 500 errors return HTML
    }
    if (body.message) return body.message; // 401 errors and the token endpoint
    if (body.error) {
      // Exam, Assignment, Rubrics, Webhooks
      return Object.entries(body.error.message)
        .map(([field, msgs]) => `${field}: ${[].concat(msgs).join(" ")}`)
        .join("; ");
    }
    if (typeof body.detail === "string") return body.detail; // 403 errors
    if (Array.isArray(body.detail)) return body.detail.map((item) => item.msg).join("; ");
    return JSON.stringify(body);
  }
  ```
</CodeGroup>

## When to retry

| Response                                                  | Retry?                                                                                                      |
| --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `401` or `403` for an expired token                       | Yes, after you [get a new token](/api-reference/endpoint/authentication)                                    |
| `404` right after starting a Lesson Plan or Worksheet job | Yes. The job hasn't started yet. See [Async jobs](/api-reference/async-jobs-and-webhooks#poll-for-results). |
| `429`                                                     | Not until the limit resets at 00:01 server time. Rejected requests also count toward daily limits.          |
| `500`, `502`, `503`                                       | Yes, with exponential backoff. Don't retry a `POST` that starts a job until you've checked it didn't start. |
| Other `4xx`                                               | No. Fix the request first.                                                                                  |
