Skip to main content
The MeetingKit API is versioned by date. The current version is 2026-10-12.
The base URL will not change. It is https://api.meetingkit.com/api/v1 and it is going to stay that way. There is no /api/v2, and no migration waiting for you.

Versioning

A new version is minted only when we change something in a way an existing integration could notice — the left column of the table below. Everything else ships to every version the day it lands, so a pinned integration still gets new endpoints, new fields, and new options without doing anything. Your pin. Every API key is pinned to the version that was current the first time it called the API, and stays there until you move it. Requests without a header are served at your pin. The header. Send Happyscribe-Version: 2026-09-15 on any request to be served as the API was on that date. Any date works — it resolves to the newest version on or before it — so you can pin your code to the day you wrote it. This is per request and never changes your key’s pin. The response tells you where you are. Every response carries the version that served it, and, when you are behind, a link to the next breaking change above your version:
Upgrading. Send the header with the newer date against production, read the release history from your version up, fix what the Breaking entries name, then move your pin — contact support with your account email until the setting reaches your account page. Webhook endpoints carry their own pin, set when the endpoint is created.

What you must tolerate

Your integration has to keep working when any of these happen. We ship them without warning, and they are not breaking changes:
Parse defensively. Do not assume a response object contains only the keys you know about, and do not fail validation on unrecognized ones.
JSON objects are unordered. Never depend on key position.
This is the one that breaks integrations most often. state, failure_reason, export formats, and language codes all gain values over time. Handle unknown values as a default case — never write an exhaustive switch that throws on anything unfamiliar.
Existing calls keep their current behavior when you omit them.
Additive by construction.
Your receiver must ignore event types it does not recognize, and respond 2xx rather than erroring.
audioUrl, videoUrl, downloadUrl, and upload URLs are short-lived and signed. Fetch them fresh each time. Never cache them, parse them, or persist them.
Object IDs and error messages are opaque. Their length and format can change, including gaining or losing fixed prefixes. Store IDs as strings with room to spare.

What we promise

Everything else. We will not remove an endpoint, remove a response field, rename either, make an optional parameter required, or change what an existing call does, without going through the deprecation process below.

Deprecation and removal

Removals are rare, and they never arrive unannounced. When we deprecate something, three things happen at once. 1. The response tells you. Deprecated endpoints return an RFC 9745 Deprecation header and a link back to this page:
You will see this in your own logs without reading anything we publish. Many HTTP clients surface it automatically. 2. The changelog records it. A Deprecated entry naming the replacement. 3. If and when a removal date is set, the endpoint additionally returns an RFC 8594 Sunset header with that date:
A deprecated endpoint with no Sunset header has no removal date. That is the normal state — we deprecate long before we remove, and most deprecated paths simply stay alive. Before removing anything we check whether it is still being called. If it is, we extend the window and contact the organizations still using it directly. A deprecated path or field is eventually removed at the current version by a new breaking version. It keeps working, unchanged, for every key pinned before that version.

Deprecated paths

These still work and will keep working. They are not in the API reference because they are no longer the recommended way to call the product. The /transcriptions rows are a pure rename: each path keeps its exact behavior and response shape under /conversations. GET /orders/:id and POST /orders/:id/confirm are not deprecated — Orders remains the resource you retrieve and confirm.

Release history

2026-10-12

snake_case everywhere Breaking Every response field is snake_case: workspaces, people, companies, conversations and their meeting, orders, upload instructions, memberships, folders and translation tasks rename their camelCase fields (createdAt is created_at, audioUrl is audio_url), and their timestamps, with glossaries’ and style guides’, are in UTC (Z). Every version still accepts both spellings as input.
  • Changed on workspaces: created_at, updated_at, owner_email, members_count, is_human_transcription_allowed, is_human_translation_allowed, email_domains, idp_company_names.
  • Changed on people, wherever they appear (/people, bots, bot.* webhooks, conversations): full_name, primary_email, job_title, linkedin_url, photo_url, avatar_url, enriched_at, created_at, updated_at.
  • Changed on companies, wherever they appear: primary_domain, employee_count, founded_year, short_description, website_url, linkedin_url, logo_url, total_funding, annual_revenue, latest_funding_stage, created_at, updated_at.
  • Changed on conversations: created_at, updated_at, delivery_estimated_at, sharing_enabled, share_code, public_url, audio_length_in_seconds, failure_reason, failure_message, soundwave_url, audio_url, video_url, cost_in_cents, _links.self.download_url, _links.self.editor_url; on their meeting: start_time, end_time, ical_uid, calendar_event_id, calendar_id, calendar_owner_role, summary_url.
  • Changed on orders: can_be_submitted, outputs_ids. On GET /uploads/new: signed_url. On memberships: organization_id, organization_name, created_at, updated_at. On folders: parent_id. On translation tasks: failure_reason, progress_percent, target_language, translated_transcription_id.
  • Changed the timestamps of those objects, and created_at and updated_at on glossaries and style guides, to ISO 8601 in UTC (2026-09-29T09:00:00.123Z), keeping their milliseconds; an order’s transcriptions[].estimated_at is 2026-09-29T09:00:00Z. They were sent with the server’s offset (2026-09-29T11:00:00.123+02:00).
