Pagination
Lists takelimit (1–100, default 20) and an opaque cursor from the previous page:
{ items, total, limit, offset, nextCursor, hasMore }. Iterate until hasMore is false. (offset exists for backwards compatibility only — prefer cursors.)
Sorting is sortBy (a per-endpoint enum) plus sortDir (asc | desc), stable with an ID tiebreak.
The filter grammar
- Different params AND together; repeating one param ORs its values.
?status=QUEUED&status=ISSUINGmatches either status;?status=ISSUED&projectId=Xrequires both. - Time windows are
…Since/…Untilpairs (ISO 8601 timestamps). Calendar dates areYYYY-MM-DDstrings. - Key-value filters use the repeatable
variable=key:valuemicro-format, split on the first colon (values may contain colons). Repeat the same key for any-of; different keys must all match. At most 5 per request. Matching is exact after whitespace trimming. - No hidden couplings — a param that only qualifies another is named after it
(
redeemedContextqualifiesredeemed) and is rejected with400 VALIDATION_ERRORwhen sent alone. - Aggregates live on
/statsendpoints — never count by requestinglimit=1lists. - Unknown params are rejected with a 400 on strict endpoints — a typo can’t silently widen your result set.
Filtering passes
GET /v1/passes supports:
| Param | Meaning |
|---|---|
status (repeatable) | Any-of over pass statuses |
externalPassId | Exact match on your correlation key |
search | Substring across label, external ID, status |
projectId | Project UUID, or the literal null for uncategorised passes |
templateId | Passes issued from any version of the template |
variable (repeatable, ≤5) | key:value stored-variable filter |
validOn | YYYY-MM-DD — passes valid for scanning that day |
redeemed | Boolean — has (or hasn’t) an unreversed accepted scan |
redeemedContext | Scopes redeemed to one scan context key; requires redeemed |
Filtering scans
GET /v1/scans supports projectId / passId / templateId, repeatable result (accepted | duplicate | unknown | ambiguous | denied | reversal), the shorthand resultGroup=denied (everything except accepted and reversal), scanContextKey, and the scannedSince / scannedUntil window.
Scan rows carry a machine-readable reasonCode (valid, already_scanned, out_of_window, expired, revoked, quota_exhausted, …) detailing the result.
Stats instead of counting
GET /v1/scans/stats answers “how many” in one call: filter by projectId, templateId, scanContextKey (or a scanContextFrom/scanContextUntil range), and scannedSince/scannedUntil; optionally groupBy=project. Returns totalPasses, redeemed, open, totalScans, totalDenied, and lastScanAt.