Staff at a box office or a till
An operator is a named human and carries a staff JWT from the identity issuer. The channel travels as X-Ticketlayer-Channel, and whether the operator has to pick one depends on the scope their role grants.
An operator is a named human, so they carry a staff credential, not a device one. The till's hardware - the printer, the cash drawer, the payment terminal - is a separate concern, handled by Stagehand's device pairing and its Local Control Token; the sale itself is the operator's.
The credential. A staff JWT (high trust) from the identity issuer, carrying the
organisation. It names no channel, so the request carries
X-Ticketlayer-Channel: <slug>. A customer, where there is one, is
selected or signed in by magic link; the operator is never the customer.
The four moments#
| Moment | What happens | Credential |
|---|---|---|
| List what is on sale | GET /events with the box office channel in the header. Full shapes: the operator may see cost and allocation. Box office staff hold events.list_hidden, so the list also carries the channel's hidden and not-yet-on-sale events, each flagged with its state; the cart still accepts only what is on sale. | staff JWT plus X-Ticketlayer-Channel |
| Make an order | POST /cart with the channel header, items, POST /cart/{id}/checkout, then POST /orders/{id}/payments for cash or the terminal. Comps, manual discounts and seat holds are here, each behind its own permission. | staff JWT plus X-Ticketlayer-Channel |
| Authenticate a customer | Usually not at all. A walk-up is anonymous, or the operator selects an existing customer, which is "on behalf of" rather than authentication. A customer who signs in at the counter uses the magic link unchanged. | staff JWT; a customer token only if the customer signs in |
| Read the order back | GET /orders/{id} under the staff JWT. Amendments, cancellations and refunds are the /orders/* surface, which is the post-checkout surface for every path. | staff JWT plus X-Ticketlayer-Channel |
Who has to pick a channel#
Not every staff role is channel-bound, and the header follows the role.
- Box office staff sell at channel scope. The box office role holds the
selling permissions - list and read events, create and read orders, take
payments, manage customers - within the channels the operator has access
to. The app calls
GET /my/sales-channelsat sign-in, offers a picker, and sends the chosen slug asX-Ticketlayer-Channelon every request after. Naming a channel the operator has no access to is a403; naming none is a400, never a silent narrowing. - Organisation owners, venue admins and event managers hold the same
permissions at organisation scope. They see everything and do not have
to pick. In Backstage an event manager reads
GET /eventswith no header and gets every event they may see; with a header they get the channel's view, as a convenience filter and nothing more.
Channel access is data, granted and revoked on the channel ("who can sell
here", which is GET/POST /sales-channels/{channelId}/access and
DELETE /sales-channels/{channelId}/access/{accessId}, behind
saleschannels.access.read and .write), not part of the permission grant.
Opening a phone channel to a whole box office is one grant of access to the
role, and a rota change never touches what the role may do.
A filter is not a boundary#
On the high-trust surface X-Ticketlayer-Channel is a filter and nothing
relies on it to withhold data: a request that reached the filter has already
passed the access check. Withholding is the low-trust surface's job,
structurally. That is why an operator sees full shapes and a buyer on the
same channel sees a declared view of the same listing, and why the two
agree about what is on sale and what it costs, because both resolve
through one listings service.
Set up a box office covers the storefront, the counter calls and scanning at the door.