Skip to main content
Every list endpoint returns one page at a time. You move forward with a cursor taken from the previous page. Endpoints that report on a period also take a date window.

The list envelope

A list response has two top-level keys: data, the items on this page, and pagination.
array
required
The items on this page. An empty array is a valid page.
string | null
required
The cursor for the next page. null on the last page.
boolean
required
true while another page exists. Stop when it is false.
A single resource comes back as { "data": { … } } with no pagination key.

limit and cursor

integer
default:50
Page size, from 1 to 200. The default is 50.GET /orgs/{orgId}/brands/{brandId}/mentions is the bulk-export endpoint and accepts up to 1000.A value outside the range answers 400 validation_error.
string
The pagination.next_cursor value from the previous page. Leave it out to get the first page.
Three rules keep a walk correct:
  • A cursor is opaque. Copy next_cursor into the next request exactly as you received it. Do not decode it, edit it or build one yourself. Pass it like any other query value, URL-encoded by your HTTP client.
  • Stop on has_more: false. next_cursor is null on that page.
  • Send limit on every request. The cursor does not remember it. Without limit, the next page falls back to the default size. You can change limit between pages.

A complete loop

Each sample yields every prompt in an organisation from GET /orgs/{orgId}/prompts. It waits out 429 and 503 responses using Retry-After, and gives up instead of sleeping when the wait is long. A spent monthly quota counts down to the end of the month, for example — see Rate limits and quotas.

What a cursor is bound to

A cursor is a position in one series. It remembers which series, so a walk cannot drift into a different one half-way through. A cursor that does not fit the request answers 400 invalid_cursor. The endpoint and the collection. A cursor belongs to the list that issued it and to the organisation, brand or prompt in that request’s path. A cursor from one organisation’s prompts does not work on another organisation’s prompts, or on any other list. On GET /orgs it belongs to the owner of your key. Two endpoints are stricter still:
  • A cursor from GET /orgs/{orgId}/prompts/{promptId}/results also belongs to the own brand the prompt was under. It stops working if the prompt is moved to another own brand.
  • GET /orgs/{orgId}/brands/{brandId}/scores serves two series, one with country and one without. They issue distinct cursors, so adding country to a walk that started without it is refused.
None of these cases carries details. The filters of the first page. The filters an endpoint takes — platform, country, brand_id, active, tag, is_own or status — travel inside the cursor. Continue with the cursor alone and they are restored, so page two is still filtered the way page one was. Repeating the same filter value is fine, so you can send your whole query string again with the cursor added. Naming a different value, or adding a filter the walk did not start with, answers 400 invalid_cursor with details.reason set to filter_changed. details.parameter names the filter. The date window of the first page. On an endpoint that takes from and to, the first page fixes the window and the cursor carries it. A walk that starts without from and to does not shift forward when it crosses midnight UTC. Naming a different from or to on a later page answers 400 invalid_cursor with details.reason set to window_changed.
To change a filter or the window, start again from the first page.

Reading results while a scan is running

Three endpoints read scan results:
  • GET /orgs/{orgId}/prompts/{promptId}/results
  • GET /orgs/{orgId}/brands/{brandId}/mentions
  • GET /orgs/{orgId}/brands/{brandId}/sources
A walk through any of them belongs to one scan generation. Every page serves only the results that were final when you asked for the first page. A result that becomes final while you page appears in no page of that walk, and in every page of the next one. You never get a page stitched together from two states of the data. Data model explains what “final” means. The price of that guarantee is that a walk can be refused part-way:
  • 400 invalid_cursor with details.reason set to stale. The brand’s data changed after the cursor was issued. A running scan is the usual cause, because every result it finalises counts as a change. Start again from the first page.
  • 409 scan_in_progress with Retry-After. Results were being finalised while a first page was read, so it could not be served from a single scan generation. Expect it while a scan of the brand runs: from the sources endpoint at any page size, and from the results and mentions endpoints on a first page that takes more than one read: a large page, or one that reaches the end of the data. Wait Retry-After seconds, then send the same request again.
In practice:
  1. Wait for the scan to settle. Poll the brand’s change token: last_scan_changed_at on GET /orgs/{orgId}/brands/{brandId}, or changed_at on each row of GET /orgs/{orgId}/brands/summary. Start a bulk walk once the value has stopped moving.
  2. Use smaller pages while a scan runs. A smaller first page is less likely to answer 409 scan_in_progress, though any first page can.
  3. Make the walk restartable. On stale, discard what you collected in this walk and begin again from the first page. Do not try to resume from the old cursor.
Export results in bulk has a complete backfill script that handles both responses.
Lists that do not read scan results never answer stale or scan_in_progress. That covers organisations, brands, prompts, scores and the webhook lists.

Date windows

Five endpoints report on a period and take a from and to window:
  • GET /orgs/{orgId}/prompts/{promptId}/results
  • GET /orgs/{orgId}/brands/{brandId}/mentions
  • GET /orgs/{orgId}/brands/{brandId}/scores
  • GET /orgs/{orgId}/brands/{brandId}/share-of-voice
  • GET /orgs/{orgId}/brands/{brandId}/sources
string
First day of the window, inclusive. The default is 30 days before to.
string
Last day of the window, inclusive. The default is today, in UTC.
Both are calendar dates in UTC, written YYYY-MM-DD. The window includes both days. to can be at most 366 days after from. On GET /orgs/{orgId}/brands/{brandId}/sources it can be at most 90 days after from, because that endpoint adds up every result in the window on each request.
A window the API cannot accept answers 400 validation_error. The reason is in details[].code: A value that is not written as YYYY-MM-DD also answers 400 validation_error. On a paginated endpoint the first page fixes the window for the whole walk, as described in What a cursor is bound to. See Errors for the full error format.