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

# Error codes

> Understanding and handling Inbox API errors

## Overview

The Inbox API returns consistent error responses with actionable information. This guide covers all error codes, their causes, and recovery strategies.

## Error response format

All errors follow this structure:

```json theme={null}
{
  "error": {
    "code": "THREAD_NOT_FOUND",
    "message": "Thread with ID 'thread_invalid' not found",
    "status": 404
  }
}
```

| Field     | Description                                |
| --------- | ------------------------------------------ |
| `code`    | Programmatic identifier for the error type |
| `message` | Human-readable description                 |
| `status`  | HTTP status code                           |

## HTTP status codes

| Status | Meaning               | Common Causes                        |
| ------ | --------------------- | ------------------------------------ |
| `400`  | Bad Request           | Invalid parameters, malformed JSON   |
| `401`  | Unauthorized          | Missing or invalid API token         |
| `403`  | Forbidden             | Token lacks required permissions     |
| `404`  | Not Found             | Resource doesn't exist               |
| `409`  | Conflict              | Duplicate resource or state conflict |
| `422`  | Unprocessable Entity  | Valid JSON but invalid data          |
| `429`  | Too Many Requests     | Rate limit exceeded                  |
| `500`  | Internal Server Error | Server-side issue                    |

## Authentication errors

### UNAUTHORIZED

**Status:** 401

**When it occurs:**

* API token is missing from the request
* API token is invalid or malformed
* API token has expired or been revoked

**Example response:**

```json theme={null}
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid or missing authentication token",
    "status": 401
  }
}
```

**How to fix:**

1. Verify the `Authorization` header is present: `Authorization: Bearer YOUR_TOKEN`
2. Check for extra spaces or newlines in the token
3. Generate a new token from your Inbox dashboard

<CodeGroup>
  ```typescript auth-error.ts theme={null}
  try {
    const { data } = await client.get('/team');
  } catch (error) {
    if (axios.isAxiosError(error) && error.response?.status === 401) {
      console.error('Authentication failed. Check your API token.');
      // Prompt user to re-authenticate or check token
    }
  }
  ```

  ```bash cURL theme={null}
  # Verify your token format
  curl https://inboxapp.com/api/v1/team \
    -H "Authorization: Bearer $INBOX_API_TOKEN" \
    -v  # Verbose output to see headers
  ```
</CodeGroup>

### FORBIDDEN

**Status:** 403

**When it occurs:**

* Token is valid but lacks access to the resource
* Resource belongs to a different team
* Feature not available on your plan

**Example response:**

```json theme={null}
{
  "error": {
    "code": "FORBIDDEN",
    "message": "Insufficient permissions to access this resource",
    "status": 403
  }
}
```

**How to fix:**

1. Verify the resource belongs to your team
2. Check if your plan includes the requested feature
3. Contact support if you believe this is an error

***

## Resource errors

### THREAD\_NOT\_FOUND

**Status:** 404

**When it occurs:**

* Thread ID doesn't exist
* Thread was deleted
* Using platform ID instead of Inbox ID

**Example response:**

```json theme={null}
{
  "error": {
    "code": "THREAD_NOT_FOUND",
    "message": "Thread with ID 'thread_invalid' not found",
    "status": 404
  }
}
```

**How to fix:**

1. Verify you're using the Inbox ID (CUID2 format like `l44e15irdq4db30i77cgphhx`), not the platform ID
2. Use `/threads/lookup` to find threads by prospect platform ID
3. Check if the thread was deleted

```typescript theme={null}
// Wrong: Using platform ID
await client.get(`/threads/1566123362161725440`);

// Correct: Using Inbox ID
await client.get(`/threads/l44e15irdq4db30i77cgphhx`);

// Correct: Lookup by platform ID
await client.get('/threads/lookup', {
  params: {
    externalPlatformId: '1876543210987654321',
    accountLinkId: 'df6jbw4h36qm5d9iu2sgn7kx'
  }
});
```

