Skip to content

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:

HTTP
Authorization: Bearer bl_live_<id>_<secret>
PathReturns
/eventsThe events your key can read, newest first.
/events/{event_id}/ticket-typesThe event's ticket types.
/events/{event_id}/attendeesOne row per ticket holder, with only the details shared with your key.
/events/{event_id}/attendees/{attendee_id}One attendee.
/events/{event_id}/check-insCheck-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

GET/api/v1/events
JSON
{
    "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

GET/api/v1/events/{event_id}/ticket-types
JSON
{
    "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

GET/api/v1/events/{event_id}/attendees
GET/api/v1/events/{event_id}/attendees/{attendee_id}

One row per ticket holder. Every row has:

FieldMeaning
event_idThe event.
attendee_idThe holder, as an opaque ID for your key.
eligibilityeligible (holds a ticket), pending (a purchase is still processing) or ineligible.
updated_atWhen anything about this attendee last changed.

The rest appears only if it is shared with your key:

FieldDetailValue
emailEmail addressA string or null.
nameFull nameA string or null.
companyCompanyA string or null.
job_titleJob titleA string or null.
ticket_entitlementsTicket types and accessTheir tickets: id, ticket_type_id, status, and checkin_state if venue check-in is shared.
ticket_type_namesTicket type namesid and name of each ticket type they hold. Needs ticket types and access.
purchase_statePurchase statusTheir purchases: id and state (confirmed, pending, rejected or unknown).
checkin_stateVenue check-inchecked_in, not_checked_in or unknown.
checkin_timeCheck-in timeWhen they were checked in, or null. Needs venue check-in.
JSON
{
    "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>
JSON
{
    "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_state is checked_in or checked_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_id is the ticket's holder now (it matches the attendees endpoint), or null for 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

  1. At start-up, and every 10 minutes: read every page of /events/{event_id}/attendees for the guest list.
  2. Every 5 seconds during doors: poll /events/{event_id}/check-ins with your last cursor, and apply the rows in order. An empty answer is cheap.
  3. On 410, drop the cursor and read check-ins from the start (it replays the night's scans, oldest first). On 429, wait for Retry-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.