{"openapi":"3.1.1","info":{"title":"Greenroom read-only API","version":"v1","summary":"Server-to-server reads of one event's proposals, speakers, and published program.","description":"Greenroom's `/api/v1/*` surface is read-only, event-scoped, and gated by a server-side\nAPI key. It exists so another system can mirror a conference program; it is not the\napplication's own API, and it can neither create nor modify anything.\n\nTwo kinds of key are accepted, and they differ in reach. The deployment-wide\n`GREENROOM_API_KEY` is set by the operator in the server environment and may address any\nevent on the deployment. A per-event key is issued by an event's organizer from that\nevent's settings, looks like `grk_<id>_<secret>`, and may address that one event and\nnothing else — presenting it with any other `event` selector is refused exactly as an\ninvalid key is. Either kind is sent the same way, in either header below.\n\nThe `<id>` in the middle of a per-event key is not secret: it is how the server finds\nwhich key you presented, and it is what your event's settings page shows you in the\nlist. The `<secret>` after it is the part that authenticates, and the server keeps only\na one-way hash of it — nobody, including the organizer who created it, can read a key\nback after it is issued.\n\nEvery request needs a credential, and every request without an accepted one is\n`401 UNAUTHORIZED`. There is no separate \"this server is not configured\" answer: a\ndeployment can be fully set up with nothing but per-event keys, so a missing or wrong\ncredential is an authentication failure and is reported as one. A deployment that has\nconfigured neither a `GREENROOM_API_KEY` (one shorter than 32 characters is ignored\nentirely, as though unset) nor any per-event key simply refuses every request, because\nno credential can be accepted — it still exposes no program data.\n\nA presented credential is always checked: against the deployment-wide key when one is\nset, and against this deployment's issued per-event keys either way. An organizer's\nper-event key therefore works whether or not the operator has set a global one.\n\nChecking the deployment-wide key costs no database read. Checking a per-event key costs\nexactly one indexed lookup of the key itself, which reaches no event and no program\ndata; the event named by `event` is only ever read after a credential has been accepted,\nand for a per-event key only ever within that key's own event.\n\nEvery refusal of a credential is the same `401 UNAUTHORIZED`: an unknown key, a revoked\nkey, and a valid per-event key aimed at somebody else's event are deliberately\nindistinguishable, and none of them reveals whether the event named actually exists.\n\nThis document is itself unauthenticated: it describes the contract and contains no\nprogram data and no credential. Every credential in every example below is the literal\nplaceholder `replace-with-at-least-32-random-characters` — the deployment-wide key is not event-scoped, so\npublishing it would make every event this API can address public. Ask the deployment's\noperator for a real one, or your event's organizer for a per-event one.","license":{"name":"AGPL-3.0-only","identifier":"AGPL-3.0-only"}},"servers":[{"url":"https://your-app.example","description":"Replace with your own deployment's origin."}],"tags":[{"name":"Submissions","description":"Proposals submitted to this event's calls for papers."},{"name":"Speakers","description":"People reachable through this event's published program."},{"name":"Schedule","description":"Placed, published sessions with their slot, room, and track."},{"name":"Meta","description":"The contract itself. No key required."}],"security":[{"bearerApiKey":[]},{"apiKeyHeader":[]}],"paths":{"/api/v1/submissions":{"get":{"operationId":"listSubmissions","summary":"List this event's proposals","description":"Every proposal belonging to the selected event, with its form, category, speaker\nroster, and the answers given to that form's fields.\n\nReview data is never part of this payload: assignments, scores, rubric criteria, and\nprivate reviewer comments have no field here and cannot be reached through it.\n\nBrowse reads order by `createdAt`, `id` and may narrow with one lifecycle status.","tags":["Submissions"],"security":[{"bearerApiKey":[]},{"apiKeyHeader":[]}],"x-ordering":["createdAt","id"],"parameters":[{"$ref":"#/components/parameters/EventSelector"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"$ref":"#/components/parameters/SubmissionStatus"}],"responses":{"200":{"description":"A stably ordered page of results for the selected event.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmissionListEnvelope"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/EventNotFound"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/v1/submissions/{submissionId}":{"get":{"operationId":"getSubmission","summary":"Get one event-scoped proposal","description":"Returns exactly the proposal projection used by the submissions list.\nReview assignments, scores, rubric criteria, and private reviewer comments are excluded.\nThe authenticated event predicate is applied to the id lookup; a missing or cross-event id\nreturns the same `404 SUBMISSION_NOT_FOUND`.","tags":["Submissions"],"security":[{"bearerApiKey":[]},{"apiKeyHeader":[]}],"parameters":[{"$ref":"#/components/parameters/EventSelector"},{"$ref":"#/components/parameters/SubmissionId"}],"responses":{"200":{"description":"The proposal for the selected event.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmissionItemEnvelope"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/SubmissionNotFound"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/v1/speakers":{"get":{"operationId":"listSpeakers","summary":"List this event's speakers","description":"People reached only through this event's own abstract or session speaker relations —\nthe global user table and event memberships are not a source here, so an organizer or\nan evaluator never appears as a speaker.\n\nA held-back talk does not announce its speaker. A session qualifies its speakers only\nwhile it is published, and a proposal qualifies its speakers only when it has no linked\nsession yet or that session is published. `appearances` counts the same event-local,\npublication-filtered relations, so it never hints at a withheld talk.\n\nOrdered by `name` ascending with `id` ascending as the tiebreaker.","tags":["Speakers"],"security":[{"bearerApiKey":[]},{"apiKeyHeader":[]}],"x-ordering":["name","id"],"parameters":[{"$ref":"#/components/parameters/EventSelector"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"A stably ordered page of results for the selected event.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeakerListEnvelope"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/EventNotFound"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/v1/schedule":{"get":{"operationId":"listSchedule","summary":"List this event's published, placed sessions","description":"One entry per placement: a slot with its room, optional track, and the session that\nsits in it. Because the record read is the placement itself, backlog and unplaced\nsessions cannot appear.\n\nOnly published sessions are returned. The same filter feeds the page and the total, so\n`meta.pagination.total` never advertises rows the page will not hand back.\n\nOrdered by `startsAt` ascending with `id` ascending as the tiebreaker.","tags":["Schedule"],"security":[{"bearerApiKey":[]},{"apiKeyHeader":[]}],"x-ordering":["startsAt","id"],"parameters":[{"$ref":"#/components/parameters/EventSelector"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"A stably ordered page of results for the selected event.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScheduleListEnvelope"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/EventNotFound"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/v1/openapi.json":{"get":{"operationId":"getOpenApiDocument","summary":"This document","description":"Returns this OpenAPI 3.1.1 document as JSON.\n\nDeliberately unauthenticated, and the one route on this surface that is. It performs\nno database read and returns no program data, so requiring the key would only make\nthe contract unreadable to the integrator deciding whether to ask for one.","tags":["Meta"],"security":[],"responses":{"200":{"description":"The OpenAPI document describing this API.","content":{"application/json":{"schema":{"type":"object"}}}}}}}},"components":{"securitySchemes":{"bearerApiKey":{"type":"http","scheme":"bearer","description":"`Authorization: Bearer replace-with-at-least-32-random-characters`\n\nCarries either accepted key: the deployment-wide `GREENROOM_API_KEY`, or an event's\nown `grk_<id>_<secret>` key, which reaches only that event.\n\nChecked first: when both headers are sent, a malformed `Authorization` value is a\nfailure rather than something an unrelated `X-API-Key` can bypass."},"apiKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key","description":"`X-API-Key: replace-with-at-least-32-random-characters`\n\nCarries either accepted key, exactly as the Bearer scheme does.\n\nRead only when no `Authorization` header is present."}},"parameters":{"EventSelector":{"name":"event","in":"query","required":true,"description":"The event's slug or id. Required on every request — this API has no implicit\ncurrent event and never spans events.\n\nWith the deployment-wide key, an unknown value is `404 EVENT_NOT_FOUND`. With a\nper-event key, anything other than that key's own event is `401 UNAUTHORIZED`,\nwhether or not such an event exists: a per-event key cannot be used to discover\nwhich other events this deployment hosts.","schema":{"type":"string","minLength":1,"maxLength":191},"example":"forward-2026"},"Limit":{"name":"limit","in":"query","required":false,"description":"Rows per page. Must be a whole number from 1 to 100; anything else is `400 INVALID_QUERY`.","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},"Offset":{"name":"offset","in":"query","required":false,"description":"Rows to skip. Must be a whole number from 0 to 1000000; anything else is `400 INVALID_QUERY`.","schema":{"type":"integer","minimum":0,"maximum":1000000,"default":0}},"SubmissionStatus":{"name":"status","in":"query","required":false,"description":"One exact lifecycle status that narrows the existing event-scoped submissions browse read.","schema":{"type":"string","enum":["DRAFT","SUBMITTED","UNDER_REVIEW","MAYBE","ACCEPTED","REJECTED","WITHDRAWN"]}},"SubmissionId":{"name":"submissionId","in":"path","required":true,"description":"Proposal id. It is matched with the selected, authenticated event, so another event's id is indistinguishable from a missing one.","schema":{"type":"string","minLength":1,"maxLength":191}}},"responses":{"BadRequest":{"description":"The query is missing `event` (`EVENT_REQUIRED`) or a parameter is out of bounds (`INVALID_QUERY`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"examples":[{"version":"v1","data":null,"error":{"code":"EVENT_REQUIRED","message":"Query parameter 'event' is required."},"meta":null}]}}},"Unauthorized":{"description":"The only authentication failure this surface reports. One refusal covers every cause: no key sent at all, a value that is not shaped like a key, a key matching neither the deployment-wide key nor any issued per-event key, a per-event key that has been revoked, and a valid per-event key used against an event other than its own. It is also the answer when this deployment has configured no credential of either kind — the surface is reachable, so it authenticates rather than claiming to be unconfigured. Both comparisons are constant-time over fixed-size hashes, so a failure discloses neither the secret's length nor how much of it was right — and never whether the event named exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"examples":[{"version":"v1","data":null,"error":{"code":"UNAUTHORIZED","message":"A valid API key is required."},"meta":null}]}}},"EventNotFound":{"description":"No event has that slug or id. Returned after authentication, so it is not an unauthenticated probe for which events exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"examples":[{"version":"v1","data":null,"error":{"code":"EVENT_NOT_FOUND","message":"Event not found."},"meta":null}]}}},"SubmissionNotFound":{"description":"No proposal with that id belongs to the selected event. This also covers a cross-event id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"examples":[{"version":"v1","data":null,"error":{"code":"SUBMISSION_NOT_FOUND","message":"Submission not found."},"meta":null}]}}},"InternalError":{"description":"An unexpected failure, reported inside this envelope. The message is fixed: database text and request details are never returned to a caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"examples":[{"version":"v1","data":null,"error":{"code":"INTERNAL_ERROR","message":"Something went wrong."},"meta":null}]}}}},"schemas":{"ErrorEnvelope":{"type":"object","description":"Every failure on this surface, in the same four top-level fields as a success.","additionalProperties":false,"required":["version","data","error","meta"],"properties":{"version":{"const":"v1"},"data":{"type":"null"},"error":{"$ref":"#/components/schemas/ErrorBody"},"meta":{"type":"null"}}},"ErrorBody":{"type":"object","description":"A stable machine code and a message safe to show a human.","additionalProperties":false,"required":["code","message"],"properties":{"code":{"type":"string","enum":["EVENT_REQUIRED","INVALID_QUERY","UNAUTHORIZED","EVENT_NOT_FOUND","SUBMISSION_NOT_FOUND","INTERNAL_ERROR"]},"message":{"type":"string"}}},"ListMeta":{"type":"object","additionalProperties":false,"required":["event","pagination"],"properties":{"event":{"$ref":"#/components/schemas/EventMeta"},"pagination":{"$ref":"#/components/schemas/Pagination"}}},"ItemMeta":{"type":"object","additionalProperties":false,"required":["event"],"properties":{"event":{"$ref":"#/components/schemas/EventMeta"}}},"EventMeta":{"type":"object","description":"The event the request resolved to, echoed so a caller can confirm the selector.","additionalProperties":false,"required":["id","name","slug","timezone"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"timezone":{"type":"string","description":"IANA timezone name. Timestamps in `data` are UTC ISO-8601 regardless."}},"examples":[{"id":"clx0event0000000000000000","name":"Forward 2026","slug":"forward-2026","timezone":"UTC"}]},"Pagination":{"type":"object","description":"Offset pagination. `total` counts every row matching the same filter as the page.","additionalProperties":false,"required":["limit","offset","total","hasMore","nextOffset"],"properties":{"limit":{"type":"integer","minimum":1,"maximum":100},"offset":{"type":"integer","minimum":0,"maximum":1000000},"total":{"type":"integer","minimum":0},"hasMore":{"type":"boolean","description":"True when `offset + limit` is below `total`."},"nextOffset":{"type":["integer","null"],"description":"The `offset` to request next, or null on the last page."}},"examples":[{"limit":50,"offset":0,"total":2,"hasMore":false,"nextOffset":null}]},"SpeakerRef":{"type":"object","description":"A person on a proposal's or a session's roster. Primary speakers sort first.","additionalProperties":false,"required":["id","name","email","avatarUrl","isPrimary"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string","format":"email"},"avatarUrl":{"type":["string","null"]},"isPrimary":{"type":"boolean"}},"examples":[{"id":"clx0user00000000000000001","name":"Ada Lovelace","email":"ada.lovelace@example.test","avatarUrl":null,"isPrimary":true}]},"Submission":{"type":"object","description":"One proposal. Carries no review, score, or reviewer-comment field.","additionalProperties":false,"required":["id","title","description","format","durationMinutes","status","submittedAt","createdAt","updatedAt","form","category","speakers","answers"],"properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":["string","null"],"description":"The proposal's abstract body."},"format":{"type":["string","null"]},"durationMinutes":{"type":["integer","null"]},"status":{"type":"string","description":"Lifecycle status, e.g. `DRAFT`, `SUBMITTED`, `UNDER_REVIEW`, `ACCEPTED`, `REJECTED`, `WITHDRAWN`."},"submittedAt":{"type":["string","null"],"format":"date-time","description":"Null while the proposal is still a draft."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"form":{"$ref":"#/components/schemas/SubmissionForm"},"category":{"oneOf":[{"$ref":"#/components/schemas/SubmissionCategory"},{"type":"null"}]},"speakers":{"type":"array","items":{"$ref":"#/components/schemas/SpeakerRef"}},"answers":{"type":"object","description":"Answers keyed by form-field key. Values are whatever that field type stores, so this map is intentionally open.","additionalProperties":true,"examples":[{"audience-level":"intermediate","needs-av":true}]}}},"SubmissionForm":{"type":"object","description":"The call for papers this proposal was submitted to.","additionalProperties":false,"required":["id","name","slug"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"}}},"SubmissionCategory":{"type":"object","description":"The event-owned category chosen on the proposal. Null when none was chosen.","additionalProperties":false,"required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"}}},"Speaker":{"type":"object","description":"A person on this event's published program, with event-local appearance counts.","additionalProperties":false,"required":["id","name","email","avatarUrl","profile","appearances"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string","format":"email"},"avatarUrl":{"type":["string","null"]},"profile":{"oneOf":[{"$ref":"#/components/schemas/SpeakerProfile"},{"type":"null"}],"description":"Null when the person has never filled in a speaker profile."},"appearances":{"$ref":"#/components/schemas/SpeakerAppearances"}}},"SpeakerProfile":{"type":"object","description":"The person's one global profile. `slideDeckUrl` is not per-event today.","additionalProperties":false,"required":["bio","company","jobTitle","headshotUrl","slideDeckUrl","socialLinks"],"properties":{"bio":{"type":["string","null"]},"company":{"type":["string","null"]},"jobTitle":{"type":["string","null"]},"headshotUrl":{"type":["string","null"]},"slideDeckUrl":{"type":["string","null"]},"socialLinks":{"description":"Free-form, author-supplied link map, or null. Treat every value as untrusted text.","additionalProperties":true,"examples":[{"mastodon":"https://example.test/@ada"}]}}},"SpeakerAppearances":{"type":"object","description":"Counts within this event only, under the same publication filter as the list itself.","additionalProperties":false,"required":["submissions","sessions"],"properties":{"submissions":{"type":"integer","minimum":0},"sessions":{"type":"integer","minimum":0}}},"ScheduleSlot":{"type":"object","description":"A placement: when and where, plus the published session that sits there.","additionalProperties":false,"required":["id","startsAt","endsAt","room","track","session"],"properties":{"id":{"type":"string","description":"The slot's id, not the session's."},"startsAt":{"type":"string","format":"date-time"},"endsAt":{"type":"string","format":"date-time"},"room":{"$ref":"#/components/schemas/Room"},"track":{"oneOf":[{"$ref":"#/components/schemas/Track"},{"type":"null"}]},"session":{"$ref":"#/components/schemas/Session"}}},"Room":{"type":"object","additionalProperties":false,"required":["id","name","capacity"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"capacity":{"type":["integer","null"]}}},"Track":{"type":"object","additionalProperties":false,"required":["id","name","color"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"color":{"type":"string","description":"CSS colour string as the organizer set it."}}},"Session":{"type":"object","description":"The published talk in this slot.","additionalProperties":false,"required":["id","title","description","format","durationMinutes","speakers"],"properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":["string","null"]},"format":{"type":["string","null"]},"durationMinutes":{"type":"integer"},"speakers":{"type":"array","items":{"$ref":"#/components/schemas/SpeakerRef"}}}},"SubmissionListEnvelope":{"type":"object","description":"A page of proposals.","additionalProperties":false,"required":["version","data","error","meta"],"properties":{"version":{"const":"v1","description":"Always the string `v1`."},"data":{"type":"array","items":{"$ref":"#/components/schemas/Submission"}},"error":{"type":"null","description":"Always null on a 2xx response."},"meta":{"$ref":"#/components/schemas/ListMeta"}},"examples":[{"version":"v1","data":[{"id":"clx0abstract00000000000001","title":"Scheduling a conference without losing a room","description":"What placement conflicts actually cost, and how to see them early.","format":"TALK","durationMinutes":30,"status":"ACCEPTED","submittedAt":"2026-01-14T09:12:00.000Z","createdAt":"2026-01-12T17:40:00.000Z","updatedAt":"2026-02-02T11:05:00.000Z","form":{"id":"clx0form00000000000000001","name":"Call for speakers","slug":"call-for-speakers"},"category":{"id":"clx0cat000000000000000001","name":"Operations"},"speakers":[{"id":"clx0user00000000000000001","name":"Ada Lovelace","email":"ada.lovelace@example.test","avatarUrl":null,"isPrimary":true}],"answers":{"audience-level":"intermediate","needs-av":true}}],"error":null,"meta":{"event":{"id":"clx0event0000000000000000","name":"Forward 2026","slug":"forward-2026","timezone":"UTC"},"pagination":{"limit":50,"offset":0,"total":2,"hasMore":false,"nextOffset":null}}}]},"SubmissionItemEnvelope":{"type":"object","description":"One event-scoped proposal.","additionalProperties":false,"required":["version","data","error","meta"],"properties":{"version":{"const":"v1","description":"Always the string `v1`."},"data":{"$ref":"#/components/schemas/Submission"},"error":{"type":"null","description":"Always null on a 2xx response."},"meta":{"$ref":"#/components/schemas/ItemMeta"}},"examples":[{"version":"v1","data":{"id":"clx0abstract00000000000001","title":"Scheduling a conference without losing a room","description":"What placement conflicts actually cost, and how to see them early.","format":"TALK","durationMinutes":30,"status":"ACCEPTED","submittedAt":"2026-01-14T09:12:00.000Z","createdAt":"2026-01-12T17:40:00.000Z","updatedAt":"2026-02-02T11:05:00.000Z","form":{"id":"clx0form00000000000000001","name":"Call for speakers","slug":"call-for-speakers"},"category":{"id":"clx0cat000000000000000001","name":"Operations"},"speakers":[{"id":"clx0user00000000000000001","name":"Ada Lovelace","email":"ada.lovelace@example.test","avatarUrl":null,"isPrimary":true}],"answers":{"audience-level":"intermediate","needs-av":true}},"error":null,"meta":{"event":{"id":"clx0event0000000000000000","name":"Forward 2026","slug":"forward-2026","timezone":"UTC"}}}]},"SpeakerListEnvelope":{"type":"object","description":"A page of speakers.","additionalProperties":false,"required":["version","data","error","meta"],"properties":{"version":{"const":"v1","description":"Always the string `v1`."},"data":{"type":"array","items":{"$ref":"#/components/schemas/Speaker"}},"error":{"type":"null","description":"Always null on a 2xx response."},"meta":{"$ref":"#/components/schemas/ListMeta"}},"examples":[{"version":"v1","data":[{"id":"clx0user00000000000000001","name":"Ada Lovelace","email":"ada.lovelace@example.test","avatarUrl":null,"profile":{"bio":"Works on program operations.","company":"Analytical Engines","jobTitle":"Principal Engineer","headshotUrl":null,"slideDeckUrl":null,"socialLinks":{"mastodon":"https://example.test/@ada"}},"appearances":{"submissions":1,"sessions":1}}],"error":null,"meta":{"event":{"id":"clx0event0000000000000000","name":"Forward 2026","slug":"forward-2026","timezone":"UTC"},"pagination":{"limit":50,"offset":0,"total":2,"hasMore":false,"nextOffset":null}}}]},"ScheduleListEnvelope":{"type":"object","description":"A page of placements.","additionalProperties":false,"required":["version","data","error","meta"],"properties":{"version":{"const":"v1","description":"Always the string `v1`."},"data":{"type":"array","items":{"$ref":"#/components/schemas/ScheduleSlot"}},"error":{"type":"null","description":"Always null on a 2xx response."},"meta":{"$ref":"#/components/schemas/ListMeta"}},"examples":[{"version":"v1","data":[{"id":"clx0slot00000000000000001","startsAt":"2026-05-14T09:00:00.000Z","endsAt":"2026-05-14T09:30:00.000Z","room":{"id":"clx0room00000000000000001","name":"Main Hall","capacity":400},"track":{"id":"clx0track0000000000000001","name":"Operations","color":"#167565"},"session":{"id":"clx0session000000000000001","title":"Scheduling a conference without losing a room","description":"What placement conflicts actually cost, and how to see them early.","format":"TALK","durationMinutes":30,"speakers":[{"id":"clx0user00000000000000001","name":"Ada Lovelace","email":"ada.lovelace@example.test","avatarUrl":null,"isPrimary":true}]}}],"error":null,"meta":{"event":{"id":"clx0event0000000000000000","name":"Forward 2026","slug":"forward-2026","timezone":"UTC"},"pagination":{"limit":50,"offset":0,"total":2,"hasMore":false,"nextOffset":null}}}]}}}}