Skip to content

API conventions

Pagination

List endpoints return results in pages, newest first, and are navigated with a cursor.

The list object#

Every list endpoint returns the same envelope.

List response
{
  "object": "list",
  "data": [
    {
      "id": "msg_2b1Xw9aQ3rT8yU0pL4kZ",
      "object": "message",
      "mode": "live",
      "channel_id": "ch_7Hq2mN5vB8cX1zL0pK3j",
      "channel_type": "whatsapp",
      "direction": "outbound",
      "to": "+971501234567",
      "from": "+971800123456",
      "type": "text",
      "content": {
        "text": {
          "body": "Your code is 482910"
        }
      },
      "status": "delivered",
      "error": null,
      "reference": "order-1042",
      "metadata": {
        "user_id": "u_17"
      },
      "billing": {
        "source": "package",
        "amount_micros": 0,
        "package_grant_id": "grant_5tGh2Kp9LmQ4xW7nB1cD",
        "refunded": false
      },
      "sender": null,
      "contact_id": null,
      "created_at": "2026-10-05T09:30:00.000Z",
      "updated_at": "2026-10-05T09:30:02.871Z",
      "sent_at": "2026-10-05T09:30:01.210Z",
      "delivered_at": "2026-10-05T09:30:02.871Z",
      "read_at": null,
      "failed_at": null
    }
  ],
  "has_more": true,
  "next_cursor": "msg_2b1Xw9aQ3rT8yU0pL4kZ"
}
FieldDescription
objectAlways list.
dataThe objects of this page, newest first.
has_moretrue if more objects exist after this page.
next_cursorThe ID of the last object in data. Pass it as starting_after to get the next page. null when has_more is false.

Parameters#

ParameterDefaultDescription
limit20Page size, from 1 to 100. A value outside the range fails with 400 parameter_invalid.
starting_afterNoneAn object ID. The page starts with the object that follows it. Use the next_cursor of the previous page.
curl "https://api.omnimessage.co/v1/messages?limit=50&starting_after=msg_2b1Xw9aQ3rT8yU0pL4kZ" \
  -H "Authorization: Bearer om_live_xxxxxxxxxxxxxxxxxxxxxxxx"

Iterate over all results#

Request a page, process data, and repeat with starting_after set to next_cursor until has_more is false. Keep the filters identical between pages.

async function* listMessages(filters = {}) {
  let startingAfter;
  do {
    const params = new URLSearchParams({ limit: '100', ...filters });
    if (startingAfter) params.set('starting_after', startingAfter);

    const response = await fetch(`https://api.omnimessage.co/v1/messages?${params}`, {
      headers: { Authorization: `Bearer ${process.env.OMNIMESSAGE_API_KEY}` },
    });
    const page = await response.json();
    if (!response.ok) throw new Error(`${page.error.code}: ${page.error.message}`);

    yield* page.data;
    startingAfter = page.has_more ? page.next_cursor : undefined;
  } while (startingAfter);
}

for await (const message of listMessages({ status: 'failed', created_after: '2026-10-01T00:00:00Z' })) {
  console.log(message.id, message.error.code);
}

Consistency#

  • Cursors are stable. Objects created while you paginate appear before your first page and do not shift later pages, so you never see an object twice or skip one.
  • To pick up new objects later, start again from the first page and stop when you reach an ID you have already processed, or filter messages with created_after.
  • A cursor is just an object ID and does not expire. An ID that does not exist fails with 400 parameter_invalid.

Paginated endpoints#

EndpointOperation
GET /v1/messagesList messages
GET /v1/channelsList channels
GET /v1/webhook_endpointsList webhook endpoints
GET /v1/contactsList contacts
GET /v1/contact_listsList contact lists
GET /v1/segmentsList segments
GET /v1/campaignsList campaigns
GET /v1/campaigns/{id}/recipientsList the recipients of a campaign
GET /v1/integration_sourcesList integration sources
GET /v1/automation_eventsList event receipts
GET /v1/eventsList events

GET /v1/messages/{id}/events and GET /v1/channels/{id}/templates use the same envelope but return everything in one page: has_more is always false.

    Loading