How your backend uses Ta'ab as a pass and card provider: find out whose card or QR this is,
put a pass in their wallet under your own reference, and redeem it when they tap their card or
show the Ta'ab app.
This guide covers what holds across the whole API. Each endpoint's fields, examples and
errors are in the reference below it on /developers, which is generated from
the code, and in the OpenAPI document at /api/partner/v1/openapi.json.
Ta'ab answers two questions only: who holds this credential and what is the state of
this pass. Whether the pass's trip is today, on this bus, in this seat is yours to decide.
Ta'ab never sees it.
You can also accept passes you did not issue. If your institution is on the acceptance list
of another institution's product, such as a T-Pass that several bus companies honour, its
holders can use it with you. See "Passes you accept but did not issue".
No money moves through this API. Nothing here debits a wallet.
Getting access
Start in the sandbox, https://sandbox.taab.mohbel.com. It runs the same code against its
own database, and nothing in it is real money or a real pass.
- Sign in and sign up your organisation at
/institutions/request. In the sandbox this is
approved at once, and you are its owner.
- Under API clients in the admin portal, create a client. Its id is made for you; its
secret is shown once, and only its hash is stored. Rotating it replaces the old one at
once; disabling it stops it at once.
- Under Test cards, your organisation has five test accounts. Each has a QR code, and
three have a card, with its UID and printed number. Issue them passes through this API
and redeem them by their
externalRef.
You can also try Ta'ab without code: add a product to your catalog, issue it to a test
account in the admin portal, and redeem it with the staff scanner at /scan. The scanner
picks the holder's pass by type, so it never uses a pass you issued with an externalRef:
a ticket means a seat on a date, which Ta'ab doesn't check.
To go live, apply at https://taab.mohbel.com/institutions/request. Ta'ab reviews the
application; once approved, you are the owner there too and create your live clients the
same way. Sandbox clients, passes and accounts stay in the sandbox.
Every client belongs to one institution, the organisation on the Ta'ab network that its
passes are issued by. Only an institution's owners manage its clients.
Authentication
Every request carries your client credentials:
Authorization: Basic base64(clientId:secret)
Content-Type: application/json
This is server to server. Keep the secret on your backend, never in a mobile or browser app.
Each client is granted scopes, checked on every call:
| Scope |
Allows |
identity.resolve |
POST /identity/resolve |
connections.link |
POST /links (with or without a credential), GET /links/{linkId} |
cards.activate |
POST /cards/activate |
passes.issue |
POST /passes, POST /passes/{externalRef}/revoke, GET /products |
passes.redeem |
POST /passes/verify, POST /passes/redeem (with or without a reference), GET /redemptions |
wallet.charge |
POST /charges |
either passes.* scope |
GET /passes, GET /passes/{externalRef}, GET /accounts/{taabId}/passes |
By reference you only ever see passes you issued. Another client's reference, or a pass a
Ta'ab admin issued by hand, reads as pass_not_found. The passes a holder linked to you holds,
that you issued or honour, are GET /accounts/{taabId}/passes.
Credentials
Wherever a holder presents something, send it as one of:
{ "type": "nfc", "uid": "04A22B9151C280" }
{ "type": "qr", "token": "<the string decoded from the Ta'ab app's QR>" }
Send the raw value, in the POST body. Never a hash, and never in a URL, where it would
land in access logs.
NFC: send the tag's UID, never an NDEF text record. Any spelling is accepted:
04A22B91, 04:a2:2b:91, 04 A2 2B 91. Ta'ab normalises it with this rule:
strip every character that is not [0-9a-fA-F]
uppercase the remainder
reject unless 8 ≤ length ≤ 20 and length is even (4–10 bytes)
Byte order is whatever the reader reports. Do not reverse it.
QR: the token rotates every 60 seconds. A token that has been superseded is stale,
not unknown: ask the holder to let the app refresh, and scan again. The QR is the bare
token with no prefix. It is not a UUID.
Passes
A pass is issued to one holder, under your own reference (externalRef), and belongs to your
institution. A holder can have any number of your passes at once.
There are two ways to say what a pass is:
- From your catalog. Send a
productId from GET /products. The product decides the
pass's type, how many uses it has and how long it lasts. This is how to issue a monthly
pass or a ten-visit card.
- Spelled out. Send
validFrom, validUntil, and optionally type and uses. This is
how to issue a ticket for one trip or one event. type defaults to transit and uses
to 1; uses: null means unlimited within the window. Use type: "general" when no other
type fits.
A product's days start when the pass is issued, or the first time it is redeemed: startsOn
in GET /products says which. A pass waiting for its first use has a validUntil a year
out, which is only the deadline for using it once. Its first approved redemption replaces
that with the real end of its window.
A product can be for some concession profiles only (a student bus pass): concessions in
GET /products lists them, null for anyone. A holder has the concessions their
organisations' rosters give them (a school's roster says student). Issuing such a product to
anyone else answers 409 concession_required, and nothing is issued. A spelled-out pass has
no such rule.
metadata is shown on the pass in the Ta'ab app and Ta'ab never interprets it. Five keys are
layout hints the app knows: label, origin, destination, departureAt and seat. You
can add up to 20 keys of your own, with string, number, boolean or null values.
A pass's status records what was done to it: active, expired or revoked. A ticket
that has been used, or whose window has closed, still says active; check remainingUses
and validUntil for what it can still do.
Redeeming needs the holder's credential, so there is no way to mark a pass used on their
behalf. If you admit someone by other means, such as scanning your own ticket code, the pass
stays unused and lapses when its window closes. Revoke it if you want it gone sooner.
Selling the same product again: renewal
A holder whose monthly pass ends on Friday can buy next month on Wednesday. When you
POST /passes with a productId that counts no uses (uses: null in GET /products), for a
holder who is already using a pass you sold them of that product, Ta'ab does not issue a
second pass. It renews theirs:
- The answer is that pass, with its own
externalRef and a validUntil later by the
product's durationDays, and renewal: { externalRef, addedDays } naming your sale.
renewal is null whenever a new pass was issued. A validFrom you send is not used: a
renewal always continues from where the pass ends.
- Your sale's
externalRef is its key, as for any issue: sending it again returns the same
renewal with replayed: true, and GET /passes/{externalRef} with it reads the pass.
- Refunding a renewal:
POST /passes/{externalRef}/revoke with the renewal's reference
takes its days back off, and the pass stays active. Once those days have begun, the answer
is 409 conflict. Revoking the first sale's reference revokes the pass itself, renewals
and all.
409 already_held: the holder has a pass of this product that can't be renewed by
you: it hasn't started yet (it waits for its first use, or begins later), or someone else
sold it to them. Selling it again would leave them two.
A product that counts uses (a ten-ride card) is not renewed: each sale is a pass of its own.
Passes you accept but did not issue
Some passes are honoured by several institutions: a T-Pass belongs to a transit authority
and every bus company on its list accepts it. You have no reference for such a pass, so you
redeem it without one.
POST /passes/verify returns acceptedPasses beside passes: what this holder could
use with you right now without naming a pass. It is there for your screen ("T-Pass, valid
until 12 Nov"). You never need its passId.
POST /passes/redeem without externalRef uses the holder's best pass of type
(default transit) that your institution accepts. A pass that counts nothing is used
before one that counts rides; between two alike, the one that lapses sooner. The answer
has externalRef: null and the passId that was used.
operatorRef says who carried the rider when you front several operators, such as a
ticketing system with many bus companies. It is your own name or id for them, up to 80
characters. Ta'ab records it and groups its usage report by it; send the same value every
time for the same operator.
A ticket is never used this way. A ticket is a pass you issued spelled out, for one trip or
one event: it is redeemed by its externalRef and nothing else, so a redemption without a
reference can't spend it.
A pass you sold from your catalog is different. When you issue a productId (a monthly
T-Pass, say) under your sale's reference, the pass is the holder's like any other: a
redemption without a reference can use it, at your own institution and at every institution
that accepts the product, and verify lists it in both passes and acceptedPasses. You
can still read it back or revoke it by its externalRef. It lapses with its window; revoking
it after that answers conflict, since it is already expired.
Each entry of acceptedPasses says whose it is and what a sale would do: relation is
issued for your institution's pass and accepted for another institution's that yours
honours, and renewable is true when selling its product again renews it.
passes holds only the passes this client issued. A pass of your product that your staff
issued in the admin portal is in acceptedPasses alone, and selling that holder the product
again answers already_held, because only a pass you sold is renewed by your sale. So before
you take money for a renewal, check renewable on the pass in acceptedPasses: true, a sale
renews it; false, the sale would answer already_held (someone else sold it, or it hasn't
started yet).
When the holder has nothing you accept, the answer is a denial like any other:
denied_expired with passId: null when they hold no pass of that type at all, or
denied_wrong_institution when they hold one that you are not on the list for.
Which institutions accept a product is set by Ta'ab, with each institution's agreement. Ta'ab
moves no money between you: it counts redemptions, and the pass's owner can export them by
institution and by operatorRef.
Idempotency
Both writes that matter are safe to retry.
- Issuing:
externalRef is the key. Sending the same one again, as a retry or as two
requests racing, returns the original pass (or renewal) with 200 and replayed: true.
The reference is permanent: a revoked pass keeps it, so re-issuing needs a new one.
- Redeeming:
idempotencyKey is the key. Reusing a key returns the original decision
with replayed: true and consumes nothing further. Use one key per real-world redemption
(your ticket id is a good choice) and retry freely on a timeout.
Concurrent redemptions of a single-use pass approve exactly once.
Errors and denials
Errors are { "code": "...", "message": "..." }. Branch on code; message is for people
and may be reworded.
| Status |
Code |
Meaning |
| 400 |
invalid_input |
The body, query or path is malformed, or a uid isn't a card UID |
| 401 |
unauthorized |
Missing, wrong or disabled client credentials |
| 403 |
forbidden |
Valid client, but it lacks the scope this route needs |
| 404 |
credential_not_found |
No Ta'ab card has that UID, or the QR is not one Ta'ab issued. On cards/activate: none of your card stock has that UID |
| 404 |
account_not_found |
No account has that Ta'ab ID |
| 404 |
pass_not_found |
You have no pass with that externalRef |
| 404 |
product_not_found |
You have no product with that productId |
| 409 |
card_not_assigned |
A real Ta'ab card, but in stock, returned, lost or retired |
| 409 |
credential_stale |
The QR token has been superseded by a newer one |
| 409 |
credential_revoked |
The QR credential was revoked (a lost phone) |
| 409 |
account_inactive |
The account is suspended, closed or scrubbed |
| 409 |
account_unlinked |
On POST /passes: the account has no login and no card, so nobody could present the pass. A walk-in card that was reported lost before anyone claimed it |
| 409 |
already_held |
The holder has a pass of this product that this sale can't renew (see "Selling the same product again") |
| 409 |
concession_required |
The product is for some concession profiles only (concessions in GET /products), and the holder has none of them |
| 409 |
conflict |
The externalRef is already used for a different account, the product is archived, a renewal's days have begun so it can't be revoked, or the card you are activating is someone else's, lost or retired |
| 429 |
rate_limited |
Too many requests this minute |
A denied redemption is not an error. It is HTTP 200 with result: "denied_…":
| Result |
Meaning |
approved |
One use consumed (or, for an unlimited pass, the visit recorded) |
denied_no_uses_remaining |
Already fully used |
denied_expired |
occurredAt is outside the pass's validity window. Without an externalRef: the holder has no pass of that type, or theirs has lapsed |
denied_wrong_institution |
Without an externalRef only: the holder has a valid pass of that type, but none your institution accepts |
denied_revoked |
The pass was revoked, or the card was returned or reported lost |
denied_not_holder |
The credential belongs to a different account than the pass |
denied_recently_used |
An unlimited transit pass already used with you, for the same operatorRef, in the last 10 minutes: one card is carrying a second rider. Tell them to show their own pass. A ride with another operator is not affected |
denied_daily_limit |
The pass's uses for today are spent: a pass with a dailyLimit that many times today, the day being its institution's. It works again after that institution's midnight |
denied_suspended |
The presenter's account is not in good standing |
denied_unknown_credential |
A QR token that has since rotated. Scan again |
Treat a 4xx as "the request was wrong" and a denial as "the request was fine and the answer
is no". Every denial is recorded in the holder's Ta'ab history. Only an unknown credential or
an unknown pass is a 404, because there is nothing to record it against.
Rate limit
A client may make 600 requests a minute. Past that, requests answer 429 rate_limited with a
Retry-After header giving the seconds until the next minute starts. This is a guard against
a runaway loop, not a quota: a busy boarding queue is a few requests a second.
Timeouts, retries and outages
A tap is waiting on the answer, so use a short timeout: about five seconds.
- A timeout, a network error, a
5xx or a 429 says nothing about the holder. Retry
with the same externalRef or idempotencyKey. Never show it as a refusal.
- A
401 or 403 is about you, not the holder. Your credentials or scopes are wrong.
Log it loudly and treat it as an outage at the door.
- Never make a sale wait on Ta'ab. Issue after the sale has gone through, and keep
retrying until it succeeds. The simplest way is to work from state: on a schedule, look
for paid tickets with no pass and issue them, and for dead tickets with a live pass and
revoke them. Both calls are idempotent, so the sweep can run as often as you like.
- Decide in advance what the door does when Ta'ab can't be reached. If you can identify
the holder another way, such as your own ticket's code, admit them on that.
Paging
GET /passes returns your passes newest first. Follow nextCursor until it is null.
GET /redemptions returns your redemptions oldest first. It always returns a nextCursor
you can resume from, even when you are caught up, and hasMore says whether another page is
ready now. To follow new redemptions, keep the last cursor and ask again with it.
A cursor is opaque. A page never skips or repeats a row, however much is added while you
page.
Linking an account
With the connections.link scope, POST /links links a holder's Ta'ab account to yours, in
one of two ways.
With the card in hand. Send the card's UID, or the Ta'ab app's QR, as credential.
Presenting it is the holder's consent: the answer is 201 with status: "approved", their
taabId, and the card that was tapped (its printed cardNumber). Its errors are those of
identity/resolve. Linking the same holder again reuses the link and answers a new linkId.
Without a card, the user links by saying yes in Ta'ab. No card, QR or staff member is
involved.
POST /links. You get a linkId, a url and a code. They are valid for 10 minutes,
and url and code are shown only in this answer.
- Send the user to
url: a redirect from your web app, a link in your mobile app, or a QR
on a screen. If they can't open it (a kiosk, a POS), show code instead; they type it at
/connect in Ta'ab. Either way they sign in to Ta'ab, see your app's name and what you
will learn, and approve or decline. Someone without a Ta'ab wallet gets one as they
approve.
GET /links/{linkId} until status is no longer pending. On approved, store
taabId against your user.
To get the user back to you, register one or more return URLs on your client under API
clients in Ta'ab, and send one as returnUrl in step 1. It must match a registered URL
exactly. A return URL is an https address, or your mobile app's own scheme, such as
yourapp://taab/linked, so a phone app gets the user back after they answer in the
browser. When the user answers, Ta'ab sends them there with link and status added to the
query. Treat that as a hint to call GET /links/{linkId}, never as the answer itself.
Either way, you learn the Ta'ab ID and the passes they hold that you issue or honour (below),
never their balance, history or other passes. The holder sees your app in their wallet and can
unlink it at any time. The link then reads revoked with no taabId. Passes you already
issued stay valid; stop issuing new ones to that Ta'ab ID.
Keep the linkId. A card you linked may be a walk-in card that its rider later claims into
the Ta'ab account they already had. The link follows the card, and GET /links/{linkId}
answers with the Ta'ab ID it now belongs to.
Passes a linked holder holds
GET /accounts/{taabId}/passes (either passes.* scope) lists, for a holder linked to you or
on your institution's list (a roster, or a card your counter activated), the live passes your
institution issued or honours, whoever sold them: a T-Pass a bus company sold, a company's pass
for its staff that you accept, your own tickets. Each has the same fields as an entry of
acceptedPasses, relation and renewable included. Those usable now come first, in the
order Ta'ab would spend them; those that start later follow.
This is what a page showing a rider their T-Pass reads: find the pass of your product, show
its validUntil (or, with windowDays set, "starts on your first ride"), and offer Renew
only when it is renewable. A holder who isn't linked to you answers 409 not_connected; link
them with POST /links.
Riding with no signal
A bus that loses signal can still board riders whose passes your institution honours.
- While online, keep a list.
GET /offline/passes?type=transit (scope passes.redeem,
paged by cursor) lists every holder with a pass you honour at some point in the next 24
hours: their taabId, the sha256 of each of their cards' UIDs (normalised: hex digits only,
uppercase), and the passes. Refresh it every few minutes while you have signal, and stop
taking offline rides after its honourUntil.
- Keep Ta'ab's key. With no signal, the Ta'ab app shows an offline code instead of its
rotating QR:
o1.{kid}.{taabId}.{window}.{signature}. GET /offline/key gives the
Ed25519 public key that signs it. The signature covers everything before the last dot;
window is floor(unix seconds / 600); accept the window either side of your clock's.
- With no signal, decide on the device. Accept a card whose UID hash is listed, or an
offline code that verifies and whose Ta'ab ID is listed. Refuse a second tap of the same
holder within 10 minutes on the same operator, as Ta'ab does.
- Upload each ride once you are back.
passes/redeem with offline: true, the tap's
own occurredAt (within 72 hours) and an idempotencyKey made on the device. The ride
stands: the result is approved, and offlineDenial says what Ta'ab would have answered at
the time (a pass revoked since the list was made, a second tap elsewhere), or null.
Ta'ab's usage report counts it as a ride and lists the flagged ones apart.
Online, an offline code is accepted wherever a QR is, in its own window only.
Cards for riders without a phone
With the cards.activate scope, your counter can hand a rider a working Ta'ab card on the
spot: no Ta'ab login, no phone, no form. Ta'ab allocates card stock to your institution; ask
for it. The cards are listed under NFC cards in the admin portal, where your staff can
do the same from a browser ("Walk-in").
- Tap the card and
POST /cards/activate with its uid. You get 201 with the card's
taabId, its printed cardNumber, and a claimCode. The card works from now on.
- Sell them a pass as for anyone:
POST /passes to that taabId.
- Print
claimCode on their slip. It is shown only in this answer. If the answer never
reached you, call again with "newClaimCode": true for a fresh one; the old one stops
working.
Activating a card that is already one of yours answers 200 with replayed: true, its
taabId, and claimCode: null. So a retry is safe, and a sale can start from any card the
rider already holds.
Before it is activated, a card from your stock answers card_not_assigned on
identity/resolve and passes/verify, as a lost or returned card does. So a counter that
reads a card first can offer "sell it as a new card" on that code, and let cards/activate
decide: it activates a card in stock or returned, and answers conflict for one that is lost,
retired or someone else's.
Claiming. Whenever the rider likes, they enter the code in the Ta'ab app or at
/activate. Someone new takes the card's account over and keeps its Ta'ab ID. Someone who
already has a Ta'ab account gets the card and its passes moved onto theirs, and from then on
the card answers with their Ta'ab ID. So find a returning rider by tapping their card
(identity/resolve), or by a link you made with the card (GET /links/{linkId}), never by a
Ta'ab ID you stored at the sale.
A card lost before anyone claims it is gone with its passes: nobody can prove it was theirs.
Report it lost in the admin portal and it stops working at once. After that, POST /passes
to its Ta'ab ID answers account_unlinked.
Riders on walk-in cards are on your institution's list in the admin portal as "Walk-in
card", apart from your roster: giving everyone on the roster a pass leaves them out.
Charging a balance
Some holders keep a balance with an organisation: a school's cafeteria money, added in
cash at its counter. A client with the wallet.charge scope (a canteen's till app) charges
that balance when the holder presents their card or QR: POST /charges with the
credential, amountCents, your own idempotencyKey and, optionally, a reason the holder
reads on their statement. The card is the consent, as for a redemption.
The answer is approved with balanceAfterCents, or declined with a reason:
insufficient_balance (also when the holder keeps no balance with you), over_daily_limit
(a guardian's limit), account_inactive, credential_revoked, purse_frozen. A decline is
HTTP 200 and takes nothing. Until Ta'ab holds a payment licence, a balance belongs to the
organisation it was added at and is spent only there: you can charge only the balance kept
with your own institution, and licence_required and wrong_organisation are reserved for
once that changes.
Suggested flows
Link an account. With a card: the user taps it, or staff scan their Ta'ab QR. Call
POST /links with it as credential and store the linkId and taabId on your user. Refuse
to link on any error: that is what makes the card a genuine Ta'ab card and not just any NFC
tag. Link again whenever the user links a card, and keep the newest: a card can be returned
and assigned to someone else. Without a card: see "Linking an account" above.
Show a rider their passes. Read GET /links/{linkId} for their current taabId, then
GET /accounts/{taabId}/passes.
Sell a ticket. Once the booking is paid, POST /passes with the ticket number as
externalRef. Do this out of band and retry on failure. It is idempotent, and a Ta'ab outage
must not block a sale. Revoke on cancel or refund.
Sell a membership or a monthly pass. Create the product once in the Ta'ab admin portal.
POST /passes with its productId and your own reference for the sale. Sell it again the
same way for the next period; Ta'ab renews the pass the holder is using.
Sell to a walk-in at the counter. Tap a card from your stock, cards/activate, then
POST /passes to the returned taabId and print the claim code on the slip. Next month, tap
the card, identity/resolve, and sell again to the Ta'ab ID it answers with.
Accept a shared pass. Card tap or Ta'ab QR, then passes/redeem with no externalRef,
your operatorRef, and a per-ride idempotencyKey. On approved, let them on. Do this
when you have no ticket of your own for the rider; if you do, redeem the ticket.
Redeem. Card tap or Ta'ab QR, then passes/verify. Match one of the returned
externalRefs to what is happening now, run your own checks, then passes/redeem with a
per-redemption idempotencyKey. On approved, let them in.
passes/verify tells you who is presenting even when passes is empty. With the taabId
you stored at linking you can still find their booking, which covers a ticket whose pass
has not reached Ta'ab yet, and an unpaid booking you want to collect for. A QR can rotate
between verify and redeem; that redemption answers denied_unknown_credential, and the
holder scans again.
Reconcile. Poll GET /redemptions with your last cursor and compare against your own
records.
Changes
- November 2026, concession profiles.
concessions on GET /products (null for
anyone) and 409 concession_required on POST /passes for a holder a product isn't for.
Every product is for anyone until its organisation says otherwise, so nothing you issue
changes. Nothing was removed or renamed.
- November 2026, charging a balance. The
wallet.charge scope and POST /charges.
Nothing was removed or renamed.
- October 2026, a daily limit.
dailyLimit on passes, on acceptedPasses and on
GET /products: how many times a day a pass can be used, null for no limit. A new
result, denied_daily_limit, past it. Only a product its institution gives a limit has
one; nothing you issue changes. Nothing was removed or renamed.
- October 2026, a linked card shows its passes.
credential on POST /links, which links
at once; status, taabId and card on its answer (code and url are null only when
you send a credential); a link made with a card follows it when it is claimed into another
account; GET /accounts/{taabId}/passes and 409 not_connected; validFrom, externalRef,
relation and renewable on each of verify's acceptedPasses. Nothing was removed or
renamed.
- October 2026, riding with no signal.
GET /offline/passes, GET /offline/key, the
offline QR code (o1.…, accepted as a qr credential), offline: true on
passes/redeem, the fields offline and offlineDenial on its answer and on
GET /redemptions, and the error offline_unavailable. Nothing was removed or renamed.
- October 2026, cards for riders without a phone. The
cards.activate scope and
POST /cards/activate. An account with no Ta'ab login is now a holder like any other:
identity/resolve and passes/verify answer for its card where they used to say
account_unlinked, and POST /passes returns account_unlinked only for an account with
no login and no card. Return URLs may use your app's own scheme. Nothing was removed or
renamed.
- October 2026, renewals. Selling a product that counts no uses to a holder already
using the pass you sold them of it renews that pass. Added
renewal to the POST /passes
answer (null for a new pass), 409 already_held, and revoking a renewal by its
reference. Nothing was removed or renamed.
- October 2026, selling passes. A pass you issue from a
productId is no longer treated
as a ticket: a redemption without a reference can use it, verify lists it in
acceptedPasses too, and it is marked expired when its window closes. Passes issued
spelled out are unchanged. No request or answer changed shape.
- October 2026, one rider per pass. A new result,
denied_recently_used: an unlimited
transit pass is refused a second ride with the same operator within 10 minutes. Treat a
result you don't know as a refusal you can't explain, never as approval.
- October 2026, linking. The
connections.link scope, POST /links,
GET /links/{linkId}, and 404 link_not_found. Nothing was removed or renamed.
- October 2026, self-service. The sandbox; owners create, rotate and disable their own
clients. No change to any request or answer.
- October 2026, shared passes. Passes you accept but did not issue:
acceptedPasses on
passes/verify; externalRef is now optional on passes/redeem, with type and
operatorRef; operatorRef on each row of GET /redemptions; startsOn on each product.
In a redeem answer, externalRef and passId can be null, but only when you sent no
externalRef. Nothing was removed or renamed.
- October 2026. Added
type, productId, uses: null and free-form metadata on
issuing; type and productId on every pass; GET /passes, GET /passes/{externalRef},
GET /redemptions, GET /products; the rate limit and 429 rate_limited;
404 product_not_found. Metadata keys other than the five hints used to be dropped and are
now kept. Nothing was removed or renamed.