Skip to main content

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:

ScopeGrants
registrations_readList and read registrations, including attendee contact details and answers
registrations_checkinCheck in and check out only
registrations_writeAdd 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, or transfer_pending.
  • checkedInAt — attendance: a timestamp when the guest is checked in, null when 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":

SituationResult
First check-in of an active registration200, sets checkedInAt, fires ticket.checked_in
Re-scan of an already-checked-in registration200, no side effects — no duplicate webhook, no new activity entry
Check-in of a canceled, refunded, or otherwise non-active registration422 — the ticket is no longer valid; do not admit
Check-in of a transfer_pending registration422 — the transfer hasn't been claimed yet
Check-out, any lifecycle status200, 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:

  • paymentMethod is complimentary or cash. 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 return 422 — 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 (422 otherwise).
  • 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: true marks the guest attended immediately (the walk-up case) and fires ticket.checked_in on top of the order webhooks. Default is false.
  • sendEmail controls the ticket email to the guest and defaults to true — 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 relatedRegistrationIds is exactly the affected set, and a ticket.cancelled webhook 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:

fieldTypecontent
short_text, textFree text
dropdownOne of the question's options, exactly
terms"true" (accepted) or "false" (declined) — the question's termsText is what the guest agrees to
addressA JSON object: { "line1": …, "line2"?: …, "city": …, "state": …, "postalCode": …, "country": … }
phoneA 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.
  • resendTicket is 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.cancelled fires.
  • No money moves. Refunds are not part of this endpoint; issue them from the Viewcy dashboard.
  • Already canceled → 200, idempotent. refunded or transferred → 422 — those lifecycles are past the point of a plain cancel.
  • Festival passes cascade — see Festival passes.

See also​