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

# Errors

# API error codes and HTTP status mapping

> Every EvalGate error code, its HTTP status, what it means, and how to handle errors in TypeScript and Python with try/catch examples.

When a request fails, EvalGate always returns a JSON object in the same envelope shape — regardless of the HTTP status code. Parsing this envelope lets you handle every error class in a single place.

## Error envelope

Every error response has this structure:

```json theme={null} theme={null}
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "name is required",
    "details": null,
    "requestId": "550e8400-e29b-41d4-a716-446655440000"
  }
}
```

<ResponseField name="error.code" type="string">
  A machine-readable constant identifying the error class. Use this field for programmatic error handling — never parse `message`.
</ResponseField>

<ResponseField name="error.message" type="string">
  A human-readable description of what went wrong. Suitable for logs and development debugging; do not display this to end users without filtering.
</ResponseField>

<ResponseField name="error.details" type="unknown | null">
  Optional additional context. For `VALIDATION_ERROR`, this contains a Zod `issues` array describing which fields failed and why. `null` for all other codes.
</ResponseField>

<ResponseField name="error.requestId" type="string">
  A UUID that uniquely identifies the request on EvalGate's servers. Include this value in any support ticket — the team can use it to locate the exact request in logs. The same UUID appears in the `x-request-id` response header.
</ResponseField>

## Error codes and HTTP status mapping

| Code                  | HTTP status | When it occurs                                                            |
| --------------------- | ----------- | ------------------------------------------------------------------------- |
| `UNAUTHORIZED`        | 401         | Missing or invalid API key                                                |
| `FORBIDDEN`           | 403         | Key lacks required scope for this resource                                |
| `NO_ORG_MEMBERSHIP`   | 403         | Authenticated user is not a member of the target organization             |
| `QUOTA_EXCEEDED`      | 403         | Organization has reached a usage limit (upgrade required)                 |
| `NOT_FOUND`           | 404         | Resource does not exist, or the ID belongs to a different organization    |
| `VALIDATION_ERROR`    | 400         | Request body or query parameters failed schema validation                 |
| `CONFLICT`            | 409         | A resource with the same identifier already exists                        |
| `RATE_LIMITED`        | 429         | Too many requests — back off and retry after the `X-RateLimit-Reset` time |
| `INTERNAL_ERROR`      | 500         | Unexpected server error — include the `requestId` when contacting support |
| `SERVICE_UNAVAILABLE` | 503         | EvalGate is temporarily unavailable — retry with exponential backoff      |

<Note>
  `QUOTA_EXCEEDED` and `NO_ORG_MEMBERSHIP` both map to HTTP 403. Distinguish them by the `code` field, not the status code.
</Note>

## Handling errors in code

<CodeGroup>
  ```typescript TypeScript theme={null} theme={null}
  import { AIEvalClient } from '@evalgate/sdk';

  const client = AIEvalClient.init();

  try {
    const evaluation = await client.evaluations.create({
      name: 'My eval',
      type: 'unit_test',
    });
    console.log('Created:', evaluation.id);
  } catch (err: unknown) {
    if (err instanceof Error && 'code' in err) {
      const apiErr = err as { code: string; message: string; requestId?: string };
      switch (apiErr.code) {
        case 'UNAUTHORIZED':
          console.error('Check your EVALGATE_API_KEY environment variable.');
          break;
        case 'RATE_LIMITED':
          console.error('Rate limit hit — retrying after backoff.');
          break;
        case 'VALIDATION_ERROR':
          console.error('Invalid request:', apiErr.message);
          break;
        default:
          console.error(`API error ${apiErr.code}: ${apiErr.message}`);
          console.error('Request ID for support:', apiErr.requestId);
      }
    } else {
      throw err;
    }
  }
  ```

  ```python Python theme={null} theme={null}
  import os
  import requests

  BASE_URL = "https://evalgate.com"
  HEADERS = {
      "Authorization": f"Bearer {os.environ['EVALGATE_API_KEY']}",
      "Content-Type": "application/json",
  }

  response = requests.post(
      f"{BASE_URL}/api/evaluations",
      headers=HEADERS,
      json={"name": "My eval", "type": "unit_test"},
  )

  if not response.ok:
      body = response.json()
      error = body.get("error", {})
      code = error.get("code")
      request_id = error.get("requestId")

      if code == "UNAUTHORIZED":
          raise RuntimeError("Check your EVALGATE_API_KEY environment variable.")
      elif code == "RATE_LIMITED":
          raise RuntimeError("Rate limit hit — back off and retry.")
      elif code == "VALIDATION_ERROR":
          raise ValueError(f"Invalid request: {error.get('message')}")
      else:
          raise RuntimeError(
              f"API error {code}: {error.get('message')} (requestId: {request_id})"
          )

  data = response.json()
  print("Created:", data["id"])
  ```
</CodeGroup>

<Tip>
  When retrying after a `429 RATE_LIMITED` response, read the `X-RateLimit-Reset` header to determine the exact time the window resets, rather than using a fixed sleep duration. The TypeScript SDK handles this automatically with exponential backoff.
</Tip>