Otherwise, only the names change. Keys and webhook endpoints pinned before 2026-10-12 keep the camelCase names and the server’s offset. From 2026-10-12 a bot payload carries no people or companies at all, including a bot.* event recorded before this release and resent: see The bot records a meeting below. Metadata, flat and merged Breaking A bot’s metadata is a flat map of strings, like every other object’s: up to 50 keys of up to 40 characters, values up to 500 characters. PATCH /bots/{id} merges it instead of replacing it ("" removes a key, metadata: "" removes them all), and a value stored as a nested object or a number reads as its JSON text.
  • Changed metadata on bots, POST /bots and PATCH /bots/{id}: nested objects, arrays and null are refused with a 422 naming the key; numbers and booleans are stored as their text; keys keep the casing you send.
  • Added metadata to members, workspaces (for owners and admins), brand kits and webhook endpoints, and PATCH /meetings/{id} to set a meeting’s. These are additive and reach every version. A change of a meeting’s metadata alone sends no meeting.updated webhook.
Keys and webhook endpoints pinned before 2026-10-12 keep a bot’s metadata as it was: any JSON object up to 16 KB, replaced by PATCH /bots/{id}, returned as stored. Connect links create members lazily Breaking Calendar connect links no longer take or return email_match: a link for a member_id connects whichever account the member approves. member_data.email pre-fills the provider’s sign-in instead.
  • Removed email_match from POST /calendar_connect_links and the link object. Sending it is a 422.
  • Added member_data (name, email, metadata) to POST /calendar_connect_links: the member is created when the user consents. Not with member_id. Additive, on every version. member_data.name takes the same names as POST /members (up to 100 characters, not a URL): a longer one is a 422 when you create the link instead of a connect_failed after consent.
  • Added member_id to the query string of the return to return_url, on every version.
Keys pinned before 2026-10-12 keep email_match (default exact) and read links without member_data. Policies keep what a partner can use Breaking Policies and a meeting’s policy and effective_policy drop what a MeetingKit partner cannot use: transcription.model, transcription.glossary_ids, access, summary_email, storage, recording.methods.bot.silence_detection.automatic_leave and recording.methods.bot.join_at_scheduled_start; writes naming them are refused.
  • Removed from workspace, member and meeting policies (GET and PATCH /policies/{id}) and from a meeting’s policy and effective_policy, on meetings, POST /calendar_events and meeting.* webhooks: transcription.model, transcription.glossary_ids, access, summary_email, storage, recording.methods.bot.silence_detection.automatic_leave and recording.methods.bot.join_at_scheduled_start. Bots keep joining 3 minutes before the scheduled start.
  • Changed a PATCH /policies/{id} naming one of them to answer 422 with unknown_field.
  • Changed recording.methods.bot.start to read only on_join or on_command. A start set in the MeetingKit app reads on_join.
Members without a policy Breaking Members no longer carry policy: read a member’s policy with GET /policies/{member id}. Calendar connections without a method Breaking Calendar connections no longer carry method, which was always user_consent. Brand kits in groups Breaking Brand kits are two groups, bot (name, tile, chat) and email, where each choice is a type and an object named after it. They replace bot_name, background_color, logo_scale, logo_url, custom_thumbnail_url, send_chat_message, chat_message, chat_message_paused, brand_color, brand_url, brand_description and apply_branding_to_summary_emails, and a write naming a field the kit does not have is refused.
  • Changed bot.tile chooses the notetaker’s tile: type is default, color, logo or image, with the options of the chosen type in the object of the same name. The other types keep their options, so switching back restores them.
  • Changed email chooses the summary email: type is default or branded. A kit created through the API starts on default.
  • Added email.branded.cta, the button of a branded email: open_meeting (the button it always had), none, or link with a label and a url where {{meeting_id}} is the meeting’s id.
  • Added images from files: send a file_ id as bot.tile.logo.file, bot.tile.image.file or email.branded.logo and read { id, url } back. A write takes that object too, so the bot and email of a read can be sent back as they are. The email logo is the tile’s logo until it gets one of its own.
  • Added workspace_id on brand kits: the workspace that owns the kit, null when the key does not belong to it. Keys pinned before 2026-10-09 read it as an integer.
  • Changed POST /brand_kits takes the workspace that owns the kit as workspace_id, which may be left out when the key belongs to exactly one workspace, and no longer needs workspace_ids: a kit can exist with no workspace using it. PATCH /brand_kits/{id} may empty workspace_ids.
  • Changed PATCH writes only what it names, at any depth; 422 with unknown_field names a field the kit does not have, on POST /brand_kits and PATCH /brand_kits/{id}.
  • Changed a brand kit is no longer removed when the last workspace stops using it. It stays, visible to the keys of the workspace that owns it and editable by that workspace’s owners and admins, until DELETE /brand_kits/{id}.
  • Removed GET and PATCH /workspaces/{id}/brand_kit. Use GET /brand_kits?workspace_id= and PATCH /brand_kits/{id}.
