# Rollcall API > Create, update and publish events, read event details, import organizer-verified guests as passes, and poll check-in history. Guests RSVP on the Rollcall event page or the organizer's own site. No guest RSVPs over this API. OpenAPI 3.1 spec (machine-readable, no key needed): https://letsrollcall.com/api/v1/openapi.json Interactive docs: https://letsrollcall.com/developers Step-by-step guide (run RSVP on your own site): https://letsrollcall.com/developers#run-rsvp-on-your-own-site Base URL: https://letsrollcall.com/api/v1 ## Authentication Send a key on every request: `Authorization: Bearer rc_live_<32 letters and numbers>` Keys are made by owners and admins in the organizer app, under Team & settings, Organization, API. A key is shown once. It only accesses its own organization. Keys default to read-only; legacy keys without scopes remain read-only. Enable Can create events to grant events:write for event creation, updates and publishing. Enable Can create passes to grant passes:write for pass import and cancellation. Every valid key can read. Keep write keys on the server, never in browser code. Read keys expose guest contact details through the feed; keep them private too. ## Endpoints - GET /events: published and cancelled events, soonest first, up to 100, omitting any that ended over 24 hours ago. Drafts never appear. curl https://letsrollcall.com/api/v1/events -H "Authorization: Bearer $ROLLCALL_API_KEY" Returns { "events": [EventSummary], "asOf": "" } - GET /events/{slug}: one event, in full (EventDetail: EventSummary plus the extra fields in the EventDetail section). curl https://letsrollcall.com/api/v1/events/rooftop-night-k3m9x2q7ab -H "Authorization: Bearer $ROLLCALL_API_KEY" Returns { "event": EventDetail, "asOf": "" } - GET /events/ext:{externalId}: any valid key; includes drafts in its own organization. Returns { "event": EventWrite, "asOf": "" }. Slug reads still return EventDetail. - POST /events: requires events:write. Creates a draft. Required JSON: externalId (1-64 letters, digits, underscores or hyphens, unique per organization), title, startsAt, endsAt, timezone. Optional publish: true creates and publishes in one transaction; default false. curl https://letsrollcall.com/api/v1/events -H "Authorization: Bearer $ROLLCALL_API_KEY" -H "Content-Type: application/json" --data '{"externalId":"opening_night","title":"Opening Night","startsAt":"2026-11-14T21:00:00-08:00","endsAt":"2026-11-15T01:00:00-08:00","timezone":"America/Los_Angeles"}' Returns { "event": EventWrite (created: true), "asOf": "" }. Reusing externalId returns the existing event with created: false and changes nothing, including its publication state. - PATCH /events/{ref}: requires events:write. Partial update of core fields; ref is a published slug or ext:{externalId}. Omitted fields stay unchanged. Returns { "event": EventWrite, "asOf": "" }. A cancelled event returns 409 CONFLICT. curl https://letsrollcall.com/api/v1/events/ext:opening_night -X PATCH -H "Authorization: Bearer $ROLLCALL_API_KEY" -H "Content-Type: application/json" --data '{"title":"Opening Night on the Rooftop"}' - POST /events/{ref}/publish: requires events:write, same ref addressing. No body needed. Returns { "event": EventWrite, "asOf": "" }. Repeating publication is a 200 no-op; a cancelled event returns 409 CONFLICT. curl https://letsrollcall.com/api/v1/events/ext:opening_night/publish -X POST -H "Authorization: Bearer $ROLLCALL_API_KEY" - GET /openapi.json and GET /: no key needed. The spec, and a small JSON index that points at it. - POST /events/{slug}/passes: requires passes:write. Import 1 to 100 guests in { "guests": [...] }. Each guest requires externalId (1-64 letters, digits, underscores or hyphens), name (trimmed, 1-60 characters) and ageVerified (attested | scanned). Optional: phone (US number normalized to E.164), email, waiverAccepted { version (1-64 characters), at (ISO time) }, doorNotice (at most 140 characters), tags (at most 20, each 1-40 characters). Returns { "passes": [{ externalId, created, code, qrValue, status (valid | waiver | cancelled) } | { externalId, error: { code (INVALID | CAPACITY | INTERNAL), field? } }], "asOf": "" }. externalId is unique per event. Repeating it returns the current pass with created: false and changes nothing. A bad guest does not fail the batch. Capacity errors are per guest. Import does not check anyone in. A waiver event without waiverAccepted returns status waiver, a code and an empty qrValue, with no going-count bump. An age-gated event defaults doorNotice to Check ID. - GET /events/{slug}/passes/{externalId}: any valid key. Returns { externalId, created: false, code, qrValue, status, asOf }. - DELETE /events/{slug}/passes/{externalId}: requires passes:write. Returns the same shape with status cancelled and qrValue empty. Repeating it is a 200 no-op. Check-in history is retained, even for a guest currently inside. - GET /events/{slug}/checkins?cursor=&limit=<1..500>: any valid key; limit defaults to 200. Returns { entries: [{ id, type (checkin.in | checkin.out | checkin.undone | walkup.created), at (original scan time), undoes? (target entry id), pass: { code, externalId (or null), source (rsvp | door | import), holderLabel }, contact: { phone (or null), email (or null) }, device: { label }, via (scan | list | walkup), reentry, firstCheckIn }], nextCursor, asOf }. Entries appear about 10 seconds after recording, after a settle window lets transactions commit. Poll every 30-60 seconds with the returned nextCursor. Deduplicate on entry id. Save nextCursor even on empty or short pages. Unapplied door presentations are omitted; the cursor still advances past them. Undo rows are included. Offline scans keep their original at time. No webhooks are sent. Pass routes and the check-in feed require a published event belonging to the key's organization. Unknown, unpublished, cancelled or other-organization events return 404. ## EventSummary slug, title, status (published | cancelled), startsAt and endsAt (ISO 8601 with the event timezone's offset; endsAt may be null), timezone (IANA), venue { name, address, city } (each may be null; name and address are null when the event hides its location or is password or invite only), coverImageUrl (or null), access (link | password | invite), ageGate (off | attest | scanOptional | scanRequired), waiver (boolean), capacity (or null), counts { going, reserved, waitlist, spotsLeft (or null) }, rsvpOpen (boolean), url. ## EventDetail (GET /events/{slug} only) Everything in EventSummary, plus: description { html, text } (html is the sanitized markup the event page renders; empty strings when none), organization { name, handle (or null), url (public profile, or null), avatarUrl (or null) }, updates [{ postedAt (ISO, UTC), subject, html, text }] (host updates on the page, newest first, at most 25), theme { style (color | mono), colors { bg, text, muted, accent, accentText, g1, g2, g3, g4 } (6-digit hex, or null when the event has no custom palette) }. A password or invite event returns an empty description and no updates, as its public page shows only a teaser. ## Event writes Create and PATCH accept title (trimmed, 1-120 characters), startsAt and endsAt (ISO strings with an offset; end after start, at most 14 days), timezone (IANA), description (markdown, stored with the app's normalization; at most 5000 normalized characters), location { name, address, city, hidden, lat, lon } (each optional, null clears a part; hidden null resets false), capacity (positive whole number or null for no cap), ageGate (off | attest | scanOptional | scanRequired), access (link | passcode), passcode (4-64 trimmed characters, write-only, required when enabling passcode access without an existing one), listed, collectPhone, allowReentry (booleans). Only link-access events can be listed. Unknown fields, including unknown location parts, return 400 INVALID with field naming the JSON field. externalId and publish belong to create only; externalId cannot be patched. EventWrite has all EventDetail fields, with nullable slug and url for drafts, status (draft | published | cancelled), externalId (or null for app-created events without one), access (link | passcode | invite), and created on create only. Existing slug reads keep EventDetail's password spelling. Passcodes are never returned. Drafts never appear in GET /events. Every valid key can read its organization's ext: references. No event-count limit is enforced at creation. API events record the key's creator and API key attribution internally. Updates use the same validation, close-out rescheduling, spot release, reminders and existing guest notices for time/place changes as the organizer app. The look (cover and palette), questions, waiver, plus-ones, invite-only access, cancelling and deleting stay in the app. Cancelling notifies guests. After publishing, a passes:write key can import guests using the slug. ## Guest RSVP and organizer imports `url` is the Rollcall event page (https://letsrollcall.com/event/). Link guests there for Rollcall RSVP. An organizer can also collect its own RSVPs and import passes for those guests. The organizer vouches for identity, age and waiver acceptance. Door staff still check ID. No guest RSVPs over the API. ## Errors and limits Errors are JSON: { "error": { "code": "INVALID" | "UNAUTHORIZED" | "FORBIDDEN" | "NOT_FOUND" | "RATE_LIMITED" | "CONFLICT" | "ORG_SUSPENDED", "message": "...", "field": "" } } 400 INVALID for malformed bodies, cursors or limits. 401 for a missing, malformed, unknown or revoked key. 403 FORBIDDEN for missing the route's events:write or passes:write scope. 404 for unknown resources or an event outside the key's access. 409 CONFLICT for cancelled event writes or conflicting event invariants. Suspended-organization keys are refused with 401; if suspension races a write, 403 ORG_SUSPENDED. 413 INVALID for a body over the proxy's 64 KB cap. 429 when over the limit, with Retry-After in seconds. Reads allow 60 requests a minute per key, burst 120. Writes have a separate budget of 600 a minute, burst 1,200. A batch spends one write request. Successful reads are cacheable for 15 seconds. Writes return Cache-Control: no-store. CORS allows any origin and supports PATCH. Keep keys private and call from a server. Use of the API is covered by the Terms of Service: https://letsrollcall.com/terms