Skip to main content
The v2 API uses three pagination styles, each fitting the underlying data model. Knowing which style applies to which endpoint lets you build a uniform client that handles all three.
PatternQuery paramsUsed by
Offsettake, skipMost list endpoints (audit logs, reporting, creatives, test cohorts, …)
Custom offsetgroupOffset, groupLimit, productOffset, productsPerGroupDiscovery (POST /discovery/.../discover-products)
Cursorlimit, starting_afterStorefront billing endpoints proxied to Stripe

Offset pagination (take / skip)

The default for v2 list endpoints. take is the page size; skip is the offset from the start.

Request

GET /api/buyer/audit-logs?take=50&skip=100
Authorization: Bearer <SCOPE3_API_KEY>
ParamTypeDefaultNotes
takenumbervariesPage size. Most endpoints cap at 100 or 200
skipnumber0Records to skip

Response

The response includes a meta.pagination block alongside data:
{
  "data": {
    "logs": [/* ...50 items... */],
    "total": 1342
  },
  "error": null,
  "meta": {
    "pagination": {
      "skip": 100,
      "take": 50,
      "total": 1342,
      "hasMore": true
    }
  }
}
FieldTypeMeaning
skipnumberEchoed offset
takenumberEchoed page size
totalnumberTotal matching rows across all pages
hasMorebooleantrue if skip + returned < total

Example: paging through audit logs

async function* listAllAuditLogs() {
  const take = 100;
  let skip = 0;

  while (true) {
    const res = await fetch(
      `https://api.agentic.scope3.com/api/buyer/audit-logs?take=${take}&skip=${skip}`,
      { headers: { Authorization: `Bearer ${process.env.SCOPE3_API_KEY}` } },
    );
    const { data, meta } = await res.json();
    yield* data.logs;
    if (!meta.pagination.hasMore) return;
    skip += take;
  }
}

Custom offset (discovery)

Discovery returns nested results — product groups, each containing several products — so it exposes two independent offsets. This lets you page through groups without re-fetching products you’ve already seen, and vice versa.

Request

POST /api/buyer/discovery/{discoveryId}/discover-products
Content-Type: application/json

{
  "advertiserId": 123,
  "groupLimit": 5,
  "groupOffset": 0,
  "productsPerGroup": 10,
  "productOffset": 0
}
ParamTypeMeaning
groupLimitnumberHow many product groups to return
groupOffsetnumberSkip this many product groups
productsPerGroupnumberProducts to include within each returned group
productOffsetnumberSkip this many products within each group

Response

{
  "data": {
    "discoveryId": "disc_01HX…",
    "productGroups": [/* ... */],
    "totalGroups": 23,
    "hasMoreGroups": true,
    "summary": { "totalProducts": 412 }
  },
  "error": null
}
hasMoreGroups tells you whether to advance groupOffset for another page of groups. summary.totalProducts is the full denormalized total across all groups.

Cursor pagination (starting_after)

Used by Stripe-Connect billing endpoints, which proxy directly to Stripe and inherit Stripe’s cursor convention.

Endpoints

  • GET /storefront/billing/transactions
  • GET /storefront/billing/payouts

Request

GET /api/storefront/billing/transactions?limit=25&starting_after=txn_1Nq…
ParamTypeDefaultMeaning
limitnumber25Page size, max 100
starting_afterstringObject ID returned in a previous page’s meta.cursor

Response

{
  "data": [/* ...transactions... */],
  "meta": {
    "count": 25,
    "limit": 25,
    "hasMore": true,
    "cursor": "txn_1NqAbc123"
  }
}
meta.cursor is the value to pass as starting_after on the next request. When hasMore is false, you’ve reached the end.
async function* listAllTransactions() {
  let cursor: string | undefined;

  while (true) {
    const url = new URL(
      "https://api.agentic.scope3.com/api/storefront/billing/transactions",
    );
    url.searchParams.set("limit", "100");
    if (cursor) url.searchParams.set("starting_after", cursor);

    const res = await fetch(url, {
      headers: { Authorization: `Bearer ${process.env.SCOPE3_API_KEY}` },
    });
    const body = await res.json();
    yield* body.data;

    if (!body.meta.hasMore) return;
    cursor = body.meta.cursor;
  }
}

Per-endpoint quick reference

EndpointStylePage params
GET /buyer/audit-logsOffsettake, skip
GET /reporting/metricsOffsettake, skip
GET /creativesOffsettake, skip
GET /test-cohortsOffsettake, skip
GET /human-feedbackOffsettake, skip
GET /measurement-engine/*Offsettake, skip
GET /advertiser-accountsOffsettake, skip
POST /discovery/:id/discover-productsCustom offsetgroupLimit, groupOffset, productsPerGroup, productOffset
GET /storefront/billing/transactionsCursorlimit, starting_after
GET /storefront/billing/payoutsCursorlimit, starting_after
When in doubt, inspect the response. Offset endpoints expose meta.pagination.hasMore; cursor endpoints expose meta.hasMore plus meta.cursor. Discovery exposes hasMoreGroups directly on data.
Don’t assume total is cheap to compute. For high-cardinality endpoints (audit logs, reporting metrics) prefer to consume pages with hasMore rather than computing Math.ceil(total / take) and looping — the count may be an estimate.