Brand kits read and take the groups at every version. Keys and webhook endpoints pinned before 2026-10-12 keep reading and writing all of these fields exactly as before, including the start value set in the MeetingKit app. A webhook retry or resend carries the values the event had when it happened. A change to one of the removed settings in the MeetingKit app still sends meeting.updated at every version; from 2026-10-12 its data.object can read the same as the previous event. One calendar event object Breaking POST /calendar_events returns the calendar event object instead of the meeting, and meeting.calendar_event is that same object (cev_ id, meeting_id, times, people, metadata) instead of { ical_uid, series_id }. series_id is gone: recurring says whether the event repeats.
  • Changed POST /calendar_events returns the calendar event object: one per meeting, the same for every member who has the event, with the meeting_id of its meeting.
  • Changed meeting.calendar_event is that same object, and on every meeting.* webhook.
  • Added metadata on POST /calendar_events, kept on the calendar event and merged on every push. Additive: accepted at every version.
  • Removed calendar_event.series_id, which was always null.
Keys and webhook endpoints pinned before 2026-10-12 still get the meeting from POST /calendar_events and { ical_uid, series_id } in meeting.calendar_event. Meeting platforms meet, zoom, teams Breaking A meeting’s platform is meet, zoom or teams (Microsoft Teams Live included) instead of google_meet, zoom, microsoft_teams or microsoft_teams_live. Any other platform stays null. Keys and webhook endpoints pinned before 2026-10-12 keep the old names. After this release, the next change to a meeting may send one more meeting.updated webhook, with a new event id, whose data.object is unchanged at versions before 2026-10-12. A resend of an event from before this release carries the calendar event object at 2026-10-12, built from what the event recorded: no metadata or response, and recurring: null. The bot records a meeting Breaking A bot is one way of recording a meeting: it carries meeting_id and flat fields (bot_name, welcome_message, recording_start, recording_mode, silence_alert, summary_language, summary_template) instead of settings, results are read only from GET /meetings/{id}/results, and conversation_id, transcript, speakers, summary, summary_sections, action_items, people and companies are gone from the bot.
  • Added meeting_id to the bot and every bot.* webhook. POST /bots creates the meeting at once, so it is set from queued.
  • Changed POST /bots: send bot_name, bot_avatar, welcome_message, recording_start (on_join or on_command), recording_mode, silence_alert, summary_language and summary_template at the top level. A request with settings is refused with 400. glossaries, destination_folder_id, access_level, send_recording_to and silence_detection_automatic_leave are no longer taken.
  • Changed the recording choices to be the meeting’s: each one sent becomes the meeting’s own policy, each one left out follows the member’s and the workspace’s, and PATCH /policies/{meeting id} can change transcription.language, summary.language, summary.template and recording.methods.bot.silence_detection.alert on a meeting started by a bot until it begins.
  • Removed settings, conversation_id, transcript, speakers, summary, summary_sections, action_items, people and companies from the bot and bot.* webhooks. Read the results with GET /meetings/{meeting_id}/results.
  • Added on every version: member_id on POST /bots (the meeting is that member’s), and include=words on GET /meetings/{id}/results for each segment’s word timings.
Keys and webhook endpoints pinned before 2026-10-12 keep sending settings and reading the bot with its results exactly as before, and a webhook retry or resend carries the body the event had when it happened. Files
  • Added POST /files and GET /files/{id}, on every version: upload an image (PNG, JPEG, GIF, WebP or SVG, up to 10 MB) to a workspace as multipart/form-data and get a file with a file_ id and a signed url. purpose says what it is for; brand_kit is the only one for now. Keys pinned before 2026-10-09 read its workspace_id as an integer.
Brand kit preview
  • Added preview_url and preview_expires_at on the brand kit, on every version: a page that shows the kit as saved (the notetaker’s tile in the call, its chat messages and the summary email). Anyone with the link can open it without signing in. The link lasts ten minutes, and every response carries a fresh one.
Workspaces born ready, members with your id
  • Added client_reference_id on the member, on POST /members and PATCH /members/{id}, on every version: your own id for the person, stored and returned, never interpreted. null until you set it.
  • Changed POST /workspaces: the new workspace has every feature on, nothing is billed, and its speakers are identified from its first meeting. Nothing to configure.

2026-10-10

Results show results only Breaking Meeting results no longer read pending: a part is none until a recording produces it. Whether one will come is the meeting’s phase, recordings and effective_policy.recording.
  • Removed pending from results.status and from each part’s status (transcript, summary, media) on meetings, GET /meetings/{id}/results and meeting.* webhooks. A meeting whose bot is queued, joining or in the call reads none until the recording is in, then processing, ready or failed as before.
  • Added to the Meeting object reference which field gives each label (will be recorded, recording, processing, not recorded).
Keys and webhook endpoints pinned before 2026-10-10 keep reading pending while a bot is queued, joining or in the call and has no transcript yet. A webhook retry or resend carries the value the event had when it happened. After this release a meeting with a bot in one of those states sends one more meeting.updated webhook, with a new event id, whose data.object is unchanged at your version.

2026-10-09

One error body Breaking Every error, 401 and 429 included, is { "errors": [{ "code", "message", "field" }] }: code is a stable word, field is the dotted path to the request field at fault and is present only then, and the body no longer repeats the HTTP status.
  • Changed every endpoint’s error body to the errors array, with a code on every error: missing_api_key, invalid_api_key, rate_limited, not_found, internal_error and the rest listed on Errors.
  • Changed field to the full dotted path (settings.language) where some endpoints sent only the last segment, and dropped field: "base" from workspace errors.
  • Fixed 429 responses to carry the documented retry_in_seconds, on every version.
  • Added message to the workspace, order and validation errors that were an errors array without one, on every version.
