> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.tryenvoy.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.tryenvoy.ai/_mcp/server.

# Error Handling

## Error envelope

All errors return a consistent JSON structure:

```json
{
  "error": {
    "code": "not_found",
    "message": "Load not found",
    "param": "id"
  }
}
```

| Field     | Type           | Description                                                      |
| --------- | -------------- | ---------------------------------------------------------------- |
| `code`    | string         | Machine-readable error code. Use this for programmatic handling. |
| `message` | string         | Human-readable description of the error.                         |
| `param`   | string or null | The request parameter that caused the error, if applicable.      |

## Error codes

Handle errors by switching on the `error.code` field, not the HTTP status code.

| Code                       | HTTP Status | Description                                                               |
| -------------------------- | ----------- | ------------------------------------------------------------------------- |
| `validation_error`         | 400, 422    | Invalid request parameters or body. Check `param` for the specific field. |
| `not_authenticated`        | 401         | Missing or invalid credentials.                                           |
| `insufficient_permissions` | 403         | Valid credentials but not authorized for this resource or action.         |
| `not_found`                | 404         | The requested resource does not exist.                                    |
| `already_exists`           | 409         | A resource with the same identifying fields already exists.               |
| `rate_limit_exceeded`      | 429         | Too many requests. See the `Retry-After` header.                          |
| `internal_error`           | 500         | An unexpected server error. Contact support if it persists.               |

## Example error responses

**Validation error (422):**

```json
{
  "error": {
    "code": "validation_error",
    "message": "field required",
    "param": "stops[0].address.city"
  }
}
```

**Authentication error (401):**

```json
{
  "error": {
    "code": "not_authenticated",
    "message": "Missing or invalid authentication credentials.",
    "param": null
  }
}
```

**Rate limit error (429):**

```json
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Retry after 30 seconds.",
    "param": null
  }
}
```

## Handling errors in code

Switch on `error.code` rather than HTTP status for precise handling.

### Python

```python
import time
import requests

response = requests.post(
    "https://tryenvoy.ai/api/v1/loads/load_.../offers",
    headers={"X-API-Key": "<your-api-key>"},
    json={"carrier_id": "car_...", "amount": 2500.00},
)

if response.status_code >= 400:
    error = response.json()["error"]

    match error["code"]:
        case "not_found":
            print(f"Resource not found: {error['message']}")
        case "already_exists":
            print("This resource already exists.")
        case "validation_error":
            print(f"Invalid field {error['param']}: {error['message']}")
        case "not_authenticated":
            print("Check your API key -authentication failed.")
        case "rate_limit_exceeded":
            retry_after = int(response.headers.get("Retry-After", 60))
            time.sleep(retry_after)
        case _:
            print(f"Unexpected error: {error['code']} -{error['message']}")
```

### TypeScript

```typescript
const response = await fetch("https://tryenvoy.ai/api/v1/loads/load_.../offers", {
  method: "POST",
  headers: {
    "X-API-Key": "<your-api-key>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ carrier_id: "car_...", amount: 2500.0 }),
});

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

  switch (error.code) {
    case "not_found":
      console.error(`Resource not found: ${error.message}`);
      break;
    case "already_exists":
      console.error("This resource already exists.");
      break;
    case "validation_error":
      console.error(`Invalid field ${error.param}: ${error.message}`);
      break;
    case "not_authenticated":
      console.error("Check your API key -authentication failed.");
      break;
    case "rate_limit_exceeded":
      const retryAfter = parseInt(response.headers.get("Retry-After") ?? "60", 10);
      await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
      break;
    default:
      console.error(`Unexpected error: ${error.code} -${error.message}`);
  }
}
```

## Validation errors

For `422` responses, the `param` field uses dot notation for nested fields (e.g., `stops[0].address.city`). Validate your payload structure against the [API Reference](/api-reference) before sending requests.