Skip to main content

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:
{
  "error": {
    "code": "THREAD_NOT_FOUND",
    "message": "Thread with ID 'thread_invalid' not found",
    "status": 404
  }
}
FieldDescription
codeProgrammatic identifier for the error type
messageHuman-readable description
statusHTTP status code

HTTP Status Codes

StatusMeaningCommon Causes
400Bad RequestInvalid parameters, malformed JSON
401UnauthorizedMissing or invalid API token
403ForbiddenToken lacks required permissions
404Not FoundResource doesn’t exist
409ConflictDuplicate resource or state conflict
422Unprocessable EntityValid JSON but invalid data
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer-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:
{
  "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
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
  }
}

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:
{
  "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:
{
  "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 (thread_xxx), not the platform ID
  2. Use /threads/lookup to find threads by prospect platform ID
  3. Check if the thread was deleted
// Wrong: Using platform ID
await client.get(`/threads/12345-67890`);

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

// Correct: Lookup by platform ID
await client.get('/threads/lookup', {
  params: {
    prospectPlatformId: '987654321',
    accountLinkId: 'acc_abc123'
  }
});

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:
{
  "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
// Check if prospect exists before operations
const { data } = await client.get('/prospects/lookup', {
  params: { platformId: '987654321' }
});

if (!data.prospect) {
  console.log('Prospect not in system. Start a conversation first.');
}
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 (acc_xxx), not the X user ID
// Get valid account link IDs
const { data } = await client.get('/account-links');
const validIds = data.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:
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request body",
    "status": 400,
    "details": {
      "field": "text",
      "issue": "Message text 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
// Validate before sending
function validateMessage(text: string) {
  if (!text?.trim()) {
    throw new Error('Message text is required');
  }
  if (text.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:
// Lookup first
const { data: lookup } = await client.get('/threads/lookup', {
  params: { prospectPlatformId, accountLinkId }
});

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

// Create only if none exists
const { data } = await client.post('/threads', {
  prospectPlatformId,
  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:
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded. Try again in 60 seconds.",
    "status": 429
  }
}
Response headers:
Retry-After: 60
X-RateLimit-Limit: 100
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 for detailed limits and backoff strategies.

Error Handling Pattern

A comprehensive error handler for all error types:
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

Error messages may change, but codes are stable:
// 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
}
Catch validation errors locally before making API calls to improve user experience.
Include request details for debugging:
console.error('API Error:', {
  endpoint: '/threads',
  params: { threadId },
  error: error.response?.data
});
Missing resources are often recoverable - create them or suggest alternatives.