API tokens created before scoped tokens were introduced continue to work with full access when no scopes are set. New tokens should request only the scopes your integration needs — see Authentication and OAuth.
October 2026
October 10 — When a participant’s acceptance invitation was sent
Additive. Nothing existing changes shape.acceptance_invite_sent_aton sessionparticipants. On events with Participant Acceptance turned on, each entry in a session’sparticipantsnow says when that role’s acceptance invitation was last sent, ornullif it hasn’t been sent. It sits next toacceptance_status, which is now documented too. The legacyspeakers,chairpersonsandmoderatorsarrays don’t carry it.
This arrives on the production hosts (
public-api.sessionboard.com, public-api-eu.sessionboard.com) with the next Public API release.October 10 — Contact writes accept about, salutation and address
Additive. Nothing existing changes shape. Added — more standard fields on contact writes.PUT /v1/event/{eventId}/contacts/{contactId}, POST /v1/event/{eventId}/contacts/create and bulk contact writes now save about, salutation, honorific, address_line_1, address_line_2, address_city, address_state, address_postal_code and address_country, using the names GET returns. These keys were previously dropped without an error. address_country accepts the numeric code GET returns, an alpha-2/alpha-3 code or the English name; an unknown value returns 400. Write responses now echo the stored values, including title.
Added — reason on the log export 403. A log export token calling any non-log endpoint gets "reason": "log_export_only" in the 403 body.
This arrives on the production hosts (
public-api.sessionboard.com, public-api-eu.sessionboard.com) with the next Public API release.October 10 — API tokens stop working when their creator leaves the organization
Changed — a token follows its creator’s membership. Requests with an API token (x-access-token) whose creator has been removed from the organization or set to Inactive now return 401, and appear in /v1/logs/api-requests with the reason owner_inactive. Reactivating or re-adding the person restores their tokens. OAuth sessions already ended this way. If an integration runs on a departing colleague’s token, replace it with one created under an account that stays.
This arrives on the production hosts (
public-api.sessionboard.com, public-api-eu.sessionboard.com) with the next Public API release.October 10 — Log export: the stream values
Documentation only. Nothing about the API changed.
Corrected — stream on a log event. It is api_request on /v1/logs/api-requests, security_event on /v1/logs/security-events and audit on /v1/logs/audit-trail. The SIEM guide example and the LogEvent schema showed the URL segment (audit-trail) instead, which a SIEM filter would never match.
October 8 — Session files can be uploaded into an upload slot
Additive. Nothing existing changes shape.content_slot_idonPOST /v1/event/{eventId}/sessions/{sessionId}/filesand.../files/upload. Name the upload slot a file answers (Poster, Presentation, Handout…). The slot must be active, apply to the session, accept the file’s declared type and have room under itsmax_files; otherwise400names the slot. The file’stypecomes from the slot. Optional — API callers act as the organization, so no slot deadline applies and a file may still be attached without a slot. Ignored on events that use a single file request.session_idfilter onGET /v1/event/{eventId}/content-slots. Returns only the active slots that apply to that session, in speaker order — the list to pick acontent_slot_idfrom.404if the session is not on the event.
October 7 — Awards: programs, categories, rounds, promo codes, submissions and reviews
Additive. Nothing existing changes shape. New — manage Awards through the API. Awards are organization-level, so every path starts at/v1/organization/{orgId}/awards:
- Programs — list, get, create, update, delete. Creating a program sets up its submission form and first round, as the admin app does, and counts against your plan’s program limit.
- Categories and subcategories, rounds, and promo codes under
/programs/{programId}/...— list, get, create, update, delete. - Submissions — list (filter by
status,category_id,current_round_id), get with form answers, update answers, delete. Submissions are still created by submitters in the program’s portal. - Reviews — list and get each reviewer’s assignment and scores on a submission, and remove a reviewer. Scores are still entered by reviewers in the review portal.
PUT and PATCH both update only the fields you send. Deletes are soft: the record goes to Trash and can be restored in the admin app. Where the admin app asks you to confirm — a program with submissions, a round with reviewer assignments, a promo code already used, a category with submissions — the API answers 409 until you send force=true. A category with subcategories also needs cascade=true. The default round can’t be deleted (422), promo codes must be unique within a program (409), and a percentage code can’t exceed 100 (400).
New — the read:awards and write:awards scopes. A token whose scopes don’t include them gets 403; a legacy token with no scopes can read but not write. Both scopes are also offered over OAuth. Another organization’s id anywhere in the path returns 404. See Authentication.
These endpoints and scopes arrive on the production hosts (
public-api.sessionboard.com, public-api-eu.sessionboard.com) with the next Public API release; until then they answer 404.October 7 — MCP connection: OAuth, not a token switch
Documentation only. Nothing about the API changed. Corrected — assistants sign in with OAuth. Claude, ChatGPT, Copilot, and Gemini connect to the hosted MCP server by signing in. There is no MCP switch on the organization or on an API token, and an API token pasted into the assistant does not authenticate. The person signing in needs AI features on for the organization and the Connect AI assistants permission. Corrected —read:insights is a scope, not a toggle. A script calling /v1/insights/... with x-access-token needs read:insights in the token’s scopes. A token with an empty scope list is the legacy full-access case. The MCP Server page, Reports & Dashboards, and OAuth now say this. OAuth also advertises the write scopes (write:sessions, write:contacts, and the rest of the authorization-server list); the old note that OAuth was read-only was wrong.
The Help Center walkthrough is at Connect AI assistants.
October 6 — Log export for SIEMs: GET /v1/logs/{stream}
Additive. Nothing existing changes shape.
New — pull your logs into Splunk, Microsoft Sentinel or Elastic. Three endpoints return your organization’s logs as pages of events in one shared shape: /v1/logs/api-requests (every request made with your API tokens to this API, the MCP server and Insights, kept 365 days), /v1/logs/security-events (sign-ins, failed sign-ins, two-factor and passkey events, password resets) and /v1/logs/audit-trail (every recorded change to your data). Each page returns a next_cursor; send it back on the next call and no event is missed or repeated, even one recorded late. 60 requests per minute per token. See Exporting logs to a SIEM for Splunk, Sentinel and Elastic setups.
New — the read:audit_logs scope, on a dedicated token. Log export needs an API token holding read:audit_logs and no other scope. Only users who can manage users can create one; it always covers the whole organization, and it can’t call any other endpoint (403). OAuth tokens can’t use log export.
Changed — failed authentication is logged. Requests rejected for a missing, invalid, revoked or expired key, an archived organization, an inactive member, disabled AI features, or an event belonging to another organization are now recorded, with the reason, and appear in /v1/logs/api-requests as outcome: "failure". Responses to those requests are unchanged.
These endpoints and the scope arrive on the production hosts (
public-api.sessionboard.com, public-api-eu.sessionboard.com) with the next Public API release; until then they answer 404.October 5 — OAuth tokens stop working when the user leaves the organization
Changed — an OAuth (MCP / AI assistant) token now ends with its user’s membership. When the user who authorized the connection is deactivated in or removed from the organization, requests with their Bearer token return401 with “User is no longer an active member of this organization”, and refreshing the token fails with invalid_grant. Previously the token kept working until it expired. Event-only users with AI Access are unaffected while they remain active. API tokens sent with x-access-token are organization credentials and keep working unchanged. See OAuth → Permission Model.
This takes effect on the production hosts with the next Public API release.
October 2 — Upload slots: GET /v1/event/{eventId}/content-slots and content_slot_id on session files
Additive. Nothing existing changes shape.
New — list the files an event asks its speakers for. Organizers can now define upload slots under Program → Settings → Files — one per ask (Poster, Presentation, Handout, …), each with its own due_date, due_date_extended, allowed_file_types, min_files / max_files, required flag and order. GET /v1/event/{eventId}/content-slots returns the active slots in the order speakers see them; ?include_archived=true adds retired ones (is_active: false). Requires read:sessions. Which sessions a slot applies to, and which roles may upload, are organizer-side configuration and are not exposed. See List upload slots.
New — content_slot_id on every session file. GET /v1/event/{eventId}/sessions/{sessionId}/files now includes content_slot_id, the slot the file answers, so a poster is distinguishable from a deck without inspecting filenames or MIME types. It is null for files uploaded before slots existed or outside any slot.
Opt-in per event. Slots are on for events created or cloned from October 2 onwards; events created earlier stay on their single file request until an organizer turns the switch on. For those events the slot list is empty and content_slot_id is null on every file — treat an empty list as “this event does not use slots”, not as an error. Uploads through this API do not yet take a slot id.
This endpoint is live on the DEV host. The production hosts (
public-api.sessionboard.com, public-api-eu.sessionboard.com) pick it up with the next Public API release; until then they answer 404 for the route and omit content_slot_id.October 2 — MCP write tools are flagged destructive
Changed — everymanage_* tool on the MCP server now declares destructiveHint: true. Previously only tools with a delete action carried the flag; manage_session, manage_contact, manage_exhibitor, manage_sponsor and manage_metadata reported destructiveHint: false because they cannot delete. Each of them can overwrite an existing record through update, which is what the hint is meant to signal, so they now report true. Tool names, parameters, actions and responses are unchanged. In practice an AI client may ask you to confirm before one of these tools writes, where before it may have run without a prompt; read tools (list_*, read_*, get_*, query_data, execute_sbql, search, fetch) are unaffected.
September 2026
September 30 — Contact updates through the API trigger automations
Changed —PUT /v1/event/{eventId}/contacts/{contactId} now fires field-change automations. A value written through this endpoint was stored but never announced: Sessionboard automations watching that field (When → A field changes) did not run, and the change did not appear in the contact’s activity feed. The write now publishes the same field-change event an admin edit does, with the source recorded as public_api, so an automation that emails a speaker when Status becomes Confirmed fires whether the status changed in the admin or through your integration. Request and response shapes are unchanged. Because the same event feeds webhooks, a contact.updated delivery now follows an API update too; a request that changes both standard and custom fields in one call may deliver two, one per group of values, so keep webhook handling idempotent. If you have an automation that should not react to your own writes, add an Only enroll when filter on the record rather than relying on the previous silence.
September 28 — One 404 message per single-record GET
Changed — a missing record reads the same however it is missing. GET /v1/event/{eventId}/sessions/{sessionId} answered an unknown id with Session could not be found and a soft-deleted one with Session could not be found for this event. Both now answer Session not found, the message this reference documents. The speaker, participant and contact GETs by id move to their documented messages the same way: Speaker not found, Participant not found on this event, and Contact not found (event and organization). Status 404 and name: "NotFoundError" are unchanged; if you match on the message text, match on these.
September 28 — Bulk TYPE_MISMATCH lists dropdown options as an array
Changed — a rejected dropdown value names each valid option unambiguously. When a sessions, exhibitors or sponsors bulk operation rejects a value that is not one of a dropdown field’s options, the TYPE_MISMATCH error listed the options joined with commas, so an option whose own label contains a comma (Banana, Chocolate) read as two options. The message now quotes each option (Valid options: ["Banana, Chocolate", "10"]), and the error object gains valid_options, an array with one entry per option. code and the rest of the error are unchanged, and valid_options appears on no other error. See Error codes on 400.
September 27 — Filter session files by type; type and label in file responses
GET /v1/event/{eventId}/sessions/{sessionId}/files accepts ?type= — one value, a comma-separated list, or the parameter repeated — and returns only files of those types. The organizer-facing type (presentation, handout, poster, holding_slide, …) and display label are now included on every session file response, so a client can tell a generated Holding Slide from a speaker’s upload without inspecting the filename. A malformed type value answers 400; an unfiltered call is unchanged. See Session files.
September 26 — Linked composition sources report as editable
Changed —composition_status.is_read_only is now always false. Sessions linked into another record (role: "source") could already be updated through the API, but the flag still said true. It now matches the behavior. role, is_linked and target are unchanged. Deleting, withdrawing, duplicating or re-composing a linked source is still refused until it is unlinked. See Sessions & composition.
September 25 — Participants: every session role in one search, filter by role, look up by email
Nothing about existing endpoints changed.POST /v1/event/{eventId}/speakers and GET .../speakers/{contactId} return exactly what they did yesterday; everything here is new.
Added — POST /v1/event/{eventId}/participants returns every contact holding a session role. Speaker search matches a session’s speakers only. Chairpersons, moderators and custom roles (Panelist, Coach, Author, …) were reachable only by walking POST .../sessions and merging four arrays per session. Participant search returns all of them in one paginated list, each contact once, with a roles array listing every role it holds on the event and the session ids where it holds each — { roleId, roleName, slug, coreRole, isCustom, sessionIds }. Every other field is the same Contact shape speaker search returns, in the same order, with roles appended last. All speaker-search filters, sort, expand and pagination work identically, including top-level-session matching for categorization filters and the 400 for an empty filter array. See Search participants.
Added — filter by role. filters.roleIds narrows the search to participants holding at least one of the given roles, standard or custom; filters.roleSlugs does the same by slug (speaker, moderator, a custom slug) and also works on older events whose roles have roleId: null. GET /v1/event/{eventId}/participant-roles lists the event’s roles with their ids, names, core_role and is_custom, in the order the Sessions settings page shows them. See List participant roles.
Added — look up by email. Most external systems key on email, not on a Sessionboard contact id. filters.email on the search is an exact, case-insensitive match that returns an empty result for an unknown address. GET /v1/event/{eventId}/participants?email= is the single-record form: it returns the participant, or 404. Search on this API is a POST, so the GET on the collection path is unambiguous — there is no /by-email sub-path to remember. See Get a participant by email.
Added — GET /v1/event/{eventId}/participants/{contactId} returns one participant with its roles, and 404 when the contact is not on the event or holds no session role.
Roles are stored per event, and an event that has never been opened in the Sessions editor may not have its standard roles created yet. Its participants are still returned; a standard role on such an event has
roleId: null (with roleName, slug and coreRole filled in) and GET .../participant-roles returns an empty list until the roles exist.September 25 — Subsessions carry the admin-defined order
Event admins can now drag subsessions into a custom order in Sessionboard. The public API reflects that order and tells you what it is. This is an additive change — no existing field changes type or disappears. Added —subsession_order on every subsession. Each item in parent.subsessions[] — the minimal Subsession shape, the expand=subsession_details shape, the SubsessionStatus items on POST /v1/event/{eventId}/sessions/status, and the top-level body when you GET /v1/event/{eventId}/sessions/{sessionId} with a subsession id — now includes subsession_order (integer, nullable). It is the position the admin set among that parent’s subsessions, or null for subsessions of a parent that has never been reordered.
Changed — subsessions[] is returned in that order. Session search, GET by id and the status search all sort children by subsession_order, then subsession number, then start time, then creation time — the same order the Sessionboard admin, portal and agenda embeds use. Previously the session endpoints ordered children by start time alone and the status search by subsession number, so a reorder in the admin was not visible through the API and the two searches could disagree. For a parent whose subsessions have never been reordered, the array is unchanged in practice, since the backfilled order matches the previous subsession-number-then-start-time sort. If your integration relies on a specific child order, sort on subsession_order (or starts_at) client-side rather than on array position.
September 19 — custom_fields works on sessions/bulk, and creating a status needs status
Fixed — POST /v1/event/{eventId}/sessions/bulk writes custom_fields instead of discarding them. An operation carrying custom_fields was reported as "status": "success" and nothing was stored, on create and on update alike — the update path built its write from a fixed list of standard columns and never read the property at all. Values are now written on both actions. This was the last bulk endpoint with the gap: contacts/bulk, exhibitors/bulk and sponsors/bulk have always honored it. As everywhere else, an unknown internal_name, a value of the wrong type, or a field owned by an integration fails that operation only and rolls it back; the operations around it still commit.
Clarified — draft sessions do not take custom_fields. POST /v1/event/{eventId}/agenda-drafts/{draftId}/sessions/bulk and its single-record sibling are the one exception to “data takes the same fields as the matching single-record endpoint”. A draft session is a scheduling placement of an existing session inside a draft agenda, not a record of its own, so there are no custom fields on it. Set them on the underlying session through /v1/event/{eventId}/sessions.
Breaking — POST /v1/event/{eventId}/statuses/create requires color and status. This reference documented name as the only required field, which has never worked: both of the others are stored without defaults, so a minimal payload always failed. It failed as a 500, which is what made this hard to diagnose. Sending { name } alone now answers 400 naming color, and { name, color } answers 400 naming status. Nothing that succeeded before stops succeeding — a payload that omitted either field was already failing.
status is the underlying approval state a custom label maps onto, and several labels may share one. It must be pending, accepted, declined, accept_queue or decline_queue; any other value is now a 400 naming the field rather than a 500. The same enum applies to PUT /v1/event/{eventId}/statuses/{id}. The create response code is 201, not 200 as documented. The other event-settings creates — tags, tracks, levels, formats, rooms, languages and affiliations — need only name and are unchanged. See Create a session status.
September 18 — Breaking: missing records answer 404, and GDPR pagination is validated
Breaking — a session the search cannot see is a 404, not 200 with a body of null. GET /v1/event/{eventId}/sessions/{sessionId} resolved the id against a lookup that sees soft-deleted sessions, then answered from a search that does not, and returned HTTP 200 with a body of literally null. No operation in this reference has ever declared a null payload; every one of them declares 404. A soft-deleted session, and a subsession the search cannot return, now answer 404 with Session could not be found for this event. If you were checking the body for null, check the status instead.
Breaking — an id that belongs to a different resource is a 404. The same pattern ran on three more single-record GETs: GET /v1/event/{eventId}/contacts/{contactId}, GET /v1/event/{eventId}/speakers/{speakerId}, and GET /v1/organization/{orgId}/contacts/{contactId}. Asking for a sponsor or exhibitor group id as a contact, or a contact id as a speaker, returned 200 with null; it now returns 404. The sponsor and exhibitor GETs already behaved this way.
Breaking — a record that is not on the event answers 404 instead of 403. The group GETs (sponsors, exhibitors, speakers) rejected a record with no association to the event with 403, even though the same end state answered 404 when the association had been removed through this API’s own DELETE. It is now 404 in both cases, matching the single not-found response these operations document. If your integration reads 403 as “my token is wrong” and disables itself, this change stops that false alarm. (403 still means what it says: an authorization failure.)
Breaking — GET /v1/gdpr/requests validates its query parameters. ?page=0, ?pageSize=500 and ?pageSize=-1 were accepted with a 200 and silently clamped. They now return 400, as does any query parameter the operation does not declare. page is 1–999 and pageSize is 1–100; the maximum is what keeps the endpoint usable on organizations holding millions of rows, so it is a contract rather than a clamp. The spec now lists the 400 alongside 200 and 401. See GDPR requests.
Fixed — pageSize works on the session, content, and transcription lists. The shared PageSize parameter and every POST search are spelled pageSize, but these GET lists read only page_size, so the documented camelCase spelling was ignored and every response came back at the default 25. Both spellings are now accepted, and pageSize above 100 is capped at 100 on these list endpoints.
September 17 — Reference corrections: group deletes, bulk totals, and empty filter arrays
Documentation only. Nothing about the API changed — these are four places where this reference described behavior the API has never had, each of which could make a generated client or a schema validator reject a valid exchange. Corrected — deleting a sponsor or exhibitor returns204, not 200. DELETE /v1/event/{eventId}/sponsors/{sponsorId} and .../exhibitors/{exhibitorId} answer 204 with an empty body. This page documented 200 "Sponsor deleted", so a client asserting on 200 treated every successful delete as unexpected. A repeated delete of an already-deleted record is a 404.
Corrected — a group delete removes the group, not one of its roles. The path names the role you call under, but the record that is soft-deleted is the underlying group. A group holding both roles loses both, and a group linked to several events is removed from all of them — it stops being returned by any event’s sponsor or exhibitor search across your organization. This matches the admin UI, where the unit of deletion is the group; there is no role-scoped or event-scoped delete. Restore reverses it with the same reach, and now documents the 400 you get for a record that is not deleted. See Soft-delete a sponsor and Soft-delete an exhibitor.
Corrected — bulk responses carry the totals as summary. Every bulk endpoint returns { batch_id, results, summary }; this page called the totals object stats. summary.succeeded counts every row whose status is not error.
Corrected — the categorization filter arrays need at least one ID. trackIds, levelIds, formatIds, languageIds, tagIds and sessionIds are rejected with a 400 naming the field when sent as [], on session search, session status search and speaker search alike. The schemas now declare minItems: 1, so a validator catches it locally. To apply no filter, omit the property — filters: {} and an absent filters both return the full unfiltered set.
Advisory — fetch the spec files unconditionally. /api-reference/openapi.json and /api-reference/openapi.yaml are served by this documentation site with the same ETag and Last-Modified, so a conditional request for one carrying the other’s validator returns 304 and a cache can hand back the wrong format. If you regenerate a client in CI, send no If-None-Match or If-Modified-Since on these two URLs. An unconditional fetch is always correct. See Fetching the OpenAPI spec.
September 17 — GDPR request outcomes, bulk contacts takes the documented body, and pageSize works on event settings
Changed — POST /v1/gdpr/requests records the request and returns the erasure outcome. The call processes the erasure against your organization’s data and returns the stored request, including the id to quote as evidence and a response object listing what was matched, scrubbed, or retained. Read status: complete means at least one record matched and was processed; no_records_found means nothing in your organization matched that email and nothing was erased — escalate rather than treating it as fulfilled. A 502 means the erasure did not run and nothing was recorded; retry. The documented success code is 200, not 201.
Changed — GET /v1/gdpr/requests returns request history, newest first. It is a bare JSON array (the reference previously showed a results envelope) and is paginated with page and pageSize (default 25, maximum 100). See GDPR requests.
Fixed — POST /v1/event/{eventId}/contacts/bulk accepts the documented { action, id, data } body. Every operation previously failed with email is required, including update and delete, because the endpoint read contact fields off the operation itself and never looked at data. The documented envelope now works for all three actions. The flat form ({ "email": …, "first_name": … } with no action) is still accepted as a create for existing integrations, and is deprecated.
Fixed — a failed bulk contact operation leaves nothing behind. Operations previously shared one transaction and ran ten at a time, so a row reported as error could still have committed part of its writes, duplicate-email detection was unreliable inside a batch, and results did not follow the request order. Each operation now runs in its own transaction, in request order, so results[i] always describes operations[i] and a rejected operation is rolled back whole.
Fixed — bulk endpoints write translated_fields instead of discarding them. contacts/bulk, sessions/bulk, exhibitors/bulk and sponsors/bulk accepted translated_fields in an operation, reported success, and stored nothing. Variants are now written, and a variant for a language the event has not enabled fails that operation and rolls it back — the same validation the single-record endpoints have had since 13 September.
Fixed — event-settings lists honor pageSize. GET on tags, tracks, levels, formats, rooms, languages, and statuses read only the undocumented page_size, so a request sending the documented pageSize silently got 25 results. Both spellings now work and pageSize wins when both are sent; a value outside 1–100 is clamped on these endpoints rather than rejected. Results also page stably now — rows created in the same write share a timestamp, and without a tiebreaker a row could repeat on one page and be missing from the next.
Fixed — reference: metadata and field deletes return 204. DELETE on tags, tracks, levels, formats, rooms, languages, statuses and custom fields was documented as 200. All return 204 No Content with no body, and always have.
Fixed — reference: the event-settings lists are paginated. GET on languages, formats, tracks, levels, rooms and statuses was documented as returning every record as a bare array. Each returns { "data": [...], "pagination": {...} } with snake_case pagination keys (current_page, page_size, total_pages, total_results) — unchanged behavior, now described correctly.
September 16 — Group roles, integration-owned fields, and a reverted checkbox change
Fixed — contacts no longer appear in sponsor and exhibitor search.POST /v1/event/{eventId}/sponsors and .../exhibitors returned contact records alongside real groups, as unnamed rows with an ACCT- id, and GET /v1/event/{eventId}/sponsors/{contactId} answered with a sponsor-shaped body. Both endpoints now return only account records that carry the role on that event.
Fixed — a group holding both roles is visible under both. One account can be a sponsor and an exhibitor on the same event. Linking the second role (POST .../exhibitors/create with the account_id of an existing sponsor, or the reverse) reported success but the role stayed invisible: the role’s search omitted it, GET by id returned 200 with a body of null, and PUT, DELETE and restore addressed to that role failed. The second role is now returned by search and GET, and is writable. Bulk create links a second role too. See Groups: sponsors and exhibitors.
Changed — GET for a role a record does not hold is now 404. Previously such a request returned 200 with a null body — a contact id, or a group linked only as the other role. It now returns 404, as the reference always documented.
Changed — writes to integration-owned custom fields are rejected. Fields hydrated by a connector (swoogo_session_id, cvent_session_id, visit_session_id) are read-only. Including one in custom_fields — or in values on PUT /v1/event/{eventId}/sessions/{sessionId}/fields — previously returned 200 and discarded the value silently. It now returns 400 with error code FIELD_READ_ONLY and the field in details.field, and no other field in that request is written. Omit these fields from your payload; their values stay readable. See Custom fields.
Fixed — reference: sponsor and exhibitor create return 201, and the session fields endpoint takes values. The reference documented 200 for both /create endpoints, and documented a custom_fields object for PUT /v1/event/{eventId}/sessions/{sessionId}/fields when that endpoint takes a values array of { internal_name, value, language? } and responds with the fields it wrote rather than the whole session.
September 14 — Categorization filters on session status search
Added —POST /v1/event/{eventId}/sessions/status accepts the five categorization filters (trackIds, tagIds, levelIds, formatIds, languageIds), the same camelCase body arrays as session search and speaker search. IDs within one filter match with OR; separate filters combine with AND. Filters are evaluated against the top-level session, so a subsession row matches via its parent — and unlike the other session searches, a deleted root session still matches, since surfacing deleted sessions is what this endpoint is for. Previously these fields were rejected with 400 "data.filters should NOT have additional properties". See Filtering and Search sessions by status.
September 14 — Malformed dropdown values no longer fail requests
Fixed — dropdown custom fields with malformed stored entries. Endpoints that serializecustom_fields — session and speaker search (POST /v1/event/{eventId}/sessions, POST /v1/event/{eventId}/speakers), the related get endpoints, and GET /v1/events?expand=custom_fields — returned 500 when a dropdown-type field’s stored value contained a null entry, a shape some legacy data imports produce. Null entries are now ignored: a dropdown whose stored entries are all null serializes as value: null (empty), and a value with a mix of null and real selections formats from the real selections only. No request or response shape changed — requests that previously succeeded return identical results.
September 14 — Documentation correction: speaker search matches the speakers role only
Corrected — speaker search participant roles.POST /v1/event/{eventId}/speakers returns the contacts attached to a session as speakers; chairpersons, moderators and other participants are never returned, with or without a categorization filter. The behavior is unchanged and correct — the page simply did not say so, and the session object exposes four separate participant arrays (speakers, chairpersons, moderators, participants) that are easy to mistake for interchangeable. Because roles are per session, the same contact can be a speaker on one session and a moderator on another, so a contact in the unfiltered speaker list may legitimately be missing from a filtered one. See Filtering and Search speakers.
September 13 — MCP docs refresh & AI assistant guides
Docs — MCP Server reference updated: full 38-tool inventory (including thesearch / fetch pair and the nine read-only list tools), Middle East region URL (mcp-me.sessionboard.com), and corrected PII-masking behavior (always on for OAuth query results; per-token Hide PII setting for API tokens, off by default). The Sessionboard connector is now listed in Claude’s connector directory, and the Help Center has step-by-step connection guides for Claude, ChatGPT, Microsoft Copilot, and Google Gemini — overview at Connect AI assistants.
September 13 — Event tags return real data, translated-fields validation & empty-string clears
Fixed —GET /v1/event/{eventId}/tags returns the event’s tags. The list endpoint previously read from an internal org-level table that the app never writes, so it returned an empty list for every event. It now returns the event’s taxonomy tags (the ones organizers create and apply to sessions in the app) with the same {"data": [...], "pagination": {...}} envelope and page/page_size parameters as the sibling metadata endpoints (tracks, levels, formats). Items now include type, color, and order. Writes are consistent too: POST /tags/create accepts color and order and returns 201 (previously created orphan records the app never showed), and PUT /tags/{id} supports the updated_at optimistic-concurrency check (409 STALE_UPDATE). The search endpoint (POST /tags) is unchanged — it already read the correct table.
Changed — translated_fields is validated (write:sessions, write:contacts, write:metadata)
On every create/update that accepts translated_fields, entries are now validated before anything is written: translated_fields must be an array, every entry must include a non-empty translated_language, and the language must be one of the event’s enabled locales (the event default plus enabled variants). Invalid input fails the whole request with 400 VALIDATION_ERROR naming the offending key and listing the enabled locales — previously such entries were silently accepted and could write variants under a bogus internal locale. Requests that already send valid entries are unaffected. Related cleanup: the updated_fields[].language echo on PUT .../sessions/{sessionId}/fields is now omitted when the request didn’t specify a language (it previously echoed an internal "EN_US" placeholder).
Fixed — empty string clears numeric custom fields. For number, currency, and percent custom fields, sending "" (or a whitespace-only string) in custom_fields now clears the stored value, identical to sending null. Previously it stored an actual 0, which is indistinguishable from a real zero. 0 and "0" still store zero.
September 12 — Single-session GET is no longer cached
Changed — Sessions (read:sessions)
GET /v1/event/{eventId}/sessions/{sessionId} no longer serves cached responses. It previously shared the session search cache, which could return a stale updated_at for up to 2 minutes after an edit and cause the follow-up PUT to fail with 409 Conflict. The GET now always returns the current record, so the optimistic-concurrency flow (GET → PUT with updated_at) works immediately after any change.
POST /v1/event/{eventId}/sessions (session search) keeps its 2-minute cache — see Caching.
September 8 — Events pagination (breaking), ISO 8601 datetimes & sponsor/exhibitor names
Changed — datetime custom field values are ISO 8601. OnGET /v1/events?expand=custom_fields, datetime values serialize as event-local time with the event’s UTC offset (for example 2026-08-15T09:00:00-03:00), falling back to the raw UTC instant (2026-08-15T12:00:00.000Z) when the event has no valid timezone. On session, speaker, sponsor, and exhibitor custom_fields, datetime values are the raw UTC instant. Previously datetime values were human-readable strings in server-local time with no timezone marker, which was ambiguous for any consumer parsing them. Date-only fields are unchanged.
Fixed — sponsor & exhibitor name persists and is validated. Create (POST .../sponsors/create, POST .../exhibitors/create) now persists name and enforces the documented contract: name is required unless account_id links an existing account (400 otherwise, and 400 when account_id does not reference an existing account). Update rejects an explicit empty-string name with 400. name is returned on create, update, get, and search responses. Previously name was silently dropped on create/update, and the resulting unnamed records could be missing from search and get-by-id responses — such records are now returned (with name: null until a name is set).
Fixed — session status search sort.deletedAt. POST /v1/event/{eventId}/sessions/status with {"sort": {"deletedAt": {"sort": "desc"}}} now works as documented (previously it returned 500). Deleted sessions sort before never-deleted ones in both directions.
Fixed — single-value expand query parameter. GET endpoints that accept expand now take the documented single-value form (?expand=composition) in addition to the repeated-key (?expand=a&expand=b) and bracket (?expand[]=a) forms. Previously the single-value form returned 400 on some endpoints.
September 8 — Filter validation, speaker filter semantics & zero-value custom fields
Changed — session list filter validation. OnGET /v1/event/{eventId}/sessions, a non-UUID value in any categorization filter param (track_id/track_ids, level_id/level_ids, format_id/format_ids, language_id/language_ids, tag_id/tag_ids) now returns 400 with error code VALIDATION_ERROR and a message naming the offending parameter (previously an unhandled 500).
Fixed — speaker search categorization filters. On POST /v1/event/{eventId}/speakers, the session categorization filters (filters.trackIds, levelIds, formatIds, languageIds, tagIds) now match against the top-level session — a speaker assigned to a subsession matches via the subsession’s parent session, aligning with session search semantics. Previously a subsession’s own categorization values could match speakers whose parent session did not satisfy the filter.
Fixed — zero values in numeric custom fields. Custom field values whose stored value is numeric 0 (number, currency, percent, quantity, and formula fields) now serialize as their formatted value instead of null across all responses that include custom_fields. null now reliably means the field is empty.
September 7 — Documentation corrections: search sort shape & categorization filters
Corrected — searchsort shape. Search endpoints take nested sort keys ({"sort": {"startsAt": {"sort": "asc"}}}), not the previously documented flat {"order": "createdAt", "sort": "asc"} pair. Session search supports startsAt (null starts_at sorts last); session status search supports deletedAt. See Sorting.
Documented — session categorization filters. Tracks, tags, levels, formats, and languages are filterable on session search and speaker search (camelCase body arrays such as filters.trackIds) and on the session CRUD list (snake_case query params such as track_ids). These filters are not available on other endpoints. See Filtering.
August 2026
August 26 — List events expands
Added —GET /v1/events expand (read:events)
Opt-in collections on each event. Omitted from the payload when not requested; present as [] when requested and empty.
Repeat the query param or comma-separate:
?expand=custom_fields,integration_mappings. source on mappings is the union key (underscores, e.g. expo_platform); name is the display label. LiveBuzz / apps-engine campaign IDs are not included in integration_mappings. These values are not on the session Expand enum.
The default event shape now also documents starts_at, ends_at, and features.primary_speakers / speaker_acceptance.
July 2026
July 20 — Session & event content packs
Added — Composed content (read:transcriptions)
One-call packs for mobile and partner apps — composed transcript elements (speaker turns, optional interval_sec), preferred summary, insights, podcast status/URL, and summary/event PDF links.
Raw
.../transcriptions CRUD is unchanged. Content is not session composition.
Documentation
- New guide: Consuming session content
- Updated: Transcriptions & Media
July 10 — Session files & upload paths
Added — Session files (read:sessions / write:sessions)
Attach PDFs, PowerPoint decks, Word docs, and similar files to a session.
Simple upload — one multipart request with field
file. Sessionboard detects size and MIME type, scans the file, and returns the attached file. No presigned URL steps.
Direct-to-storage — create → PUT bytes to signed URL → complete. Use for files over 50 MB (up to 500 MB) or when you prefer uploading straight to storage (standard presigned-URL pattern used by AWS, Stripe, and similar APIs).
Session-scoped creates use standard REST (POST on the collection path — no /create suffix). The /create suffix remains only where POST on the collection path is search (sessions, contacts, metadata entities, etc.).
Documentation
- New guide: Uploading session files — two-tab playbook (simple vs direct-to-storage)
- API overview: Session Files
July 7 — Sessions & scopes
Fixed- Embedded contact fields on session responses (
photo_url,company_name,title,address_country, and related profile fields) now populate correctly onspeakers[],chairpersons[],moderators[], andparticipants[]in the event’s default language. - Participant ordering respects configured role sort order and per-participant
order.
- Read routes for transcriptions and recordings require the
read:transcriptionsscope when your token has explicit scopes. - Read routes for media items require the
read:mediascope when your token has explicit scopes. read:transcriptionsandread:mediaadded to the OAuth scope catalog.
- Session participant model documented:
participants[]vs legacy role arrays,participant_roleshape, and contact fields available withoutexpand.
July 6 — Transcriptions, recordings & media upload
Added — Transcriptions (read:transcriptions / write:transcriptions)
Supports artifact types:
fragment, summary, insight, and translation. Fragments written via the API appear in the event’s Marketing Media transcript view.
Added — Session recordings (read:transcriptions / write:transcriptions)
Added — Media upload (
read:media / write:media)
Direct-to-S3 multipart upload for video or audio files, with automatic transcription when processing completes.
Documentation
- New guide: Media & Transcriptions (three integration playbooks).
- New reference page: Transcriptions & Media.
- OpenAPI tags split into Transcriptions, Session Recordings, and Media.
July 5 — Sessions 2.0 participants
Addedparticipants[]on session search and get — flat list from the Sessions 2.0 model, including custom program roles (Author, Panelist, etc.). Always returned; no expand flag required.participant_roleon legacyspeakers[],chairpersons[], andmoderators[]when the event has Sessions 2.0 roles configured.expand=compositionon session search/get — returns composition status, target session summary, and source counts for abstract-to-session linking.- Richer nested metadata on
language,track,level,room, andformatobjects (color,order,capacity, timestamps, etc.) instead of{ id, name }only.
- Minimal
parent.subsessions[]shape now includesparticipantsalongsidespeakers.
July 3 — Session write responses
Addedis_abstracton session create and bulk create (requires Sessions 2.0 on the event).expand=compositionon create/update/bulk responses — includes composition status block.
- Create, update, bulk, get, and list responses now share one formatter — write responses match read/search shape (nested metadata, composition, etc.).
expand=linked_sourcesandexpand=compositionsupported consistently across session CRUD handlers.
June 2026
June 18 — Subsessions
FixedGET /v1/event/{eventId}/sessions/{sessionId}accepts a subsession UUID directly and returns the full session shape (with parent friendly-id keys).expand=subsession_detailsincludesis_publicon every subsession inparent.subsessions[].
May 2026
May 22 — Subsession field parity
Addedexpand=subsession_detailson session search and get — every item inparent.subsessions[]returns full parent-shape parity (status,custom_fields,chairpersons,moderators,sponsors,exhibitors,tags, nested metadata, and more). Default subsession shape is unchanged for backward compatibility.GET /v1/event/{eventId}/sessions/{sessionId}with a subsession UUID returns the full session DTO at the top level.POST /v1/event/{eventId}/sessions/statusreturns a minimalsubsessions[]array on parent rows (id, friendly_id, status, custom_status, timestamps) for status-sync integrations.
- Subsession expand behavior and
SubsessionDetailedschema documented in the API overview.
May 7 — Webhooks
Documentation- Webhook payload schemas updated to match the current event shape.
April 2026
April 13 — OAuth token lifetimes
Changed- OAuth access tokens expire after 24 hours (previously shorter).
- OAuth refresh tokens expire after 90 days.
April 12 — Deep links & OAuth hardening
Addedadmin_urlon session, contact, exhibitor, and sponsor responses — direct link to the record in the Sessionboard admin UI.
- OAuth Bearer tokens work with all MCP server tools.
- OAuth metadata URLs served correctly over HTTPS behind the load balancer.
April 11 — OAuth 2.1 & rate limits
Added- OAuth write scopes for sessions, contacts, exhibitors, sponsors, fields, metadata, and events.
- RFC 8707 resource parameter support on the token endpoint.
- Per-token rate limit overrides — contact support to raise limits for high-volume integrations.
- Browser-based OAuth:
/oauth/authorizeredirects to the consent UI. - OAuth discovery at
/.well-known/oauth-authorization-server.
- OAuth guide and Rate limiting page published.
April 10 — Convenience routes & insights
Added- Convenience routes for org-level dashboards, widgets, and saved reports at
/v1/dashboards,/v1/widgets, and/v1/queries(in addition to event-scoped paths). - Event-scoped insights, dashboards, widgets, and queries routed through the unified v1 proxy.
- Widget create no longer returns 404 when
dashboardIdis in the path. - Rules list handles missing pagination without error.
- Contact create/update returns profile fields correctly when locale casing differs.
April 9 — Agenda planning & metadata
Added- Agenda drafts — full CRUD for drafts, draft sessions, commit, and change history.
- Scheduling rules — create, read, update, delete.
- Personas — create, read, update, delete.
- Event metadata CRUD — rooms, tracks, tags, formats, levels, languages, and session statuses (read and write).
April 7 — Entity write API (major release)
Added- Session CRUD — create, update, delete, restore, bulk operations, and custom field updates.
- Contact CRUD — create, update, delete, restore, bulk, and list sessions for a contact.
- Exhibitor & sponsor CRUD — create, update, delete, restore, and bulk.
- Custom field definitions — create, update, delete.
- Organization-scoped contact read endpoints.
- Bearer token authentication alongside
X-Access-Token. - Write scopes enforced on all mutating routes.
- Full OpenAPI schemas for all CRUD endpoints.
- Sidebar reorganized by org scope, event scope, and convenience routes.
April 8 — Reliability
Fixed- Error details from the API are forwarded through the proxy (clearer validation messages).
204 No Contentresponses handled correctly on delete/restore.- Scheduled smoke tests against the dev environment (ongoing quality monitoring).
March 2026
March 25 — Organization contacts
FixedGET /v1/organization/{orgId}/contacts/{contactId}path corrected in the OpenAPI spec (was returning 404 for valid requests).
March 23 — Documentation site
Added- Public API documentation launched at apidocs.sessionboard.com (Mintlify).
- Insights & AI section — MCP server, SbQL, and analytics endpoints.
- EU region base URL documented:
https://public-api-eu.sessionboard.com.
Earlier capabilities
These endpoints predate 2026 but are part of the current API surface:- Session & speaker search —
POST /v1/event/{eventId}/sessions, speaker search, and individual session get (search results cached for 2 minutes; the individual session get is not cached — see Caching). - GDPR —
GETandPOST /v1/gdpr/requests. - Webhooks — real-time notifications on data changes (Webhooks).
- Insights — SbQL execute, AI query generation, saved reports, and dashboards (Insights overview).

