> ## Documentation Index
> Fetch the complete documentation index at: https://docs.meetingkit.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Changelog

> How the MeetingKit API is versioned, what we promise not to break, and every change we have made.

The MeetingKit API is versioned by date. The current version is **2026-10-12**.

<Note>
  **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.
</Note>

## 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:

```http theme={null}
Happyscribe-Version: 2026-09-01
Link: <https://docs.meetingkit.com/api-reference/changelog#2026-12-01>; rel="successor-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.

| Breaking — new version | Not breaking — every version gets it |
| - | - |
| Remove or rename an endpoint, field, parameter, or enum value | Add an endpoint, field, optional parameter, enum value, or webhook event type |
| Change a field's type, format or meaning | Reorder keys; change opaque-string or signed-URL formats |
| Make an optional parameter required; tighten validation that used to pass | Loosen validation |
| Change a status code, error shape, or default behaviour | Fix a bug so a documented behaviour becomes true |

## 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:

<AccordionGroup>
  <Accordion title="New properties appear in responses">
    Parse defensively. Do not assume a response object contains only the keys you know about, and do
    not fail validation on unrecognized ones.
  </Accordion>

  <Accordion title="Properties change order">
    JSON objects are unordered. Never depend on key position.
  </Accordion>

  <Accordion title="New values appear in existing enums">
    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.
  </Accordion>

  <Accordion title="New optional request parameters">
    Existing calls keep their current behavior when you omit them.
  </Accordion>

  <Accordion title="New endpoints and resources">Additive by construction.</Accordion>

  <Accordion title="New webhook event types">
    Your receiver must ignore event types it does not recognize, and respond `2xx` rather than
    erroring.
  </Accordion>

  <Accordion title="Signed URLs change format, length, or expiry">
    `audioUrl`, `videoUrl`, `downloadUrl`, and upload URLs are short-lived and signed. Fetch them
    fresh each time. Never cache them, parse them, or persist them.
  </Accordion>

  <Accordion title="Opaque strings change format">
    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.
  </Accordion>
</AccordionGroup>

## 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](https://www.rfc-editor.org/info/rfc9745/) `Deprecation` header and a link
back to this page:

```http theme={null}
HTTP/1.1 200 OK
Deprecation: @1786752000
Link: <https://docs.meetingkit.com/api-reference/changelog#deprecated-paths>; rel="deprecation"; type="text/html"
```

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](https://www.rfc-editor.org/info/rfc8594/) `Sunset` header with that date:

```http theme={null}
Sunset: Sat, 14 Feb 2027 00:00:00 GMT
```

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.

| Deprecated | Use instead | Deprecated on |
| - | - | - |
| `POST /transcriptions` | `POST /transcribe` | [2025-11-25](#2025-11-25) |
| `POST /task/transcription_translation` | `POST /translate` | [2025-11-25](#2025-11-25) |
| `GET /task/transcription_translation/:id` | `GET /orders/:id` | [2025-11-25](#2025-11-25) |
| `POST /meetings` | `POST /bots` | [2026-08-10](#2026-08-10) |
| `GET /meetings/:id` | `GET /bots/:id` | [2026-08-10](#2026-08-10) |
| `POST /meeting_bots` | `POST /bots` | [2026-09-25](#2026-09-25) |
| `GET /meeting_bots/:id` | `GET /bots/:id` | [2026-09-25](#2026-09-25) |
| `POST /orders` | `POST /transcribe` or `POST /subtitle` | [2026-08-14](#2026-08-14) |
| `POST /orders/translation` | `POST /translate` | [2026-08-14](#2026-08-14) |
| `GET /organizations` | `GET /workspaces` | [2026-08-14](#2026-08-14) |
| `GET /transcriptions` | `GET /conversations` | [2026-08-31](#2026-08-31) |
| `GET /transcriptions/:id` | `GET /conversations/:id` | [2026-08-31](#2026-08-31) |
| `PATCH /transcriptions/:id` | `PATCH /conversations/:id` | [2026-08-31](#2026-08-31) |
| `DELETE /transcriptions/:id` | `DELETE /conversations/:id` | [2026-08-31](#2026-08-31) |
| `GET /transcriptions/:id/summary` | `GET /conversations/:id/summary` | [2026-08-31](#2026-08-31) |
| `GET /transcriptions/:id/convert_to_subtitles` | `GET /conversations/:id/convert_to_subtitles` | [2026-08-31](#2026-08-31) |

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** <Badge>Breaking</Badge>

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** <Badge>Breaking</Badge>

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** <Badge>Breaking</Badge>

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** <Badge>Breaking</Badge>

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** <Badge>Breaking</Badge>

Members no longer carry `policy`: read a member's policy with `GET /policies/{member id}`.

**Calendar connections without a method** <Badge>Breaking</Badge>

Calendar connections no longer carry `method`, which was always `user_consent`.

**Brand kits in groups** <Badge>Breaking</Badge>

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.

| Before | Now |
| - | - |
| `bot_name` | `bot.name` |
| `background_color`, `logo_scale`, `logo_url` | `bot.tile.type` and its `color` or `logo` object |
| `custom_thumbnail_url` | `bot.tile.image.file` |
| `send_chat_message`, `chat_message`, `chat_message_paused` | `bot.chat.enabled`, `bot.chat.recording_message`, `bot.chat.paused_message` |
| `apply_branding_to_summary_emails` | `email.type` |
| `brand_color`, `brand_url`, `brand_description`, `logo_url` | `email.branded.color`, `.website`, `.description`, `.logo` |

* **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](/api-reference/files/object): 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** <Badge>Breaking</Badge>

`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](/api-reference/calendar-events/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`** <Badge>Breaking</Badge>

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** <Badge>Breaking</Badge>

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](/api-reference/files/object) 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](/api-reference/brand-kits/object), 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](/api-reference/members/object), 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** <Badge>Breaking</Badge>

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** <Badge>Breaking</Badge>

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](/api-reference/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** <Badge>Breaking</Badge>

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`** <Badge>Breaking</Badge>

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** <Badge>Breaking</Badge>

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](/meetingkit/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](/api-reference/errors), 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`.

**Brand kit logos look right for any upload**

* **Changed** how summary emails and the notetaker's tile show a brand kit's logo. The upload
  is trimmed of its padding, sized by its shape in the email (wordmarks 40 px tall, square marks
  48, tall marks 64, never wider than 220) and served as a crisp 2x PNG; a light logo on a
  transparent background sits on a chip in the kit's `brand_color`; an SVG is rasterized. On the
  tile, `logo_scale` now scales the mark and not its padding. The image URLs keep returning the
  file you uploaded.

### 2026-09-30

**Policies** <Badge>Breaking</Badge>

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** <Badge>Breaking</Badge>

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** <Badge>Breaking</Badge>

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** <Badge>Breaking</Badge>

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](/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](#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** <Badge>Breaking</Badge>

* **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](#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](/meetings/consent) 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](#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](#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](#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** <Badge>Breaking</Badge>

* **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](/api-reference/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](/company-memory/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** <Badge>Breaking</Badge>

* **Breaking** The `vocabulary` parameter was removed from transcription create. Custom
  terminology returned later as [glossaries](#2026-02-09).

### 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** <Badge>Breaking</Badge>

* **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** <Badge>Breaking</Badge>

* **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.
