Pass API
Partner guide

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
externalRefrequiredYour id for the film run, matching what the merchant set on the Program
bookingRefrequiredYour reference for the sale. This is the idempotency key
phonerequiredThe customer. 6–20 chars of digits and +()- — punctuation is fine, we normalise
displayNameoptionalMax 200 chars
startsAtrequiredThe screening, as a UTC instant
seatsoptionalMax 100 chars, opaque — see below
halloptionalMax 100 chars, opaque
barcodeoptionalYour 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 bookingRef twice → the same pass, alreadyIssued: true. A different startsAt or seats in 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 with alreadyIssued: 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)404no program with this externalRef
Program is not a TICKET400only a ticket program is issued from a booking system
Program still in DRAFT or archived409this program is not published yet
startsAt missing400field-bound on startsAt
seats / hall over 100 chars, non-ASCII barcode400field-bound
Missing or unknown key401see 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.

On this page