Search, Exports and Pagination

Filter, sort and page through any collection, export the results to a CSV or Excel file, and read list operations page by page.

Every collection you can query in bulk has a search operation (POST .../search) that takes the same request body, and most have an export that writes the same results to a file. A few collections also have a list operation (GET) that pages with a cursor. This page covers all three.

Before you start

You need a token. Search operations need only read access. See Environments and Authentication.

Search operations

Each search operation accepts filters and sorts only on a fixed list of fields. The list is in the operation's description on its reference page.

The request body

{
  "filter": {
    "all": [
      { "fact": "batchId", "operator": "equals", "value": "B-01JBX7R2M4Q8T6N3K5W9Y1Z0AC" },
      { "fact": "status", "operator": "equals", "value": ["FAILED", "RETURNED"] }
    ]
  },
  "sort": [{ "fieldName": "auditInfo.createdAt", "order": "desc" }],
  "page": 1,
  "pageSize": 100
}
FieldRequiredDescription
filterNoWhich records to return. Omit it to return every record.
sortNoSort keys, applied in order. Each is `{ "fieldName": "...", "order": "asc"
pageNoPage number, starting at 1. Default 1.
pageSizeNoRecords per page, from 10 to 1000. Default 100.

Any other top-level field is rejected with 400 VALIDATION_ERROR.

Filters

A filter is a group: all (every condition must match, AND) or any (at least one must match, OR). A group holds conditions, other groups, or both, nested as deep as you need.

{
  "filter": {
    "all": [
      { "fact": "taxJurisdiction", "operator": "equals", "value": "us" },
      {
        "any": [
          { "fact": "status", "operator": "equals", "value": "FAILED" },
          { "fact": "payee.payable", "operator": "equals", "value": false }
        ]
      }
    ]
  }
}

Rules:

  • One group per level: { "all": [...], "any": [...] } at the same level is rejected. Nest one inside the other instead.
  • A group cannot be empty.
  • Each condition has fact (the field), operator, and value (except blank and notBlank).
  • When the field holds a list (for example tags.key), a condition matches if any item in the list matches.

Operators

OperatorMatches when the fieldvalue
equalsequals the value. Send an array to match any of several values.Value or array of values
notEqualdoes not equal the value. Note the spelling: notEqual, not notEquals.Value
containscontains the text, ignoring case.Text
notContainsdoes not contain the text, ignoring case.Text
startsWithstarts with the text, ignoring case.Text
endsWithends with the text, ignoring case.Text
blankis missing or empty.None
notBlankhas a value.None
greaterThan, greaterThanOrEqualis greater than (or equal to) the value.Number or ISO 8601 date
lessThan, lessThanOrEqualis less than (or equal to) the value.Number or ISO 8601 date
inRangeis between the two values, both included.Two-item array [from, to]

Tips:

  • For equals, send the value exactly as the API returns it, for example the status COMPLETED, not completed.
  • Filter dates with ISO 8601 UTC strings: { "fact": "auditInfo.createdAt", "operator": "inRange", "value": ["2026-10-01T00:00:00.000Z", "2026-10-31T23:59:59.999Z"] }.
  • Filter amounts on wholeValue, the amount in minor units (cents for USD), where the operation offers it. See IDs, Amounts and Dates.

Sorting

Always send sort when you read more than one page. Without it, the order is not guaranteed, so records can move between pages. Add a field that is unique per record (for example id on payments and batches) as the last sort key to break ties, when the operation lets you sort on one.

The response

{
  "page": 1,
  "pageSize": 100,
  "count": 237,
  "data": [
    { "id": "P-01JBX7S9QK3M8T2N6R4W0Y5ZDE", "status": "FAILED" }
  ]
}
  • count: how many records match the filter, across all pages.
  • data: the records on this page. Each has the same shape as the single-object read for that collection.
  • There are more pages while page × pageSize < count.

Reading every page

page × pageSize cannot exceed 10,000. A request past that returns 400 VALIDATION_ERROR with the rule code INVALID_PAGE_TOO_HIGH. To read more than 10,000 records, split the work into date windows (for example one month of auditInfo.createdAt per pass) and page through each window.

The helper below pages through one search. Use sort fields that the operation accepts; auditInfo.createdAt and id work for payments and batches.

type SearchPage<T> = { page: number; pageSize: number; count: number; data: T[] };

async function searchAll<T>(token: string, path: string, filter: unknown): Promise<T[]> {
  const pageSize = 1000;
  const results: T[] = [];
  for (let page = 1; (page - 1) * pageSize < 10_000; page++) {
    const response = await fetch(`https://api-sandbox.paymentlabs.io${path}`, {
      method: "POST",
      headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
      body: JSON.stringify({
        filter,
        sort: [{ fieldName: "auditInfo.createdAt", order: "asc" }, { fieldName: "id", order: "asc" }],
        page,
        pageSize,
      }),
    });
    if (!response.ok) throw new Error(`Search failed with ${response.status}: ${await response.text()}`);
    const body = (await response.json()) as SearchPage<T>;
    results.push(...body.data);
    if (page * pageSize >= body.count) break;
  }
  return results;
}
curl -s -X POST https://api-sandbox.paymentlabs.io/payments/search \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "filter": { "all": [ { "fact": "referenceId", "operator": "equals", "value": "invoice-7781" } ] },
    "sort": [ { "fieldName": "auditInfo.createdAt", "order": "desc" } ],
    "pageSize": 10
  }'

