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

# Errors

RogerIQError with stable codes, status, request ID, and validation issues.

# Errors

All non-2xx responses throw `RogerIQError`. Branch on `code` (machine
readable), not `message` (human readable).

```ts
import { RogerIQ, RogerIQError } from "@rogeriq/sdk";

try {
  await roger.conversations.get("con_missing");
} catch (e) {
  if (e instanceof RogerIQError) {
    if (e.code === "CONVERSATION_NOT_FOUND") {
      // handle
    }
    console.error(e.code, e.status, e.requestId, e.message);
  }
  throw e;
}
```

## Fields

| Field | Type | Description |
|-------|------|-------------|
| `code` | string | Stable enum, e.g. `CONVERSATION_NOT_FOUND` |
| `status` | number | HTTP status code (0 if network error) |
| `message` | string | Human-readable summary |
| `requestId` | string \| undefined | Echoes the `X-Request-Id` header |
| `issues` | `{ path, message }[]` \| undefined | Field-level validation errors (400 only) |
| `retryAfter` | number \| undefined | Seconds to wait (429 only) |

## Common codes

| Code | Status | Meaning |
|------|-------:|---------|
| `UNAUTHORIZED` | 401 | Missing / invalid API key |
| `FORBIDDEN` | 403 | Authenticated but action not allowed |
| `INSUFFICIENT_SCOPE` | 403 | API key has the wrong scope |
| `API_KEY_ORG_MISMATCH` | 403 | API key belongs to a different org than the requested resource |
| `API_KEY_PROJECT_MISMATCH` | 403 | API key's org doesn't own the requested project |
| `CONVERSATION_NOT_FOUND` | 404 | Bad conversation id |
| `PROJECT_NOT_FOUND` | 404 | Bad project id |
| `CONTACT_NOT_FOUND` | 404 | Bad contact id |
| `RESOURCE_NOT_FOUND` | 404 | Generic 404 |
| `CONFLICT` | 409 | Slug collision, idempotency conflict, etc. |
| `SLUG_CONFLICT` | 409 | Specifically for project / form slugs |
| `BAD_REQUEST` | 400 | Generic bad input |
| `BAD_JSON` | 400 | Body wasn't valid JSON |
| `NO_UPDATES` | 400 | PATCH with empty body |
| `RATE_LIMITED` | 429 | Slow down (the SDK auto-retries if `retry_after` ≤ 30s) |
| `INTERNAL_ERROR` | 500 | Server bug. Include `requestId` when reporting. |

## Validation errors

POST/PATCH bodies that fail Zod validation include a structured `issues`
array:

```json
{
  "error": "Invalid body",
  "code": "BAD_REQUEST",
  "request_id": "req_abc",
  "issues": [
    { "path": "email", "message": "Invalid email" },
    { "path": "metadata.plan", "message": "Expected string" }
  ]
}
```

```ts
try {
  await roger.contacts.upsert({ email: "not-an-email" });
} catch (e) {
  if (e instanceof RogerIQError && e.issues) {
    for (const issue of e.issues) {
      console.error(`${issue.path}: ${issue.message}`);
    }
  }
}
```

## Rate limiting

The SDK auto-retries `429` once when `retry_after` ≤ 30 seconds. For
longer suggested delays the SDK throws so you can decide.

Watch live headroom via the `onResponse` callback:

```ts
const roger = new RogerIQ({
  apiKey: process.env.RIQ_API_KEY!,
  projectId: process.env.RIQ_PROJECT_ID!,
  onResponse: (meta) => {
    if (meta.rateLimit && meta.rateLimit.remaining < 10) {
      console.warn("approaching limit:", meta.rateLimit);
    }
  },
});
```

See [API rate limits](/api/rate-limits) for per-tier limits and the
`X-RateLimit-*` header reference.