### PROSPECT\_NOT\_FOUND

**Status:** 404

**When it occurs:**

* Prospect ID doesn't exist
* Platform ID doesn't match any known prospect
* Prospect was never in a conversation with your team

**Example response:**

```json theme={null}
{
  "error": {
    "code": "PROSPECT_NOT_FOUND",
    "message": "Prospect not found",
    "status": 404
  }
}
```

**How to fix:**

1. Verify the platform ID is correct (X user IDs are numeric strings)
2. Prospects are created when they first message you or when you start a thread
3. Use `/prospects/lookup` to check if a prospect exists

```typescript theme={null}
// Check if prospect exists before operations
const { data: prospect } = await client.get('/prospects/lookup', {
  params: { platformId: '1876543210987654321' }
});

if (!prospect) {
  console.log('Prospect not in system. Start a conversation first.');
}
```

### ACCOUNT\_LINK\_NOT\_FOUND

**Status:** 404

**When it occurs:**

* Account link ID doesn't exist
* Using platform ID instead of Inbox ID
* Account was disconnected

**How to fix:**

1. List account links to get valid IDs: `GET /account-links`
2. Use the Inbox ID (CUID2 format), not the X user ID

```typescript theme={null}
// Get valid account link IDs
const { data: accountLinks } = await client.get('/account-links');
const validIds = accountLinks.map(a => a.id);
console.log('Valid account link IDs:', validIds);
```

### TAG\_NOT\_FOUND

**Status:** 404

**When it occurs:**

* Tag ID doesn't exist
* Tag was deleted

**How to fix:**

1. List tags to get valid IDs: `GET /tags`
2. Create the tag if it doesn't exist

### STATUS\_NOT\_FOUND

**Status:** 404

**When it occurs:**

* Status ID doesn't exist
* Status was deleted

**How to fix:**

1. List statuses to get valid IDs: `GET /statuses`
2. Create the status if it doesn't exist

### MEMBER\_NOT\_FOUND

**Status:** 404

**When it occurs:**

* Member ID doesn't exist
* Member was removed from the team

**How to fix:**

1. List members to get valid IDs: `GET /members`

***

## Validation errors

### VALIDATION\_ERROR

**Status:** 400

**When it occurs:**

* Required field is missing
* Field value is wrong type
* Value exceeds limits

**Example response:**

```json theme={null}
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request body",
    "status": 400,
    "details": {
      "field": "content",
      "issue": "Message content is required"
    }
  }
}
```

**How to fix:**

1. Check required fields for the endpoint
2. Verify data types match the API spec
3. Ensure string lengths are within limits

```typescript theme={null}
// Validate before sending
function validateMessage(content: string) {
  if (!content?.trim()) {
    throw new Error('Message content is required');
  }
  if (content.length > 10000) {
    throw new Error('Message exceeds 10,000 character limit');
  }
}
```

### INVALID\_CURSOR

**Status:** 400

**When it occurs:**

* Cursor object is malformed
* Cursor is from a different query
* Cursor has expired

**How to fix:**

1. Use cursors exactly as returned from the API
2. Don't modify cursor values
3. Start over from page 1 if cursor errors persist

***

## State errors

### DUPLICATE\_THREAD

**Status:** 409

**When it occurs:**

* Thread already exists for this prospect-account pair

**How to fix:**
Use lookup before creating:

```typescript theme={null}
// Lookup first
const { data: existing } = await client.get('/threads/lookup', {
  params: { externalPlatformId, accountLinkId }
});

if (existing) {
  // Use existing thread
  return existing;
}

// Create only if none exists
const { data: created } = await client.post('/threads', {
  externalPlatformId,
  accountLinkId
});
```

### DUPLICATE\_TAG

**Status:** 409

**When it occurs:**

* Tag with same name already exists

**How to fix:**
Use a unique name or find the existing tag.

