Events
Every path is relative to https://app.lobbe.ai/api/v1, and every request sends Authorization: Bearer $LOBBE_TOKEN. The API reference lists every route and explains the x-lobbe-* fields.
GET /events
Section titled “GET /events”What happened to the owner’s meetings since a cursor: created, waiting for names, ready, failed, updated, deleted.
| Property | Value |
|---|---|
| Scopes | recordings:read or recordings:write |
| Spends minutes | No |
| Rate limit | read: 600 requests per 10 minutes per token |
| Success | 200 |
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
after |
query |
"latest" | string |
No | Where to read from: the nextCursor of the previous page, passed back as is, or latest, which starts from now (no events, and a cursor to poll with). Absent = the oldest event a fresh read serves, about two hours inside the 30-day window events are kept for. A cursor older than that window answers 410 CURSOR_EXPIRED: take a new cursor with after=latest first, then catch up from listReadyRecordings, then read on from that new cursor. A malformed value is a 400 |
limit |
query |
integer |
No | How many events to return, 1–500 (default 100) |
Response 200
Section titled “Response 200”What happened to the meetings this token reaches since after, oldest first. With recordings:read, the owner’s own meetings (not those of a series they joined); with only recordings:write, the meetings this token submitted.
| Field | Type | Always present | Description |
|---|---|---|---|
schemaVersion |
1 |
Yes | Always 1 within API v1 |
events[] |
object[] |
Yes | The events on this page, oldest first; for one meeting, in the order its changes happened. No event carries a meeting’s content: only ids, its status, an error code or the kind of change |
events[] (type: "recording.created") |
object |
A meeting was received | |
events[].id |
string |
Yes | The event’s own id, the same on every read: use it to skip an event you already handled. It is not a cursor (page with nextCursor) |
events[].recordingId |
string |
Yes | The id of the meeting it is about |
events[].seriesId |
string | null |
Yes | The series the meeting was in when the change happened; null when it was in none, or while series are switched off |
events[].occurredAt |
string |
Yes | When the change happened (ISO 8601, UTC); follow the feed’s order rather than sorting by it |
events[].type |
"recording.created" |
Yes | recording.created: the meeting was received in full, whether recorded in the app, uploaded or submitted by a token |
events[].data |
object |
Yes | The meeting’s status as the change left it |
events[].data.status |
"QUEUED" | "TRANSCRIBING" | "NAMING" | "GENERATING" | "READY" | "FAILED" |
Yes | Where the meeting is: QUEUED, TRANSCRIBING, NAMING (waiting for the speakers’ names), GENERATING (the Lobbe is being made), then READY; or FAILED |
events[] (type: "recording.naming_required") |
object |
A meeting is waiting for names | |
events[].id |
string |
Yes | The event’s own id, the same on every read: use it to skip an event you already handled. It is not a cursor (page with nextCursor) |
events[].recordingId |
string |
Yes | The id of the meeting it is about |
events[].seriesId |
string | null |
Yes | The series the meeting was in when the change happened; null when it was in none, or while series are switched off |
events[].occurredAt |
string |
Yes | When the change happened (ISO 8601, UTC); follow the feed’s order rather than sorting by it |
events[].type |
"recording.naming_required" |
Yes | recording.naming_required: the meeting is at NAMING, waiting for its speakers’ names or a skip (skipRecordingNaming); sent again each time naming opens again |
events[].data |
object |
Yes | The meeting’s status as the change left it |
events[].data.status |
"QUEUED" | "TRANSCRIBING" | "NAMING" | "GENERATING" | "READY" | "FAILED" |
Yes | Where the meeting is: QUEUED, TRANSCRIBING, NAMING (waiting for the speakers’ names), GENERATING (the Lobbe is being made), then READY; or FAILED |
events[] (type: "recording.ready") |
object |
A meeting’s Lobbe is ready | |
events[].id |
string |
Yes | The event’s own id, the same on every read: use it to skip an event you already handled. It is not a cursor (page with nextCursor) |
events[].recordingId |
string |
Yes | The id of the meeting it is about |
events[].seriesId |
string | null |
Yes | The series the meeting was in when the change happened; null when it was in none, or while series are switched off |
events[].occurredAt |
string |
Yes | When the change happened (ISO 8601, UTC); follow the feed’s order rather than sorting by it |
events[].type |
"recording.ready" |
Yes | recording.ready: the meeting’s Lobbe was made for the first time and its bundle can be read; a later regeneration is recording.updated with regenerated, never a second recording.ready |
events[].data |
object |
Yes | The meeting’s status as the change left it |
events[].data.status |
"QUEUED" | "TRANSCRIBING" | "NAMING" | "GENERATING" | "READY" | "FAILED" |
Yes | Where the meeting is: QUEUED, TRANSCRIBING, NAMING (waiting for the speakers’ names), GENERATING (the Lobbe is being made), then READY; or FAILED |
events[] (type: "recording.failed") |
object |
A meeting failed | |
events[].id |
string |
Yes | The event’s own id, the same on every read: use it to skip an event you already handled. It is not a cursor (page with nextCursor) |
events[].recordingId |
string |
Yes | The id of the meeting it is about |
events[].seriesId |
string | null |
Yes | The series the meeting was in when the change happened; null when it was in none, or while series are switched off |
events[].occurredAt |
string |
Yes | When the change happened (ISO 8601, UTC); follow the feed’s order rather than sorting by it |
events[].type |
"recording.failed" |
Yes | recording.failed: the meeting failed for good; a failure Lobbe still retries on its own is never sent. After retryRecording the meeting moves on with no event of its own, and its next event follows |
events[].data |
object |
Yes | The meeting’s status and why it failed |
events[].data.status |
"QUEUED" | "TRANSCRIBING" | "NAMING" | "GENERATING" | "READY" | "FAILED" |
Yes | Where the meeting is: QUEUED, TRANSCRIBING, NAMING (waiting for the speakers’ names), GENERATING (the Lobbe is being made), then READY; or FAILED |
events[].data.error |
object |
Yes | Why the meeting failed |
events[].data.error.code |
string |
Yes | The failure’s code, e.g. NO_SPEECH or AUDIO_CORRUPT |
events[].data.error.retryable |
boolean |
Yes | Whether retryRecording can help: false for a meeting with no speech or damaged audio |
events[] (type: "recording.updated") |
object |
A meeting changed: a finished one, or a deleted one that came back | |
events[].id |
string |
Yes | The event’s own id, the same on every read: use it to skip an event you already handled. It is not a cursor (page with nextCursor) |
events[].recordingId |
string |
Yes | The id of the meeting it is about |
events[].seriesId |
string | null |
Yes | The series the meeting was in when the change happened; null when it was in none, or while series are switched off |
events[].occurredAt |
string |
Yes | When the change happened (ISO 8601, UTC); follow the feed’s order rather than sorting by it |
events[].type |
"recording.updated" |
Yes | recording.updated: a READY meeting changed, and data.change says how; an edit to a meeting still in progress is carried by its recording.ready. The exception is restored, which follows a recording.deleted: the meeting came back at the status it had, so its data.status can be any status |
events[].data |
object |
Yes | The meeting’s status and the kind of change |
events[].data.status |
"QUEUED" | "TRANSCRIBING" | "NAMING" | "GENERATING" | "READY" | "FAILED" |
Yes | Where the meeting is: QUEUED, TRANSCRIBING, NAMING (waiting for the speakers’ names), GENERATING (the Lobbe is being made), then READY; or FAILED |
events[].data.change |
"regenerated" | "renamed" | "transcript_edited" | "speakers_renamed" | "series_changed" | "restored" |
Yes | What kind of change, never the change itself: regenerated (the Lobbe was made again), renamed (the title changed), transcript_edited (the owner corrected the transcript), speakers_renamed (a speaker’s name changed), series_changed (the meeting moved into, out of or between series) or restored (a deleted meeting is back) |
events[] (type: "recording.deleted") |
object |
A meeting was deleted | |
events[].id |
string |
Yes | The event’s own id, the same on every read: use it to skip an event you already handled. It is not a cursor (page with nextCursor) |
events[].recordingId |
string |
Yes | The id of the meeting it is about |
events[].seriesId |
string | null |
Yes | The series the meeting was in when the change happened; null when it was in none, or while series are switched off |
events[].occurredAt |
string |
Yes | When the change happened (ISO 8601, UTC); follow the feed’s order rather than sorting by it |
events[].type |
"recording.deleted" |
Yes | recording.deleted: the meeting was deleted, at any status; its bundle answers 404 from then on, unless it is restored |
events[].data |
object |
Yes | The meeting’s status as the change left it |
events[].data.status |
"QUEUED" | "TRANSCRIBING" | "NAMING" | "GENERATING" | "READY" | "FAILED" |
Yes | Where the meeting is: QUEUED, TRANSCRIBING, NAMING (waiting for the speakers’ names), GENERATING (the Lobbe is being made), then READY; or FAILED |
nextCursor |
string |
Yes | Always set: pass it back as after for what happens next. A page that has caught up gets a cursor at now, so polling an account with nothing new never ages into 410 |
Errors answer { error: { code, message, details? } }, and code is the contract: see Errors.