Keys pinned before 2026-10-09 keep each endpoint’s previous error body. A request without a valid key has no pin: send Happyscribe-Version: 2026-10-09 to get the new 401. The previous bodies are:
  • A single error string, for most endpoints: { "error": "Not found" }. A missing or invalid key also repeats the status: { "error": "Invalid API key.", "status": 401 }.
  • Bots: { "errors": ["Meeting url must be a valid URL"] }, and on 409 also a top-level code (bot_not_editable, bot_not_cancellable).
  • Workspaces: { "errors": [{ "code": "workspace_update_not_allowed", "field": "base", "message": "…" }] }.
  • Members, meetings, policies, brand kits and calendar connections already used the errors array for the errors they raise themselves. What changes on them is what the API raises before or around them: authentication (401), rate limits (429), a malformed body or missing parameter (400), an invalid Happyscribe-Version, a generic 409 and 500.
Typed ids Breaking Workspaces, glossaries, custom summary templates and consent events are identified by typed ids (wks_…, gls_…, tpl_…, cns_…) instead of integers, everywhere they appear. Every version still accepts the integer ids as input.
  • Changed workspace ids to wks_…: the workspace id, every workspace_id on meetings, members, calendar connections, calendar connect links and agent sessions, workspace_ids on brand kits, a workspace policy’s owner.id, organizationId on organization memberships, and the /workspaces/{id} and /policies/{id} paths.
  • Changed glossary ids to gls_…: the glossary id, glossary_ids in policies and in order operations, and the bot’s glossaries.
  • Changed custom summary template ids to tpl_… in a policy’s summary.template and the bot’s summary_template. Built-in templates keep their slug, such as sales_call.
  • Changed consent event ids to cns_…, including the /consent_events/{id} path.
Keys and webhook endpoints pinned before 2026-10-09 keep reading integer ids. Pagination links (_links.next) and error messages that name a workspace carry wks_ ids on every version; follow links as they are. After this release each meeting sends one more meeting.updated webhook, with a new event id, whose data.object is unchanged at your version. Folder, person and company ids are unchanged.

2026-10-08

Recording scope meetings:organized Breaking The recording scope meetings:hosted is now meetings:organized (meetings the member organizes). Both names are accepted when you write a policy.
  • Changed policy.recording.scope on members and in GET/PATCH /policies/{id} reads meetings:organized. Versions before 2026-10-08 keep reading meetings:hosted.

2026-10-01

Calendar connections Breaking Members no longer carry calendar_connection, which was always null: read a member’s calendar with GET /calendar_connections?member_id=.
  • Added POST /calendar_connect_links and GET /calendar_connect_links/{id}: a hosted link that takes one of your users through Google or Microsoft consent and connects their calendar, creating the member from the connected account when you don’t name one.
  • Added GET /calendar_connections, GET /calendar_connections/{id} and DELETE /calendar_connections/{id}: your members’ connected calendars, with their status and last sync, and a disconnect that keeps past meetings.
  • Added the calendar_connection.connected, calendar_connection.disconnected and calendar_connection.error webhooks.
  • Removed calendar_connection from members.
Keys pinned before 2026-10-01 keep reading calendar_connection: null on members. The new endpoints and webhooks reach every version.
  • Changed a workspace created with POST /workspaces to start with its meeting language detected (auto) rather than English. On every version.
Settings and settings assignments replace policies
  • Added GET /settings, POST /settings, GET /settings/{id}, PATCH /settings/{id}, DELETE /settings/{id}, GET /settings_assignments, POST /settings_assignments and DELETE /settings_assignments/{id}. Settings are values; an assignment applies them where every condition of its scope holds (workspace:, member:, meeting:, meetings:organized|internal|domain|external). See Settings.
  • Added settings on workspaces (and GET /workspaces/{id}), members and meetings, and in meeting.* webhooks: the values that apply, sources naming the assignment that supplied each one (or a reason), and every assignment that reaches the object. Lists add it with include=settings.
  • Removed GET /policies/{id} and PATCH /policies/{id}, and policy and effective_policy on meetings and in meeting.* webhooks.
  • Changed a meeting’s own choice to reach that occurrence only, and null to a code: inherit on any field, same_as_transcript for summary.language, none for branding.brand_kit_id.
One list shape
  • Changed GET /workspaces and GET /consent_events to the list every other endpoint returns: { "object": "list", "data", "has_more", "_links" }, paged with limit (default 25, at most 100) and the _links.next.url cursor. workspaces and results are now data.
  • Removed page and per_page: every list in this reference answers 400 to either.
Unknown paths answer JSON A path that matches no endpoint answers 404 not_found in the error body, before the API key is checked. A known path called with a method it doesn’t take answers 405 method_not_allowed with an Allow header. Both used to return an HTML page.
  • Changed bot errors on meeting_url: a link we can’t join, a Microsoft Teams organization that blocks outside bots, no notetaker free and an unexpected failure each say what happened. The code is still invalid.
  • Changed every 401 (no key, an unknown key, a key that may not do this, an invalid agent handoff code) to the errors body on every version; it no longer depends on Happyscribe-Version.
  • Changed POST /members and POST /calendar_events refuse an email on a disposable-mail or reserved test domain (such as example.com) with a message naming the domain. The code is still invalid.
The API is at api.meetingkit.com The base URL is https://api.meetingkit.com/api/v1, with an API key from MeetingKit. The docs live at https://docs.meetingkit.com.

