Skip to content

Pagination

Every list endpoint returns the same envelope:

json
{
  "object": "list",
  "has_more": true,
  "data": [ /* … */ ]
}
FieldMeaning
objectAlways "list".
has_moreWhether more records exist beyond this page.
dataThe records, newest first.

Parameters

ParameterDefaultNotes
limit20Between 1 and 100.
afterAn object's id. Returns the records after it (older).
beforeAn object's id. Returns the records before it (newer).

after and before are cursors, not offsets: you pass the id of a record, not a page number. The record you name is not included in the result.

Passing both after and before is an error (validation_error), as is a limit outside 1–100 or a cursor we don't recognise (invalid_parameter) — rather than quietly returning an empty page and letting you conclude you have no data.

Walking a list

Take the last id of a page and pass it as after:

bash
# first page
curl "https://api.sendgrail.com/v1/emails?limit=50" \
  -H "Authorization: Bearer $SENDGRAIL_API_KEY"

# next page — the id of the last record above
curl "https://api.sendgrail.com/v1/emails?limit=50&after=4ef9a417-02e9-4d39-ad75-9611e0fcc33c" \
  -H "Authorization: Bearer $SENDGRAIL_API_KEY"
js
let after = undefined;

do {
  const url = new URL('https://api.sendgrail.com/v1/emails');
  url.searchParams.set('limit', '50');
  if (after) url.searchParams.set('after', after);

  const res = await fetch(url, {
    headers: { Authorization: `Bearer ${process.env.SENDGRAIL_API_KEY}` },
  });
  const page = await res.json();

  for (const email of page.data) {
    // …
  }

  after = page.has_more ? page.data.at(-1).id : undefined;
} while (after);

Why cursors, not pages

A page number has to be counted from the start of the list every time you ask for it, which gets slower the deeper you go — and on a list that is still growing (emails, logs), the ground moves under you: a record arriving between two requests shifts everything down by one, so page 2 repeats a row page 1 already gave you.

A cursor says "carry on from this record". It costs the same whether you're on the first page or the thousandth, and nothing you've already seen can reappear.

Transactional email on your own domain.