# App + User Auth Model

App service-auth identifies the calling event app. Buyer/organizer assertions
identify the user authorizing an action. Neither grants OAuth repository writes.
Use the target environment's advertised broker service audience and exact
method `lxm`; do not hard-code a service fragment from an old draft or activate
an unreleased broker/checkout cutover.

## Paid buyer identity

`createTicketHold` accepts either no buyer fields (guest), or both `buyerDid`
and `buyerAssertionJwt`. A bare DID is not an authenticated buyer. The buyer
assertion has `iss = buyerDid` and
`lxm = money.atmosphere.payment.assertPayer`; the app JWT has
`lxm = tickets.atmosphere.createTicketHold`. Tokens are short-lived,
audience-bound, signature-verified and replay-protected. Store verification
metadata, never the raw user JWT.

An asserted buyer is fixed by default. Request `payerBinding: "switchable"`
only when the app reconciles `CheckoutPayerChanged` and ATM's final payer. Store
the returned private `statusToken` server-side before redirecting to `url`;
the browser `token`/URL may rotate independently. Exact replay must not relax
the original identity binding.

## Organizer authority

Creating an event/type for another organizer requires a verified organizer
assertion scoped to the exact creation method. Omission is allowed when the app
is itself the organizer; it is not a general pilot exemption.

Later mutations require current event control. The app that originally created
the configuration has no standing owner authority. An accepted co-organizer can
perform supported operational edits, forms and event reads; commercial changes
(price, currency, capacity, raw metadata/PWYW), archival and cancellation remain
primary-organizer-only. Consult each raw schema for the exact boundary.

Organizer selling approval through ATM's recipient-approval flow is separate
from configuration consent. Availability/holds/claims remain app- and
environment-scoped and require the applicable selling approval.

`acceptCollaboration` requires the named co-organizer's own method-scoped
assertion. `revenueShareBps` records manual-reconciliation intent only; it does
not split or pay ticket revenue. `listCollaborations`, `listEventWaitlist` and
`listTicketFormResponses` are POST procedures so assertions never enter query
strings.

## Free buyers

`claimFreeTicket` and `releaseFreeTicket` always require `buyerDid` plus a buyer
assertion scoped to that exact method. Fresh claims also need private delivery
email. There is no guest free-claim bypass. Waitlist calls require the buyer
assertion whenever a buyer DID is supplied.

## Scanner authority

The first-party PWA at `scan.atmosphere.tickets` signs staff in with AT Protocol
OAuth (`atproto` only), derives scanner DID from its session, and requires an
active private app/environment/event/list grant. It uses
`/scan/api/tickets/context`, `/verify`, and `/check-in` under the
`/scan/api/tickets` prefix. Browser-submitted scanner identity is not authority.

Event apps can instead call `verifyTicket`/`checkInTicket` with app service-auth.
The separate private pilot machine API uses an ATM-issued bearer bound to an
exact deployment, grant, event and scanner. See [SCANNER_API.md](SCANNER_API.md).

## Public receipts, RSVP and callbacks

A service assertion proves private identity; it does not authorize a public
payer receipt or RSVP. Public writes require the buyer's separate OAuth grant
and explicit applicable consent. ATM controls payer-record publication;
originating apps do not receive the payer grant or unpublished receipt bytes.
Zero-price claims, test payments and paid orders discounted to $0 publish no
payment proof. Public RSVP/Going is social intent, not admission authority.

ATM authenticates HTTP callbacks with its webhook signature and XRPC callbacks
with ATM service-auth. Verify raw bodies and environment, then atomically
record durable delivery deduplication with application side effects. Public
catalog records never carry webhook endpoints, assertions or staff grants.
