Pagination

Two mechanisms are available. Both need an explicit order to be stable - without one the database may return rows in a different sequence between pages, silently skipping or duplicating records.

Limit and offset

curl "https://taa.data.tidio.com/ticket_details?select=ticket_id,subject,created_at&order=_unique_id.asc&limit=1000&offset=0" ...

Advance offset by limit for each page.

Range headers

The equivalent using HTTP ranges. Ranges are inclusive, so 0-999 is the first 1000 rows:

curl -i "https://taa.data.tidio.com/ticket_details?select=ticket_id,subject&order=_unique_id.asc" \
  -H "X-Tidio-Openapi-Client-Id: $TIDIO_CLIENT_ID" \
  -H "X-Tidio-Openapi-Client-Secret: $TIDIO_CLIENT_SECRET" \
  -H "Range-Unit: items" \
  -H "Range: 0-999"

Counting rows

Send a Prefer header to have the total reported in Content-Range. The response echoes back which behaviour was actually applied in a Preference-Applied header:

PreferBehaviourCost
(omitted)Content-Range: 0-9/* - no totalfree
count=exactContent-Range: 0-9/112 - precise totalscans the whole table
count=plannedplanner estimatecheap
count=estimatedexact when small, planned when largecheap
curl -i "https://taa.data.tidio.com/agent_details?select=_unique_id&limit=1" \
  -H "X-Tidio-Openapi-Client-Id: $TIDIO_CLIENT_ID" \
  -H "X-Tidio-Openapi-Client-Secret: $TIDIO_CLIENT_SECRET" \
  -H "Prefer: count=exact"

# Content-Range: 0-0/112
# Preference-Applied: count=exact

On the large tables, use count=planned - count=exact will time out.

Keyset pagination for full extracts

offset gets slower as it grows, because the database still walks every skipped row. For a complete extract, filter on the last id you saw instead. Page 500 then costs the same as page 1:

# First page
curl "https://taa.data.tidio.com/ticket_details?select=_unique_id,ticket_id,subject&order=_unique_id.asc&limit=1000" ...

# Subsequent pages - feed the last _unique_id back in
curl "https://taa.data.tidio.com/ticket_details?select=_unique_id,ticket_id,subject&_unique_id=gt.15664967821407535453&order=_unique_id.asc&limit=1000" ...

Stop when a page returns fewer rows than the limit.

Choosing a page size

1000 rows is a good default. On the widest and largest tables - live_chat_message_details, ticket_message_details, visitor_details, ticket_details, flow_statistics_daily - use 500 to stay clear of the 25s timeout.

Paging past the end

Behaviour differs depending on whether you asked for a count:

RequestResult
offset past the end, no count200 with [] and Content-Range: */*
offset past the end + Prefer: count=exact500 with PGRST103
Range past the end200 with [] and Content-Range: */*
{"detail": {"code": "PGRST103", "details": "An offset of 999 was requested, but there are only 112 rows."}}

If you page with a count, read the total from Content-Range first and stop before the end.


Did this page help you?