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

# Errors

> A complete reference of error codes returned by the Shelfforce API.

The Shelfforce API uses conventional HTTP status codes and returns structured error responses with machine-readable error codes.

## Error response format

```json theme={null}
{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "The 'imageUrl' field is required.",
    "details": {
      "field": "imageUrl",
      "reason": "required"
    }
  }
}
```

| Field     | Type     | Description                                   |
| --------- | -------- | --------------------------------------------- |
| `code`    | `string` | Machine-readable error code (see table below) |
| `message` | `string` | Human-readable description                    |
| `details` | `object` | Optional additional context                   |

## Error codes

<ResponseField name="AUTH_REQUIRED" type="401">
  Missing `Authorization` header or invalid format. Include your API key as `Authorization: Bearer sf_live_...`.
</ResponseField>

<ResponseField name="INVALID_API_KEY" type="401">
  API key not found, revoked, or expired. Verify in [API Keys settings](https://shelfforce.ai/home/developers).
</ResponseField>

<ResponseField name="INSUFFICIENT_CREDITS" type="402">
  Not enough credits. [Purchase more](https://shelfforce.ai/drive/settings?tab=usage) or upgrade your plan.
</ResponseField>

<ResponseField name="FORBIDDEN" type="403">
  API key role lacks permission for this endpoint. See [Authentication](/authentication#key-roles).
</ResponseField>

<ResponseField name="NOT_FOUND" type="404">
  Resource does not exist or belongs to a different organisation.
</ResponseField>

<ResponseField name="VALIDATION_FAILED" type="422">
  Missing required fields or invalid values. Check the `details` field.
</ResponseField>

<ResponseField name="RATE_LIMITED" type="429">
  Rate limit exceeded. Wait for `Retry-After` seconds. See [Rate Limits](/rate-limits).
</ResponseField>

<ResponseField name="INTERNAL_ERROR" type="500">
  Unexpected server error. Safe to retry with backoff. Contact [hey@shelfforce.ai](mailto:hey@shelfforce.ai) if persistent.
</ResponseField>

## Handling errors

<Tabs>
  <Tab title="By status code">
    * **4xx** — Client errors. Fix the request before retrying. Do not retry `401`, `402`, `403`, or `422`.
    * **429** — Rate limited. Wait for `Retry-After`, then retry with [exponential backoff](/rate-limits#example-exponential-backoff).
    * **5xx** — Server errors. Safe to retry with exponential backoff.
  </Tab>

  <Tab title="Node.js">
    ```typescript theme={null}
    const response = await fetch("https://shelfforce.ai/api/v1/analyses", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${apiKey}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ imageUrl }),
    });

    if (!response.ok) {
      const { error } = await response.json();

      switch (error.code) {
        case "AUTH_REQUIRED":
        case "INVALID_API_KEY":
          throw new Error(`Authentication failed: ${error.message}`);

        case "INSUFFICIENT_CREDITS":
          await alertTeam("Credits exhausted", error.message);
          throw new Error(error.message);

        case "RATE_LIMITED":
          const retryAfter = parseInt(
            response.headers.get("Retry-After") || "10", 10
          );
          await sleep(retryAfter * 1000);
          return retry();

        case "INTERNAL_ERROR":
          await sleep(2000);
          return retry();
      }
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    response = requests.post(
        "https://shelfforce.ai/api/v1/analyses",
        headers={
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
        },
        json={"imageUrl": image_url},
    )

    if not response.ok:
        error = response.json()["error"]

        if error["code"] in ("AUTH_REQUIRED", "INVALID_API_KEY"):
            raise Exception(f"Auth failed: {error['message']}")

        elif error["code"] == "RATE_LIMITED":
            retry_after = int(response.headers.get("Retry-After", "10"))
            time.sleep(retry_after)
            return retry()

        elif error["code"] == "INTERNAL_ERROR":
            time.sleep(2)
            return retry()
    ```
  </Tab>
</Tabs>

<Tip>
  Always check the `code` field for programmatic error handling rather than parsing the `message` string. Error messages may change, but codes are stable.
</Tip>
