Registrations & check-in
A registration is one person attending one occurrence with one ticket. It is the guest-list row: who's coming, to which date, on which ticket, and whether they've arrived. Registrations are a top-level resource — like orders and patrons, they are queried across events and addressed by id:
/2025_06/registrations
/2025_06/registrations/{id}
/2025_06/registrations/{id}/check_in
/2025_06/registrations/{id}/check_out
/2025_06/events/{id}/questions
This page covers the concepts the endpoint reference doesn't: the scope model for door apps, how lifecycle and attendance differ, the check-in contract, what adding a guest does (and deliberately skips), festival-pass fan-out, and how custom questions round-trip.
The id is what the ticket QR code encodes
A registration's id is the same identifier embedded in the ticket's QR code — on the PDF ticket and in Apple/Google wallet passes alike. The QR encodes a URL ending in the registration id; a scanner app parses the id out and calls POST /registrations/{id}/check_in directly. No lookup or code-resolution step is needed.
The other identifiers round-trip with their own resources: eventId with GET /events/{id}, occurrenceId and ticketId with the event's nested resources, and orderId with GET /orders/{id}.
Scopes: built for door apps
Three scopes cover registrations, designed so a check-in app can run with the least privilege possible:
| Scope | Grants |
|---|---|
registrations_read | List and read registrations, including attendee contact details and answers |
registrations_checkin | Check in and check out only |
registrations_write | Add guests, edit attendee details, cancel — and everything registrations_checkin grants |
registrations_write implies registrations_checkin: the check-in endpoints accept either scope, so a management integration doesn't need to request both.
A token holding only registrations_checkin gets a reduced response from the check-in endpoints: enough to confirm the person at the door (name, seat, status, checkedInAt) but no email address, no answers, and no expand support. Reading attendee PII requires registrations_read or registrations_write. Personal access tokens are never scope-limited, as usual.
Lifecycle and attendance are separate fields
Two independent fields describe a registration, and they don't overlap:
status— the lifecycle:active,canceled,refunded,partially_refunded,refund_pending,transferred, ortransfer_pending.checkedInAt— attendance: a timestamp when the guest is checked in,nullwhen not.
A canceled registration can still carry a checkedInAt (they attended, then the organizer canceled), and an active one usually starts with null. Don't infer one from the other.
GET /registrations applies no default filter — canceled and transferred registrations are returned alongside active ones. Use status= to narrow, and checkedIn=true|false to filter on attendance.
status= accepts only the values listed above; anything else is a 422 naming them. A misspelling is refused rather than ignored, so a narrowed query never quietly answers with the whole guest list. Note that the ticket.cancelled webhook spells its event name with two Ls while the status value is canceled.
Checking in and out
POST /registrations/{id}/check_in marks the guest attended; POST /registrations/{id}/check_out undoes it. The contract is built for scanners, where a 200 means "admit":
| Situation | Result |
|---|---|
| First check-in of an active registration | 200, sets checkedInAt, fires ticket.checked_in |
| Re-scan of an already-checked-in registration | 200, no side effects — no duplicate webhook, no new activity entry |
Check-in of a canceled, refunded, or otherwise non-active registration | 422 — the ticket is no longer valid; do not admit |
Check-in of a transfer_pending registration | 422 — the transfer hasn't been claimed yet |
| Check-out, any lifecycle status | 200, idempotent — undoing attendance on a since-canceled registration is legitimate |
Repeats are safe by design: concurrent scanners hitting the same registration can't double-fire the webhook, and a flaky connection can retry without consequence. The webhook and the dashboard activity entry fire only on an actual transition.
Check-in and check-out have their own rate-limit bucket of 600 per minute — a shared budget sized for several scanners working one venue — independent of the read and write buckets.
Adding guests
POST /registrations adds a guest the way an organizer does at the door — it is an organizer override, not a public checkout:
paymentMethodiscomplimentaryorcash. Complimentary is free of charge regardless of ticket price; cash records the ticket's price as collected at the door. Both complete in a single request. Card payments are not supported here and return422— online sales stay on Viewcy checkout.- Capacity and sales windows are bypassed. The event's registration limit, the ticket's quantity, and its sales window don't block the request — doors open after online sales close, and the organizer decides who gets in. What still holds: the event must be published, and the occurrence must not be canceled (
422otherwise). - Required questions are not enforced. You may submit
answers, but omitting a required question doesn't block entry. (Answers you do submit are format-validated — see Questions and answers.) checkIn: truemarks the guest attended immediately (the walk-up case) and firesticket.checked_inon top of the order webhooks. Default isfalse.sendEmailcontrols the ticket email to the guest and defaults totrue— omit it and the guest is emailed.
Some tickets can't be sold this way and return 422: custom-price (pay-what-you-want) tickets, multi-entry passes, and tickets on events with assigned seating (the API doesn't take a seat selection yet — the seat field is read-only output).
Send an Idempotency-Key on every create. A door app on venue Wi-Fi retrying a timed-out request without one will create duplicate registrations — the header is optional, but here it earns its keep.
Festival passes
A festival pass admits one person to several occurrences, and the API models that as one registration per occurrence:
- Create — adding a guest with a festival-pass ticket creates the whole set. The response is the primary registration; its siblings' ids are in
relatedRegistrationIds(empty for ordinary tickets). - Cancel — canceling any registration of a festival pass cancels the entire pass. Cancellation operates at purchase granularity, matching how it works everywhere in Viewcy. The response's
relatedRegistrationIdsis exactly the affected set, and aticket.cancelledwebhook fires per sibling. - Check-in stays per-occurrence — each registration in the set checks in independently on its own date.
Questions and answers
Events can define custom checkout questions. Their definitions live on the event — GET /events/{id}/questions lists them, gated by events_read since they're event configuration with no attendee data. Their answers live on the registration — expand them with GET /registrations/{id}?expand=answers, gated by the registration scope since answers are attendee PII.
Answers round-trip by questionId. The content format depends on the question's fieldType, and writes validate it:
fieldType | content |
|---|---|
short_text, text | Free text |
dropdown | One of the question's options, exactly |
terms | "true" (accepted) or "false" (declined) — the question's termsText is what the guest agrees to |
address | A JSON object: { "line1": …, "line2"?: …, "city": …, "state": …, "postalCode": …, "country": … } |
phone | A valid phone number |
One asymmetry to know: create doesn't enforce required questions (organizer override, fast door entry), but PATCH refuses to blank a required question's existing answer (422) — an update that erases data the guest already gave is treated as a mistake.
Updating a registration
PATCH /registrations/{id} edits attendee details (attendee.firstName, attendee.lastName, attendee.email), answers, and accepts resendTicket: true. The partial-update rules apply. Semantics worth knowing:
- Only active registrations are editable — any other lifecycle status returns
422. - Changing the attendee's email can re-link the registration to an existing Viewcy account matching the new address, the same as editing an email in the dashboard. The new address's owner gains access to the ticket.
resendTicketis applied after the other changes, so the ticket email reflects them and goes to the new address.
Canceling a registration
DELETE /registrations/{id} cancels — it is a state change, not a delete. The registration remains readable, so the endpoint returns 200 with the updated resource, status: "canceled". The behavior is fixed:
- The guest is emailed about the cancellation, and
ticket.cancelledfires. - No money moves. Refunds are not part of this endpoint; issue them from the Viewcy dashboard.
- Already canceled →
200, idempotent.refundedortransferred→422— those lifecycles are past the point of a plain cancel. - Festival passes cascade — see Festival passes.
See also
- Authentication & token model — scopes, and why a missing scope is
403but a foreign resource is404. - Idempotency — the
Idempotency-Keycontract; strongly recommended onPOST /registrations. - Creating and updating resources —
nullvs. omitted fields onPATCH, validation errors. - Webhooks — react to check-ins, cancellations, and new orders as they happen.