Pass API
Partner guide

Voiding a ticket

POST /api/partner/tickets/{bookingRef}/void — a refund and a cancellation are opposite obligations.

POST /api/partner/tickets/{bookingRef}/void
Authorization: Bearer <key>
Content-Type: application/json
{
  "reason": "CANCELLATION",
  "message": "19:30 үзвэр цуцлагдлаа — Танхим 5"
}
Field
reasonrequiredREFUND or CANCELLATION
messageoptionalMax 2000 chars. What the holder is told. Omit for silence
{ "serialNumber": "8f14e45f-...", "state": "VOID", "reason": "CANCELLATION" }

Both reasons land the pass at VOID and grey the card out — Apple's voided, Google's INACTIVE object state. Both wallets sync on their own.

Why the reason is stored

A refund and a cancellation are one state change and opposite obligations.

  • REFUND — she asked for her money back. She knows the seat is gone; saying so is noise. In the merchant's numbers this is churn.
  • CANCELLATION — the projector broke. She does not know, and telling her is the entire point of putting the ticket in a wallet in the first place. In the numbers this is breakage.

They are counted separately because a merchant reading a report needs them apart. reason is a fixed pair, not free text, so it stays countable.

The message

Supply message and one notice goes out, written into the card so it reaches the lock screen. Omit it and the card greys out in silence.

The message is operational: it checks neither the three-notices-per-customer-per-day cap nor marketing consent. A ticket buyer never consented to a mailing list, and this is not marketing — she paid for the seat and is about to turn up for it. There is no merchant-facing flag for this and there never will be one; it is derived from the program type and the notice's origin, because a flag every merchant ticks is not a cap.

The program's notification tokens render, so a message may carry the facts rather than repeat them:

{ "reason": "CANCELLATION", "message": "{{starts_at}} үзвэр цуцлагдлаа" }

{{starts_at}} renders as MM-dd HH:mm in Asia/Ulaanbaatar. {{seat}} and {{hall}} also work and render an em dash when unset.

Google's own quota is real and can still refuse an exempt notice. A half-landed cancellation is accepted knowingly as better than a suppressed one.

Idempotency

Voiding twice is a no-op: the second call returns 200 with the same body and the reason already stored, and sends no second notice. A retried cancellation must not put the same sentence on her lock screen twice.

This means the reason is fixed by the first successful call. Voiding a REFUND and then calling again with CANCELLATION leaves it a REFUND and tells nobody.

Refusals

Status
Unknown bookingRef (or another merchant's)404no ticket with this bookingRef
reason missing or not one of the two400field-bound
Missing or unknown key401

What voiding does not do

There is no un-void. There is no endpoint that moves a screening's time or hall, and none that re-seats a pass — if a screening moves, the tickets for it have to be voided and reissued against the new time. A screening-change call is a known gap rather than a decision, and lives in the backlog as the next thing this surface wants.

On this page