2026-09-30

Policies Breaking A member carries its policy (what it sets: recording scope, bot, transcription, summary, access and summary email) instead of settings, and policies change through PATCH /policies/{id}. A meeting carries its policy (what was set on it) and effective_policy (what will happen, with recording.enabled_because) instead of settings and recording.reason.
  • Added GET /policies/{id} and PATCH /policies/{id}: what gets recorded and how, set on a workspace, a member or a meeting.
  • Changed members: settings is now policy, in the policy shape (recording.scope instead of the four auto_join toggles, access as grants).
  • Changed meetings: settings is now policy plus effective_policy, and recording.reason is effective_policy.recording.enabled_because.
Keys and webhook endpoints pinned before 2026-09-30 keep seeing settings on members and meetings (and meeting.* payloads), with the values that apply.

2026-09-29

Meetings API Breaking Bot ids start with bot_ instead of mtg_ (the characters after the prefix are unchanged and /bots/{id} accepts both), mtg_ now identifies a Meeting, consent events carry their bot as bot_id instead of meeting_id, and the meeting.* webhooks are sent.
  • Added GET /meetings, GET /meetings/{id} and GET /meetings/{id}/results: every meeting of a workspace, whether it came from a calendar or a bot, with its transcript as speaker turns, its speakers, summary and media once it is over.
  • Added the meeting.created, meeting.updated and meeting.finished webhooks.
  • Changed bot ids: mtg_01k6b0… is now bot_01k6b0…. Store either; /bots/{id} and GET /consent_events?bot_id= accept both.
  • Changed consent events: meeting_id is now bot_id, and the meeting_id filter on GET /consent_events is now bot_id.
Keys and webhook endpoints pinned before 2026-09-29 keep seeing mtg_ bot ids and meeting_id on consent events, and receive no meeting.* webhooks. GET /meetings/{id} with a bot’s mtg_ id keeps returning that bot. Calendar connections
  • Changed calendar connections are available to every workspace, on every version: POST /calendar_connect_links no longer returns 403 feature_not_enabled.
Webhooks API
  • Added POST, GET, PATCH and DELETE /webhook_endpoints: register, list, change and remove a workspace’s webhook receivers. The create response is the only one with the signing secret, and a new endpoint is pinned to the API version of that request.
  • Added POST /webhook_endpoints/{id}/test: sends a signed sample and returns your receiver’s answer.
  • Added GET /webhook_events and GET /webhook_events/{id}: the events sent to your endpoints for 90 days, with every delivery attempt and its response, and POST /webhook_events/{id}/redeliver to send one again.
Timestamps in UTC
  • Fixed meeting, results, bot and consent event timestamps, and the created_at of every webhook event, to be ISO 8601 in UTC (2026-09-29T09:00:00Z) as documented. They were sent with the server’s offset (2026-09-29T11:00:00+02:00): the same instant, a different string. Every version gets it. People and companies embedded in a bot (enrichedAt, createdAt, updatedAt) still carry the server’s offset for now, so one bot payload can mix both forms: parse timestamps as instants rather than comparing strings. The next time a meeting’s webhooks are computed, it can send one meeting.updated, with a new event id, that differs only in its times. Events sent before keep their original timestamps if resent.

2026-09-28

Pick a brand kit on the workspace policy
  • Added branding.brand_kit_id on the workspace policy (GET/PATCH /policies/{workspace_id}): the brand kit the workspace uses, or null for MeetingKit’s default branding. It is the same link as a kit’s workspace_ids. Meetings show the kit their bot is sent with in effective_policy.branding.
Calendar events
  • Added POST /calendar_events: push a calendar event as Google Calendar, Microsoft Graph or Nylas returned it (or in a small shape of your own) and get the meeting for that occurrence (source: calendar_sync). Every version gets it.
  • Added no_meeting_url to effective_policy.recording.enabled_because: a calendar meeting with no link to join now says recording is off. Keys pinned before 2026-09-30 read settings.recording.reason: unsupported_platform for it.

2026-09-26

Silence alert and silence leave Breaking The bot setting silence_detection is now silence_detection_alert, and the new silence_detection_automatic_leave controls whether the bot leaves after 20 minutes of silence.
  • Changed settings.silence_detection is renamed settings.silence_detection_alert. It keeps its meaning: an audible and chat alert when nobody speaks, and the bot stays. settings.silence_detection is now a 400.
  • Added settings.silence_detection_automatic_leave, default true. Every bot has always left after 20 minutes of continuous silence; send false to keep it in the call.
Keys pinned before 2026-09-26 keep sending and reading settings.silence_detection, and their bots keep leaving after 20 minutes of silence. Brand kits you can share A brand kit is now a resource with its own id, and one kit can brand many workspaces.
  • Added POST /brand_kits, GET /brand_kits, GET /brand_kits/:id, PATCH /brand_kits/:id and DELETE /brand_kits/:id. A kit’s workspace_ids lists the workspaces that use it; set it on create or update to brand many workspaces at once.
  • Deprecated GET and PATCH /workspaces/:id/brand_kit. They keep working, on the kit the workspace uses.

2026-09-25

