Skip to main content

Overview

The Inbox API uses cursor-based pagination for all list endpoints. This approach provides consistent results even when data changes between requests.

Response Structure

Paginated endpoints return items and an optional nextCursor:
{
  "threads": [
    { "id": "thread_abc123", "..." },
    { "id": "thread_def456", "..." }
  ],
  "nextCursor": {
    "id": "thread_def456",
    "timestamp": "2025-01-15T10:30:00.000Z"
  }
}
FieldDescription
itemsArray of results (key varies: threads, messages, tags, etc.)
nextCursorObject containing id and timestamp. null when no more pages.

Basic Usage

import axios from 'axios';

const client = axios.create({
  baseURL: 'https://inboxapp.com/api/v1',
  headers: {
    'Authorization': `Bearer ${process.env.INBOX_API_TOKEN}`,
    'Content-Type': 'application/json'
  }
});

async function getAllThreads() {
  const allThreads = [];
  let cursor = undefined;

  do {
    const { data } = await client.get('/threads', {
      params: {
        limit: 50,
        ...(cursor && {
          'cursor[id]': cursor.id,
          'cursor[timestamp]': cursor.timestamp
        })
      }
    });

    allThreads.push(...data.threads);
    cursor = data.nextCursor;

  } while (cursor);

  return allThreads;
}

const threads = await getAllThreads();
console.log(`Fetched ${threads.length} threads`);

Cursor Encoding

Cursors are objects that must be encoded in query parameters using bracket notation. Cursor object:
{
  "id": "thread_abc123",
  "timestamp": "2025-01-15T10:30:00.000Z"
}
Encoded in URL:
?cursor[id]=thread_abc123&cursor[timestamp]=2025-01-15T10:30:00.000Z
See the Query Parameters guide for complete encoding rules.

Page Size Limits

EndpointDefault LimitMaximum Limit
/threads50100
/threads/{id}/messages50100
/tags50100
/statuses50100
/members50100
Request more items per page to reduce API calls:
const { data } = await client.get('/threads', {
  params: { limit: 100 }  // Maximum
});

Streaming Pattern

For real-time processing without loading everything into memory:
async function* streamThreads() {
  let cursor = undefined;

  do {
    const { data } = await client.get('/threads', {
      params: {
        limit: 50,
        ...(cursor && {
          'cursor[id]': cursor.id,
          'cursor[timestamp]': cursor.timestamp
        })
      }
    });

    for (const thread of data.threads) {
      yield thread;
    }

    cursor = data.nextCursor;
  } while (cursor);
}

// Process threads one at a time
for await (const thread of streamThreads()) {
  await processThread(thread);
}

Pagination with Filters

Filters apply before pagination. The cursor tracks position within filtered results:
async function getFilteredThreads(tagId: string) {
  const allThreads = [];
  let cursor = undefined;

  do {
    const { data } = await client.get('/threads', {
      params: {
        'filter[tagIds][0]': tagId,
        limit: 50,
        ...(cursor && {
          'cursor[id]': cursor.id,
          'cursor[timestamp]': cursor.timestamp
        })
      }
    });

    allThreads.push(...data.threads);
    cursor = data.nextCursor;
  } while (cursor);

  return allThreads;
}

Error Handling

Handle pagination errors gracefully:
async function getAllThreadsWithRetry() {
  const allThreads = [];
  let cursor = undefined;
  let retries = 0;
  const maxRetries = 3;

  while (true) {
    try {
      const { data } = await client.get('/threads', {
        params: {
          limit: 50,
          ...(cursor && {
            'cursor[id]': cursor.id,
            'cursor[timestamp]': cursor.timestamp
          })
        }
      });

      allThreads.push(...data.threads);
      cursor = data.nextCursor;
      retries = 0;  // Reset on success

      if (!cursor) break;

    } catch (error) {
      if (axios.isAxiosError(error) && error.response?.status === 429) {
        // Rate limited - wait and retry
        const retryAfter = error.response.headers['retry-after'] || 60;
        await new Promise(r => setTimeout(r, retryAfter * 1000));
        continue;
      }

      if (retries < maxRetries) {
        retries++;
        await new Promise(r => setTimeout(r, 1000 * retries));
        continue;
      }

      throw error;
    }
  }

  return allThreads;
}

Best Practices

Reduce API calls by requesting the maximum 100 items per page when fetching all data.
Use the generator pattern to process items as they arrive instead of loading everything into memory.
Always include rate limit handling in pagination code. Bulk fetches can hit limits quickly.
Treat cursor objects as opaque. Don’t modify or construct them manually.