Export results to a file

An export runs the same filter and sort as the matching search and writes every match to one CSV or Excel file. It takes no page or pageSize. Use it for reports and reconciliation instead of paging through a large result.

Query parameterDescription
fileTypecsv (default) or xlsx.
timezoneOffsetYour offset from UTC in minutes, as JavaScript's Date.getTimezoneOffset() returns it (300 for UTC−5). Dates in the file use that time zone. From -840 to 720; default 0.

Most exports return 200 with { "fileUrl": "..." }. The link expires 60 seconds after the response, so download the file straight away and call the export again for a new link.

Export Payments is the exception, because payment exports can be large. It returns 201 with an exportId and builds the file in the background. Poll Get Payment Export every few seconds until status is COMPLETED, then download fileUrl. FAILED carries an errorMessage; start a new export. An export can be read for 2 days.

curl -s -X POST "https://api-sandbox.paymentlabs.io/checkouts/search/export?fileType=csv&timezoneOffset=300" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "filter": { "all": [ { "fact": "status", "operator": "equals", "value": "PAID" } ] } }'

Export errors are the search errors below plus 400 INVALID_FILE_TYPE and 400 INVALID_TIMEZONE_OFFSET.

Ledger accounts have their own date-range exports; see Funding and Ledger Accounts.

Freshness

Search results can lag a few seconds behind the latest changes. Treat search as a way to find records, and read the single object (for example Get Payment) before you act on a status or a balance.

List operations with a cursor

Some collections are read with GET and a cursor instead of search:

OperationCursor parameterPage size parameter
List Batch PaymentsexclusiveStartId—
Get Batch ImportsexclusiveStartIdscanLimit
Get Import RowsexclusiveStartIdscanLimit
List Webhook LogsexclusiveStartIdlimit

They return:

{
  "items": [],
  "currentCount": 0,
  "totalCount": 12,
  "nextPage": "P-01JBX7S9QK3M8T2N6R4W0Y5ZDE"
}
  1. Call without exclusiveStartId.
  2. If the response has nextPage, call again with exclusiveStartId set to it.
  3. Stop when nextPage is absent.

A page can hold fewer items than you asked for, even zero, while more remain. Do not stop on a short page and do not use totalCount to decide when to stop; follow nextPage only.

Other list operations, such as List Payee Payments, return every item in one array and take no paging parameters.

Errors and edge cases

StatuscodeMeaningWhat to do
400VALIDATION_ERRORThe body is invalid. The rule code in details.filter, details.sort..., details.page or details.pageSize says why.Fix the body.
400INVALID_REQUESTThe search could not run.Retry once. If it fails again, simplify the filter and contact support with the request.
422INVALID_BODYThe body is not valid JSON.Send a JSON object.

Rule codes you may see inside details:

Rule codeMeaning
INVALID_FILTER_ONLY_ALL_OR_ANYfilter has a key other than all or any.
INVALID_FILTER_ALL_AND_ANYA group uses all and any at the same level.
INVALID_FILTER_ALL_CRITERIA, INVALID_FILTER_ANY_CRITERIAA group is empty.
INVALID_FIELD_FILTERAn item in a group is neither a condition nor a group.
INVALID_FIELD_NOT_ALLOWEDfact or fieldName is not a searchable field for this operation.
INVALID_OPERATOR_REQUIRED, INVALID_OPERATOR_OPTIONoperator is missing or is not one of the operators above.
INVALID_PAGE_TOO_HIGHpage × pageSize is above 10,000.
INVALID_FIELD_TOO_LOW, INVALID_FIELD_TOO_HIGHpage is below 1, or pageSize is outside 10 to 1000.

Next steps


What’s Next

Did this page help you?