Meeting bots are bots Breaking The meeting bot is now a bot: its object type is bot, provider_reason is failure_message, its webhooks are bot.* instead of meeting.*, and transcription_id, summary_markdown, consent and settings.language are gone.
  • Changed the canonical paths are POST /bots and GET /bots/:id. /meeting_bots and /meetings keep working on every version and return a Deprecation header.
  • Changed the bot’s object is bot (was meeting), and provider_reason is renamed failure_message.
  • Changed the six bot webhooks are bot.recording_started, bot.recording_completed, bot.transcript_ready, bot.summary_ready, bot.failed and bot.cancelled (were meeting.*). An endpoint pinned before this version keeps receiving the meeting.* names and the previous payload, and a subscription made with an old name still matches.
  • Removed transcription_id (use conversation_id), summary_markdown (use summary_sections) and consent from the bot. consent is removed on every version; it was never enabled for an API workspace.
  • Removed settings.language. Send the top-level language; settings.language is now a 400.
  • Added created_at and ended_at on the bot, on every version.
Keys pinned before 2026-09-25 see none of the other breaking changes above until they move their pin. Reschedule and cancel bots
  • Added PATCH /bots/:id moves a bot created through the API when its meeting moves: join_at and meeting_url while the bot is queued, participants until the call ends, metadata until the bot is finished. The bot keeps its id.
  • Added DELETE /bots/:id cancels a bot created through the API. A queued bot becomes cancelled and sends bot.cancelled; a bot in the call leaves and keeps what it recorded; a bot with a recording answers 409.
Both endpoints work on every version, in the shape of the caller’s version. Webhook endpoints pinned before 2026-09-25 receive the cancellation as meeting.cancelled. Members
  • Added POST /members, GET /members, GET /members/{id}, PATCH /members/{id} and DELETE /members/{id}: a partner’s end users inside a workspace. Create is idempotent on workspace_id and email and sends no email; the person never hears from MeetingKit. Members carry mem_ ids, which the MeetingKit resources reference as member_id. A member’s settings (the four auto-join toggles, recording, summary and access defaults) are read-only for now. Every error these endpoints raise themselves (400, 404, 409, 422) is an errors array of code, message and, on 422, a dotted field (settings.recording.mode).

2026-09-18

Agent sessions
  • Added POST /agent_sessions exchanges the code a user copies from the agent handoff page for their personal API key and a workspace id. No API key is needed for the call. It is the endpoint auth.md tells AI agents to use.
Structured summaries and identified speakers
  • Added summary_markdown, summary_sections and action_items on the meeting resource, on meeting.summary_ready webhooks and on GET /conversations/:id/summary, next to the unchanged plain-text summary. Sections keep the summary template’s headings and bullets; an action item carries an assignee only when it opens with the name of exactly one meeting participant or linked Person.
  • Added speakers on the meeting resource, on meeting.transcript_ready webhooks and on GET /conversations/:id: every distinct transcript[].speaker label with its talk time and, when Voice ID resolved it, the Person’s name, email and id.
  • Fixed people[] on a meeting and on a conversation only lists People of the workspace you are reading through. A conversation moved between workspaces could list People linked by the workspace it left.

2026-09-16

Brand kit updates
  • Fixed PATCH /workspaces/:id/brand_kit returns 422 naming apply_branding_to_summary_emails when it is sent as null. Previously the request failed with 500.
  • Added the brand kit reference now lists what the endpoint already accepts: null for bot_name and send_chat_message, and up to 2048 characters for brand_url.

2026-09-02

Version pinning
  • Added the Happyscribe-Version request header. Any ISO date resolves to the newest version on or before it, for that request only.
  • Added every response carries Happyscribe-Version, the version that served it, and a Link: …; rel="successor-version" to the next breaking change when you are behind.
  • Added API keys are pinned to the version current at their first request, and webhook endpoints carry their own pin. See Versioning.
Forgiving requests, honest errors
  • Changed the Authorization header accepts the bare API key. Bearer <key> and Token <key> still work, and the scheme is now case-insensitive. The docs show the bare form everywhere.
  • Changed a JSON request body is parsed as JSON even when its Content-Type is missing, text/plain, or application/x-www-form-urlencoded. Multipart is untouched.
  • Added specific 400 messages for a body that is the string [object Object] (a fetch call without JSON.stringify) and for malformed JSON, which now names the parser’s position.
  • Changed a request with no Authorization header returns 401 with Missing API key. Send it as "Authorization: <your key>"., and a key that matches no account returns Invalid API key. — previously both said Unauthorized. Every v1 endpoint now answers an unauthenticated request with 401; a few returned 500.
  • Changed POST /meeting_bots and GET /consent_events return 400 naming workspace_id when it is blank. Previously they returned 401, which read as a bad key.
  • Fixed a trailing slash on an API URL no longer redirects. The redirect turned a POST into a GET in most HTTP clients, silently listing instead of creating.

2026-09-01

Deleting a busy conversation says so Breaking
  • Changed DELETE /conversations/:id (and its legacy /transcriptions path) returns 409 with a message naming the state when the conversation is in a pipeline stage that cannot be cancelled — aligning, retrying, unlocked, automatic_transcribing_second_part, and pro_transcribing/human_translating once the human work has started. It previously returned 401, which read as an authentication problem. A genuine permission denial still returns 401.
  • Fixed a successful delete is documented as 204, which is what it has always returned.