### DUPLICATE\_STATUS

**Status:** 409

**When it occurs:**

* Status with same name already exists

**How to fix:**
Use a unique name or find the existing status.

***

## Rate limiting

### RATE\_LIMITED

**Status:** 429

**When it occurs:**

* Too many requests in a short period

**Example response:**

```json theme={null}
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded. Try again in 60 seconds.",
    "status": 429
  }
}
```

**Response headers:**

```
Retry-After: 60
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1705312800
```

**How to fix:**

1. Check the `Retry-After` header for wait time
2. Implement exponential backoff
3. Reduce request frequency

See the [Rate Limits guide](/reference/rate-limits) for detailed limits and backoff strategies.

***

## Error handling pattern

A comprehensive error handler for all error types:

```typescript theme={null}
import axios, { AxiosError } from 'axios';

interface InboxError {
  code: string;
  message: string;
  status: number;
  details?: Record<string, unknown>;
}

async function handleInboxError(error: AxiosError<{ error: InboxError }>) {
  if (!axios.isAxiosError(error) || !error.response) {
    throw error;  // Not an API error
  }

  const { status, data, headers } = error.response;
  const errorInfo = data?.error;

  switch (status) {
    case 401:
      console.error('Authentication failed. Check your API token.');
      // Redirect to re-authenticate or refresh token
      break;

    case 403:
      console.error('Permission denied:', errorInfo?.message);
      break;

    case 404:
      console.error('Resource not found:', errorInfo?.code);
      // Handle based on error code
      if (errorInfo?.code === 'THREAD_NOT_FOUND') {
        // Maybe create the thread instead
      }
      break;

    case 409:
      console.error('Conflict:', errorInfo?.message);
      // Handle duplicates - usually fetch existing resource
      break;

    case 429:
      const retryAfter = parseInt(headers['retry-after'] || '60', 10);
      console.log(`Rate limited. Retry in ${retryAfter} seconds.`);
      await new Promise(r => setTimeout(r, retryAfter * 1000));
      // Retry the request
      break;

    case 500:
      console.error('Server error. Please try again later.');
      break;

    default:
      console.error('API error:', errorInfo?.message || error.message);
  }

  throw error;
}

// Usage
try {
  const { data } = await client.get(`/threads/${threadId}`);
} catch (error) {
  await handleInboxError(error as AxiosError);
}
```

## Best practices

<AccordionGroup>
  <Accordion title="Always check error codes, not just messages" icon="code">
    Error messages may change, but codes are stable:

    ```typescript theme={null}
    // Good: Check code
    if (error.response?.data?.error?.code === 'THREAD_NOT_FOUND') {
      // Handle missing thread
    }

    // Bad: Check message string
    if (error.message.includes('not found')) {
      // Fragile
    }
    ```
  </Accordion>

  <Accordion title="Validate before sending requests" icon="check">
    Catch validation errors locally before making API calls to improve user experience.
  </Accordion>

  <Accordion title="Log errors with context" icon="file-lines">
    Include request details for debugging:

    ```typescript theme={null}
    console.error('API Error:', {
      endpoint: '/threads',
      params: { threadId },
      error: error.response?.data
    });
    ```
  </Accordion>

  <Accordion title="Handle 404s gracefully" icon="ghost">
    Missing resources are often recoverable - create them or suggest alternatives.
  </Accordion>
</AccordionGroup>

## Related

<CardGroup cols={2}>
  <Card title="Rate limits" icon="gauge" href="/reference/rate-limits">
    Rate limiting details and backoff
  </Card>

  <Card title="Authentication" icon="key" href="/configuration/authentication">
    API token setup
  </Card>

  <Card title="Troubleshooting" icon="wrench" href="/resources/troubleshooting">
    Common issues and fixes
  </Card>

  <Card title="API reference" icon="code" href="/api-reference/introduction">
    Complete endpoint documentation
  </Card>
</CardGroup>
