Issuing a ticket
POST /api/partner/tickets — call it once per confirmed booking.
POST /api/partner/tickets
Authorization: Bearer <key>
Content-Type: application/json{
"externalRef": "0001-HO00001797",
"bookingRef": "URG-2026-0012345",
"phone": "99112233",
"displayName": "Бат",
"startsAt": "2026-09-04T11:00:00Z",
"seats": "Эгнээ 12 суудал 12",
"hall": "Танхим 1",
"barcode": "URG|2026-09-04|A5"
}| Field | ||
|---|---|---|
externalRef | required | Your id for the film run, matching what the merchant set on the Program |
bookingRef | required | Your reference for the sale. This is the idempotency key |
phone | required | The customer. 6–20 chars of digits and +()- — punctuation is fine, we normalise |
displayName | optional | Max 200 chars |
startsAt | required | The screening, as a UTC instant |
seats | optional | Max 100 chars, opaque — see below |
hall | optional | Max 100 chars, opaque |
barcode | optional | Your door scanner's payload. ASCII only |
externalRef is yours, not ours: the merchant types it into the Program's Захиалгын системийн
код field (Пасс → the ticket card → Дүрэм), so agree on the string first. It is unique per
merchant and a Program without one cannot be issued against.
{
"serialNumber": "8f14e45f-ceea-467a-9c1a-8b6f2c5d1e40",
"appleUrl": "https://.../public/passes/8f14e45f.../.pkpass?t=...",
"googleUrl": "https://.../public/passes/8f14e45f.../google?t=...",
"alreadyIssued": false
}Send appleUrl / googleUrl to the customer — email, SMS, a confirmation page, a QR. Both carry
the pass's auth token, so treat them as secrets belonging to that one customer. googleUrl is
null when the merchant has no Google Wallet configured.
The response is 200, never 201 — a retry returns the same body, and a client branching on the
status would be branching on something it must not care about. alreadyIssued says which happened.
Idempotency
bookingRef is unique per merchant and enforced by a database index.
- Same
bookingReftwice → the same pass,alreadyIssued: true. A differentstartsAtorseatsin the replay is ignored, not applied — there is no re-seating through this endpoint or any other. Nothing errors; the original stands. - Different
bookingRef→ always a new pass, even for the same phone on the same film run. Friday and Sunday seats are two passes. - Two genuinely concurrent calls with one reference: one wins, the other gets 409
this bookingRef is already being issued; retry to read the card back. Retry and you get the 200 withalreadyIssued: true.
One barcode per pass
A pass carries exactly one barcode — Apple renders one, Google's event ticket object has a single field. Your booking granularity therefore decides how many passes you create, because we mint one per call:
- Booking reference covers four seats → one call, one pass listing four seats. The party must enter together and no seat can be forwarded.
- Per-seat references → four calls, four passes.
We do not choose. The number of calls does.
barcode itself is optional and the choice is real:
Supplied — we store the payload opaquely, print it on the card and never interpret it. Your existing door scanner keeps working unchanged. We cannot tell you whether the ticket was used. The typeable short code is omitted from the card, since it is derived from our serial and your till cannot resolve it.
Omitted — we generate the barcode from the pass serial and print a typeable fallback under it.
Your door staff scan with our Scanner app, and the first scan is terminal: a second one is refused
with 409 this ticket is REDEEMED — it was already scanned at the door. This is the only
arrangement where admission is a number you can read back.
ASCII only, because Apple is told the barcode encoding is iso-8859-1 and silently mangling a
payload at the door is worse than refusing it here.
Seats and halls are display strings
12-12, Эгнээ 12 суудал 12, J12 and A5, A6 are all correct. We never parse, split or
normalise either field — we print what you send, trimmed. Blank and absent are the same thing and
both store nothing; a card field with nothing behind it renders an em dash. A conference with open
seating simply omits both.
Refusals
| Status | ||
|---|---|---|
Unknown externalRef (or another merchant's) | 404 | no program with this externalRef |
Program is not a TICKET | 400 | only a ticket program is issued from a booking system |
Program still in DRAFT or archived | 409 | this program is not published yet |
startsAt missing | 400 | field-bound on startsAt |
seats / hall over 100 chars, non-ASCII barcode | 400 | field-bound |
| Missing or unknown key | 401 | see authentication |
Expiry is automatic
You never send an end time. The card dies at startsAt + durationMinutes, where the duration is
set once on the Program — Dune 3 is 150 minutes at every screening of it. That derived instant goes
to Apple's expirationDate and Google's validTimeInterval, so the wallets do the expiring.
Nothing sweeps it and no endpoint returns it.