Webhooks join the API reference
  • Added every webhook to the OpenAPI spec and the API reference sidebar, collocated with its resource’s endpoints: the six meeting.* events under Meeting Bots, consent.granted and consent.revoked under Consent, and the order webhook_url callback (order.state_changed) under Orders. No wire format changed — these webhooks already existed; they are now part of the documented, versioned API surface.

2026-08-31

Transcriptions are now conversations The product treats every recording — an upload, a meeting, a dictation — as a conversation, and the API now does too.
  • Added GET /conversations, GET /conversations/:id, PATCH /conversations/:id, DELETE /conversations/:id, GET /conversations/:id/summary, and GET /conversations/:id/convert_to_subtitles — same behavior and response shapes as their /transcriptions counterparts. Write requests use a conversation body wrapper.
  • Added conversation_ids on POST /exports and source_conversation_id on POST /translate are accepted wherever transcription_ids and source_transcription_id were. When both are sent, the legacy name wins.
  • Added Export responses include conversation_ids and meeting objects (in API responses and webhook payloads) include conversation_id, alongside their unchanged legacy transcription_ids / transcription_id keys.
  • Deprecated the read-and-manage /transcriptions paths — use /conversations. They keep working unchanged, and transcription-wrapped request bodies remain accepted everywhere as a legacy alias. See deprecated paths.
Order responses keep their historical transcription-named keys, and the transcription/subtitles operation values are unchanged.

2026-08-21

Meeting participants carry emails again
  • Fixed meeting.participants on transcription responses returned { "email": null } for everyone but the organizer. It was rendering the in-room display names the recording provider reports at the end of a call, not the invite list — so no attendee could be mapped to a person.
  • Changed meeting.participants and meeting.organizer now always carry a name key, null when nothing exposed one. It was previously omitted.
  • Changed meeting.participants is now identical to the participants array on the meeting resource and on meeting webhooks. Meeting rooms and the notetaker itself are excluded from all three.
  • Changed name is now resolved from every source that could know it — the calendar invite, whatever you supplied at creation, the names people displayed in the call, and the people your workspace has already met — keeping the fullest. Expect a name where you previously saw null, and a fuller one (“Marc Assens”) where you previously saw a partial one (“Marc”).

2026-08-14

Consent events Consent collected by the Trust Engine is now visible in the API.
  • Added GET /consent_events and GET /consent_events/{id} — high-level consent outcomes (notified, accepted, declined, revoked, deletion_requested) per participant and meeting.
  • Added a consent block on the meeting object — status (pending / partial / complete / objected) plus each participant’s latest outcome.
  • Added webhook events consent.granted and consent.revoked, carrying the same consent event object.
Available for workspaces with consent collection enabled. Workspace brand kit
  • Added GET /workspaces/:id/brand_kit returns the workspace’s active brand kit configuration: notetaker bot name, video tile avatar colors and scaling, logo URL, in-call chat greeting messages, and summary email white-labeling settings.
  • Added PATCH /workspaces/:id/brand_kit updates workspace brand kit settings (owners and admins; other members get 403 with code workspace_update_not_allowed, and plans without notetaker customization get 403 with code plan_upgrade_required). Supports partial updates.
Workspace management
  • Added POST /workspaces creates a workspace with the caller as its owner. 403 with code workspace_creation_not_allowed when workspace creation is restricted for the caller; creation is rate limited per caller.
  • Added PATCH /workspaces/:id renames a workspace (owners and admins; other members get 403 with code workspace_update_not_allowed).
  • Added DELETE /workspaces/:id archives a workspace (owner only; others get 403 with code workspace_archive_not_allowed). Archiving cancels the workspace’s subscription, removes all member access, and permanently deletes its content after 30 days — see the endpoint reference before using it.
  • Changed GET /workspaces is paginated: 100 per page by default (per_page up to 500), oldest first, with a _links.next URL. It previously returned at most 500 workspaces with no way to page further.
  • Changed Missing-parameter errors name workspace_id everywhere except the Organization memberships endpoints, which keep their documented organization_id parameter and wording.
Organizations are now workspaces The product calls them workspaces, and the API now does too.
  • Added GET /workspaces — same behavior and fields as GET /organizations, under a workspaces response key.
  • Added workspace_id is accepted wherever organization_id was, including inside resource-wrapped bodies like transcription: { ... }. When both are sent, organization_id wins.
  • Deprecated GET /organizations — use GET /workspaces. It keeps working, with an unchanged response shape, and organization_id parameters remain accepted everywhere as a legacy alias. See deprecated paths.
The Organization memberships endpoints keep their current names and parameters. Service endpoints
  • Added POST /transcribe, POST /subtitle, and POST /translate — one creation endpoint per service, replacing the generic create-order call. Request bodies are flat (no order wrapper), and the subtitle/transcription split is in the path — there is no is_subtitle parameter.
  • Changed On the new endpoints, confirm defaults to true: one call starts processing. Pass confirm: false to preview pricing first, then submit with POST /orders/:id/confirm. (The deprecated create paths keep their confirm: false default.)
  • Deprecated POST /orders and POST /orders/translation. Both still work unchanged, with the same request and response shapes as before. See deprecated paths.

2026-08-10

