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

# Error Codes

> API error response format, HTTP status codes, machine-readable error codes, and troubleshooting guidance.

The PlaneConnection API returns consistent JSON error responses for all error types. Every error response includes a human-readable message, a machine-readable code, and a request ID for tracing.

## Error Response Format

```json theme={}
{
  "error": "Human-readable error message",
  "code": "MACHINE_READABLE_CODE",
  "details": {},
  "requestId": "req_a1b2c3d4"
}
```

| Field       | Type   | Always Present | Description                                   |
| ----------- | ------ | :------------: | --------------------------------------------- |
| `error`     | string |       Yes      | Human-readable error description              |
| `code`      | string |       Yes      | Machine-readable error code (see table below) |
| `details`   | object |       No       | Additional context (validation errors, etc.)  |
| `requestId` | string |       Yes      | Unique request ID for support/debugging       |

<Info>
  Always include the `requestId` when contacting support about an API error. It enables fast lookup
  in the request logs.
</Info>

## HTTP Status Codes

| Status | Code                    | Description                                                       |
| :----: | ----------------------- | ----------------------------------------------------------------- |
|  `400` | `BAD_REQUEST`           | Invalid request body, missing required fields, or malformed JSON  |
|  `401` | `UNAUTHORIZED`          | Missing or invalid authentication credentials                     |
|  `403` | `FORBIDDEN`             | Authenticated but insufficient permissions for this action        |
|  `404` | `NOT_FOUND`             | Resource does not exist or is not accessible in your workspace    |
|  `405` | `METHOD_NOT_ALLOWED`    | HTTP method not supported for this endpoint                       |
|  `409` | `CONFLICT`              | Resource conflict (e.g., duplicate entry)                         |
|  `422` | `UNPROCESSABLE_ENTITY`  | Request is syntactically valid but semantically incorrect         |
|  `429` | `TOO_MANY_REQUESTS`     | Rate limit exceeded -- wait and retry                             |
|  `500` | `INTERNAL_SERVER_ERROR` | Unexpected server error -- contact support if persistent          |
|  `502` | `BAD_GATEWAY`           | Upstream service unavailable                                      |
|  `503` | `SERVICE_UNAVAILABLE`   | Service temporarily unavailable -- retry with exponential backoff |
|  `504` | `GATEWAY_TIMEOUT`       | Upstream service timeout                                          |

## Validation Errors

When a request fails validation (status `400`), the `error` field contains the validation details as a JSON string. The structure follows Zod's `flatten()` format:

```json theme={}
{
  "error": "{\"fieldErrors\":{\"severity\":[\"Expected number, received string\"],\"likelihood\":[\"Required\"]},\"formErrors\":[]}",
  "code": "BAD_REQUEST",
  "requestId": "req_a1b2c3d4"
}
```

Parse the `error` field to extract per-field validation messages:

```javascript theme={}
const response = await fetch("/api/v1/safety/risk-assessments", {
  method: "POST",
  body: JSON.stringify(data),
});

if (!response.ok) {
  const result = await response.json();
  if (result.code === "BAD_REQUEST") {
    try {
      const validation = JSON.parse(result.error);
      // validation.fieldErrors contains per-field errors
      // validation.formErrors contains form-level errors
    } catch {
      // Non-validation error
    }
  }
}
```

## Common Error Scenarios

<Tabs>
  <Tab title="Authentication">
    **401 -- Missing token**

    ```json theme={}
    {
      "error": "Authentication required",
      "code": "UNAUTHORIZED",
      "requestId": "req_a1b2c3d4"
    }
    ```

    **Fix:** Include a valid `Authorization: Bearer <token>` header or `X-API-Key` header.

    **401 -- Expired token**

    ```json theme={}
    {
      "error": "Token expired",
      "code": "UNAUTHORIZED",
      "requestId": "req_a1b2c3d4"
    }
    ```

    **Fix:** Refresh your session token and retry.
  </Tab>

  <Tab title="Permissions">
    **403 -- Insufficient role**

    ```json theme={}
    {
      "error": "Permission denied",
      "code": "FORBIDDEN",
      "requestId": "req_a1b2c3d4"
    }
    ```

    **Fix:** The endpoint requires a higher role level. See [Authentication](/en/reference/api/authentication) for the role hierarchy.

    **403 -- Admin required**

    ```json theme={}
    {
      "error": "Admin access required",
      "code": "FORBIDDEN",
      "requestId": "req_a1b2c3d4"
    }
    ```

    **Fix:** Only users with the `admin` role or higher can perform this action.
  </Tab>

  <Tab title="Not Found">
    **404 -- Resource not found**

    ```json theme={}
    {
      "error": "Not found",
      "code": "NOT_FOUND",
      "requestId": "req_a1b2c3d4"
    }
    ```

    **Possible causes:**

    * The resource ID does not exist
    * The resource belongs to a different workspace
    * The resource was deleted

    **404 -- Route not found**

    ```json theme={}
    {
      "error": "Not found: GET /api/v1/nonexistent",
      "code": "NOT_FOUND",
      "requestId": "req_a1b2c3d4"
    }
    ```

    **Fix:** Verify the endpoint path. All versioned endpoints start with `/api/v1/`.
  </Tab>

  <Tab title="Rate Limiting">
    **429 -- Too many requests**

    ```json theme={}
    {
      "error": "Too many requests",
      "code": "TOO_MANY_REQUESTS",
      "requestId": "req_a1b2c3d4"
    }
    ```

    **Fix:** Wait for the rate limit window to reset, then retry. Use exponential backoff for automated clients.
  </Tab>
</Tabs>

## Error Handling Best Practices

<Steps>
  ### Check the HTTP status code

  Use the status code to determine the category of error (4xx = client error, 5xx = server error).

  ### Parse the error response

  Extract the `code` field for programmatic error handling. Use `error` for user-facing messages.

  ### Handle validation errors

  For `400` responses, parse the `error` field as JSON to get per-field validation details.

  ### Implement retry logic

  For `429` (rate limited) and `5xx` (server errors), implement exponential backoff:

  ```javascript theme={}
  async function fetchWithRetry(url, options, maxRetries = 3) {
    for (let attempt = 0; attempt <= maxRetries; attempt++) {
      const response = await fetch(url, options);

      if (response.ok) return response;

      if (response.status === 429 || response.status >= 500) {
        if (attempt < maxRetries) {
          const delay = Math.pow(2, attempt) * 1000;
          await new Promise((r) => setTimeout(r, delay));
          continue;
        }
      }

      throw new Error(`API error: ${response.status}`);
    }
  }
  ```

  ### Log the request ID

  Always log the `requestId` from error responses. Include it when contacting support.
</Steps>

## Production vs Development

In production, the API returns generic error messages for `500` errors to avoid leaking internal details:

```json theme={}
{ "error": "Internal server error", "code": "INTERNAL_SERVER_ERROR" }
```

In development and staging environments, the actual error message is returned for easier debugging.

## Error Tracking

Server errors (5xx) are automatically tracked and reported to the platform's error monitoring system. Error metrics are collected for operational dashboards.
