Read endpoints
Every endpoint of the developer API, what it returns, how paging works, and how to keep an onsite app up to date. For keys and authentication, see the developer API overview.
All endpoints are GET under https://blocklive.io/api/v1 and answer JSON. Send your key on every request:
Authorization: Bearer bl_live_<id>_<secret>| Path | Returns |
|---|---|
/events | The events your key can read, newest first. |
/events/{event_id}/ticket-types | The event's ticket types. |
/events/{event_id}/attendees | One row per ticket holder, with only the details shared with your key. |
/events/{event_id}/attendees/{attendee_id} | One attendee. |
/events/{event_id}/check-ins | Check-ins and check-outs in the order they happened (needs venue check-in). |
IDs
event_id is the event's Blocklive ID. Every other ID is an opaque string made for your key and that event: att_… (attendees), typ_… (ticket types), ent_… (tickets) and pur_… (purchases). The same person has a different attendee_id at another event and for another app. Use them only with this API, and don't try to match them across keys or events.
Events
/api/v1/events{
"data": [
{
"event_id": "6f1c…",
"title": "Summer Opening",
"starts_at": "2026-11-13T18:00:00.000Z",
"ends_at": "2026-11-14T02:00:00.000Z",
"time_zone": "Europe/London",
"shared_fields": ["name", "checkin_state"]
}
],
"next_cursor": null,
"complete": true,
"as_of": "2026-11-05T12:00:00.000Z"
}shared_fields lists the attendee details your key reads for that event.
Ticket types
/api/v1/events/{event_id}/ticket-types{
"data": [{ "ticket_type_id": "typ_…", "name": "General admission" }],
"as_of": "2026-11-05T12:00:00.000Z",
"shared_fields": ["ticket_entitlements", "ticket_type_names"]
}name is there only if ticket type names are shared with your key. This endpoint isn't paged.
Attendees
/api/v1/events/{event_id}/attendees/api/v1/events/{event_id}/attendees/{attendee_id}One row per ticket holder. Every row has:
| Field | Meaning |
|---|---|
event_id | The event. |
attendee_id | The holder, as an opaque ID for your key. |
eligibility | eligible (holds a ticket), pending (a purchase is still processing) or ineligible. |
updated_at | When anything about this attendee last changed. |
The rest appears only if it is shared with your key:
| Field | Detail | Value |
|---|---|---|
email | Email address | A string or null. |
name | Full name | A string or null. |
company | Company | A string or null. |
job_title | Job title | A string or null. |
ticket_entitlements | Ticket types and access | Their tickets: id, ticket_type_id, status, and checkin_state if venue check-in is shared. |
ticket_type_names | Ticket type names | id and name of each ticket type they hold. Needs ticket types and access. |
purchase_state | Purchase status | Their purchases: id and state (confirmed, pending, rejected or unknown). |
checkin_state | Venue check-in | checked_in, not_checked_in or unknown. |
checkin_time | Check-in time | When they were checked in, or null. Needs venue check-in. |
{
"data": [
{
"event_id": "6f1c…",
"attendee_id": "att_…",
"eligibility": "eligible",
"updated_at": "2026-11-05T11:58:00.000Z",
"name": "Sam Lee",
"checkin_state": "not_checked_in"
}
],
"next_cursor": "…",
"complete": false,
"as_of": "2026-11-05T12:00:00.000Z",
"shared_fields": ["name", "checkin_state"]
}Check-ins
GET /api/v1/events/{event_id}/check-ins?cursor=<last next_cursor>{
"data": [
{
"attendee_id": "att_…",
"entitlement_id": "ent_…",
"checkin_state": "checked_in",
"at": "2026-11-13T18:00:00.000Z"
}
],
"next_cursor": "…",
"complete": true,
"as_of": "2026-11-13T18:00:05.000Z"
}Needs venue check-in shared with your key.
checkin_stateischecked_inorchecked_out, one row per scan or change at the door, oldest first. A ticket can appear more than once (scanned again, or out and back in): apply rows in order, and the last one for a ticket is its state.- A scan shows up about 5 seconds after it happens.
entitlement_id(the ticket) is there only if ticket types and access are shared.attendee_idis the ticket's holder now (it matches the attendees endpoint), ornullfor a ticket that no longer exists.- Every answer has a
next_cursor, even an empty one. Keep the last one and send it on the next poll: you get only what happened since.
The attendees endpoint gives each attendee's current checkin_state and checkin_time, if shared. It is the source of truth: use it to correct your copy when you resync.
Pages
/events, /attendees and /check-ins return up to limit rows (1 to 100, default 100) and a next_cursor. Pass it back as ?cursor= for the next page. complete: true means there is nothing after this page. limit and cursor are the only query parameters.
A page cursor can be used for 15 minutes; a check-in cursor for 24 hours, so a door app restarted during doors carries on where it was.
A 410 resync_required means the cursor can't be used any more: it is too old, or the organizer changed what your key may read. Start again without a cursor. See limits and errors.
Keeping an onsite app up to date
- At start-up, and every 10 minutes: read every page of
/events/{event_id}/attendeesfor the guest list. - Every 5 seconds during doors: poll
/events/{event_id}/check-inswith your last cursor, and apply the rows in order. An empty answer is cheap. - On
410, drop the cursor and read check-ins from the start (it replays the night's scans, oldest first). On429, wait forRetry-After.
That is about 12 requests a minute for check-ins plus one guest-list sync every 10 minutes (one request per 100 attendees), well under the limit. Don't run more than one poller per key.
Check-in itself stays in Blocklive's door app. This API reads it; it doesn't change it.