Meeting bots
  • Changed POST /meetings is now POST /meeting_bots, and GET /meetings/:id is now GET /meeting_bots/:id. The name says what the resource is: a bot you dispatch to a call, not the meeting itself.
  • Deprecated POST /meetings and GET /meetings/:id. Both still work and route to the same controller. See deprecated paths.
  • Added Per-request settings on meeting bot creation, so a single bot can override the organization defaults for that call.
  • Fixed Creating a bot into a destination folder now requires write access on that folder. Bot avatars are restricted to JPEG.
  • Fixed The settings echoed back on create now match what the bot actually does, and the top-level language is validated rather than silently ignored.

2026-07-24

Delete cancels in-progress work Breaking
  • Breaking DELETE /transcriptions/:id now cancels any in-progress transcription before deleting it. Previously a delete issued while work was running could leave the job to run to completion.

2026-07-16

Meeting notetaker
  • Added POST /meetings dispatches a notetaker bot to a Zoom, Google Meet, or Microsoft Teams call.
  • Added GET /meetings/:id returns lifecycle status, and the transcript and summary once the bot has finished.
  • Added Signed organization-level webhooks for the meeting bot lifecycle. See Webhooks.

2026-07-06

Public sharing URLs
  • Added publicUrl on transcription responses, pointing at the public share page when sharing is enabled for the file.

2026-06-23

Meeting summaries
  • Added GET /transcriptions/:id/summary returns the generated summary as plain text.
  • Added summaryUrl on transcription responses, linking to the same summary.

2026-05-21

Memory — people and companies (private beta)
  • Added GET /people and GET /people/:id — the people discovered across your meeting transcripts, with search, email, and company_id filters.
  • Added GET /companies and GET /companies/:id, with search and domain filters.
  • Added person_id, person_email, company_id, and company_domain filters on GET /transcriptions, and embedded people and companies arrays on transcription responses.
  • Added audioUrl and videoUrl on transcription responses. Both are short-lived signed URLs — fetch fresh, never cache.
Memory is a private beta. Organizations that are not enrolled receive 404 on these endpoints. See People and companies.

2026-05-10

Pagination, folders, and meeting metadata
  • Added meeting on transcription responses for files that came from a meeting — title, platform, start and end time, calendar identifiers, organizer, and participants.
  • Added folder ({ id, name }) on transcription responses.
  • Added PATCH /transcriptions/:id accepts folder_id, moving the file between folders.
  • Fixed GET /transcriptions now honors per_page. It was previously ignored.

2026-04-17

Organizations and memberships
  • Added GET /organizations lists the organizations you belong to.
  • Added GET /organization_memberships, POST /organization_memberships, PATCH /organization_memberships/:id, and DELETE /organization_memberships/:id for managing members.

2026-02-09

Glossaries and style guides
  • Added GET /glossaries and GET /style_guides.
  • Added glossary_ids and style_guide_id on POST /orders, applying custom terminology and formatting preferences to the order.

2025-11-25

Orders become the write path
  • Added POST /orders creates transcription and subtitling work. Pass service: auto or service: pro, and is_subtitle: true for subtitles.
  • Deprecated POST /transcriptions — use POST /orders.
  • Deprecated POST /task/transcription_translation — use POST /orders/translation.
  • Deprecated GET /task/transcription_translation/:id — use GET /orders/:id.
Listing, retrieving, updating, and deleting transcriptions was unaffected. Only the create paths moved.

2025-05-08

Export download URLs
  • Added downloadUrl on export responses, so a completed export can be fetched without a second call.

2025-03-11

Orders for human services
  • Added GET /orders/:id returns state, pricing, and outputs once fulfilled.
  • Added POST /orders/translation places a translation order from a source transcription and target languages.
  • Added POST /orders/:id/confirm submits an order created with confirm: false, after reviewing the quoted price.
  • Added tags on transcription create and on transcription responses.

2024-06-11

Vocabulary removed Breaking
  • Breaking The vocabulary parameter was removed from transcription create. Custom terminology returned later as glossaries.

2023-08-08

Convert to subtitles
  • Added GET /transcriptions/:id/convert_to_subtitles creates a subtitle file from an existing transcription.

2023-02-28

Delete transcriptions
  • Added DELETE /transcriptions/:id.

2021-02-08

Subtitles and folder filtering
  • Added Subtitle creation through the public API.
  • Added folder_id filter on GET /transcriptions.

2020-08-12

Updates and translation
  • Added PATCH /transcriptions/:id for renaming a file and changing its sharing configuration.
  • Added POST /task/transcription_translation and GET /task/transcription_translation/:id for translating a transcript.

2020-04-09

Response slimming Breaking
  • Breaking total_count was removed from GET /transcriptions. Computing it required a full scan on every list request. Use _links.next.url to page instead.
  • Breaking Undocumented attributes were removed from transcription responses: mediaUrl, audioLength, audioUpdatedAt, policy, lastVersionUrl, versions, publicShowUrl, and publicEditUrl.

2019-08-07

Public and internal APIs split Breaking
  • Breaking /api/v1 became the public API. Endpoints that existed only to serve the MeetingKit web app — user, folders, versions, batch operations, and transcription unlock — moved to an internal namespace and are no longer reachable.

2019-02-15

Public API launch
  • Added GET /transcriptions, GET /transcriptions/:id, and POST /transcriptions.
  • Added GET /uploads/new returns a signed URL for uploading media directly to MeetingKit storage.
  • Added POST /exports and GET /exports/:id render a transcription to a downloadable file.
  • Added HAL-style _links on responses, including _links.next.url for pagination.