Kalima REST API v1
Read your recordings, transcripts, AI summaries, tasks and moments, upload audio, and organize recordings.
The Kalima REST API lets your own integration read the recordings you own, with their transcripts, AI summaries, tasks and moments, upload audio files for transcription, and organize recordings: projects, names, speakers, share links and summaries.
Authentication
There are two ways in:
- API key (Pro, Team and Enterprise). For your own scripts and integrations. Send the key as a bearer token with every request.
- OAuth (Plus and up). Used by the automation platforms Kalima registers, which ask you to approve the connection in a Kalima window. You can remove such a connection at any time under Settings, Integrations, Connected apps.
Authorization: Bearer kal_xxxxxxxxxxxxxxxxxxxx
Create a key under Settings, Integrations, API keys. Kalima shows it only once, so store it securely. Step by step, including n8n and Make: Automate with Zapier, n8n or Make.
An API key carries the read scope, the write scope or both. read covers every list, search, feed and export; add write only when the integration uploads audio or changes recordings and projects. Every request acts as the account the key belongs to and sees only the recordings that account owns.
Endpoints
Base URL: https://meetkalima.com/api/v1
| Method and path | Purpose |
|---|---|
GET /me | The account the credential belongs to |
GET /recordings | List recordings, one page at a time |
POST /recordings | Upload audio as a new recording |
GET /recordings/search | Find recordings by words in their name or transcript |
GET /recordings/{id} | One recording with its transcript text and AI summary |
PATCH /recordings/{id} | Rename a recording or move it |
GET /recordings/{id}/transcript | The transcript as segments with speaker and start time |
GET /recordings/{id}/speakers | The speakers of a recording |
PUT /recordings/{id}/speakers | Name speakers |
POST /recordings/{id}/summary | Create the AI summary |
GET /recordings/{id}/export | The recording as a file |
POST /recordings/{id}/share-link | Turn on the share link |
GET /summaries | Feed of new AI summaries |
GET /action-items | Feed of tasks from AI summaries |
GET /moments | Feed of moments marked while recording |
GET /projects | List projects |
POST /projects | Create a project |
GET /workspaces | List workspaces |
/admin/... | Manage your company's members, teams and policies (company keys with admin scopes, see below) |
GET /openapi.json | The machine-readable OpenAPI contract |
Every recording carries url, the link that opens it in Kalima, and the project and workspace it is filed in.
List recordings
By default GET /recordings returns the finished feed. A recording enters it once, as soon as nothing is being recorded into it any more and its transcript has not changed for two minutes. It keeps its place after that, even when you later correct the transcript, and it does not enter the feed a second time if more is recorded into it. The newest entries come first, and each carries finishedAt, the moment it entered the feed. That makes the feed safe to poll: act on each id once, and you never pick up a transcript that is still growing. An uploaded file that turns out to contain no speech enters it too, with segmentCount 0, so the integration that uploaded it hears back. The first time you read it, it reaches back seven days.
Add status=all to list every recording in any state, newest first, or status=failed for only the uploads whose transcription failed. Each recording has a status of recording, processing, failed or ready, and updatedAt says when its transcript or speakers last changed. projectId or workspaceId narrow any of these lists.
Lists are paginated. Pass the nextCursor of a response back as cursor until it is null. limit sets the page size (1 to 100, default 25).
To fetch only what is new, pass the newest sequence you have seen as afterSequence. Each feed item carries one, and unlike finishedAt (which finishedAfter takes) it stays exact when many recordings enter the feed in the same millisecond. Add include=summary or include=transcript,summary to get the content with each item; with the transcript a page holds at most 10 recordings.
GET /recordings/search?q=budget finds recordings whose name or transcript contains the words, best match first, each with a snippet of where the words were found. limit goes up to 25 (default 10), and projectId or workspaceId narrow the search.
Read a recording
GET /recordings/{id} returns the recording with transcriptText (plain lines of the form Speaker: text) and summary. The summary holds an overview, key points, action items with owner and due date, decisions, open questions and the full summary as Markdown. It is null until a summary has been created for the recording.
Use include=transcript, include=summary or an empty include= to fetch less. A transcript longer than 500,000 characters is cut there, and transcriptTruncated is then true. GET /recordings/{id}/transcript returns the complete transcript as segments.
GET /recordings/{id}/export?format=srt returns the recording as a file, the same file the Export dialog in Kalima downloads, with its name in the Content-Disposition header. format is txt, md, srt, vtt, json or summary-md. For a recording with a translation, track=translation exports the translation in md, srt and vtt.
Feeds
Three more feeds work like the finished feed: newest first, every item once, with cursor to page back and createdAfter to ask only for what entered after a given moment (on the summary and task feeds also afterSequence, the exact way). limit goes up to 50 (default 25), and projectId or workspaceId narrow each feed.
GET /summarieshas one item each time a recording gets its AI summary, and another when the summary is regenerated or edited. Each item carries the summary in full and aversion: 1 for a recording's first summary, then 2, 3 and so on. Renaming or moving a recording does not bring its summary back. The feed holds the summaries saved in the last seven days.GET /action-itemshas one item per task in those summaries, withownerandduewhen the summary names them. A regenerated summary that keeps a task keeps itsid, so the task is not delivered twice. Herelimitcounts summaries, not tasks.GET /momentshas the moments marked during recordings, with their AI title and their position in the recording. A moment waits up to five minutes for its title before it is listed;listedAtsays when it was, andcreatedAftercompares against it.
Organize recordings
These need the write scope.
- Rename or move:
PATCH /recordings/{id}withname, and eitherprojectIdorworkspaceId(not both). A project move also puts the recording into that project's workspace, like in the app, and needs edit rights in the project; without them the answer isPROJECT_ROLE_REQUIRED.projectId: nulltakes it out of its project. A refused move changes nothing, not even the name. - Name speakers:
PUT /recordings/{id}/speakerswith{ "names": { "Speaker 1": "Anna Weber" } }. Each key is a label as the transcript shows it, a name already given, or a key fromGET /recordings/{id}/speakers. An empty name removes the name. A key that matches no speaker is refused withSPEAKER_NOT_FOUND. - Create the summary:
POST /recordings/{id}/summaryanswers with the summary when one exists. Otherwise it answers202withstatus: "queued", and the summary arrives within a minute or two inGET /summariesand on the recording. It uses one AI summary from your plan, like the Generate Summary button. When the plan's summaries are used up, it answersQUOTA_EXCEEDEDinstead of queuing, and while a file is still being transcribed into the recording,RECORDING_NOT_READY. - Share:
POST /recordings/{id}/share-linkturns on the recording's share link and returns it. A recording has one link, so calling this again returns the same one. A new link opens for anyone signed in to Kalima who has it; your organization's sharing rules apply, and you set wider or narrower access in the app. - Projects:
GET /projectslists the projects you own or that are shared with you, newest first;namefinds names that contain it, case and accents ignored, and withexact=trueonly names equal to it.editable=truekeeps the projects you may file recordings into; every project carriescanEdit.POST /projectscreates one fromnameand, optionally,description,workspaceIdandparentProjectId(one level deep). Your plan's project limit applies (PLAN_LIMIT_REACHED).GET /workspaceslists your workspaces.
Upload audio
Send multipart/form-data with a file field. Optionally add:
| Field | Meaning |
|---|---|
name | The recording's name; the file name otherwise |
durationMs | A duration hint |
projectId | File it into this project (edit rights needed). The project's transcription context applies unless you send context or terms |
workspaceId | Or file it into this workspace, without a project |
languages | Up to three spoken languages as ISO codes, e.g. de,en |
translateTo | One language code to translate into alongside |
context | What the conversation is about, up to 10,000 characters |
terms | Names and words to recognize, separated by commas or line breaks |
curl -X POST "https://meetkalima.com/api/v1/recordings" \
-H "Authorization: Bearer kal_xxx" \
-F "file=@meeting.mp3" \
-F "name=Weekly sync" \
-F "languages=de,en"
The response (202) contains the new recording's id with the status processing. Poll GET /recordings/{id}?include= until the status is ready or failed. The transcribed duration is charged to the account's transcription balance.
Manage your company (admin API)
For companies that do not use SCIM: a company API key with admin scopes manages your company's members, teams and policies. An owner or admin creates it in the Kalima Console under Integrations & API, Create key, in the Company administration section, where each area can get no access, read access, or read and change access. See Integrations and webhooks.
| Scope | Endpoints |
|---|---|
admin:members:read | GET /admin/members, GET /admin/members/{userId} |
admin:members:write | PATCH /admin/members/{userId}, POST /admin/invitations |
admin:teams:read | GET /admin/teams, GET /admin/teams/{teamId} |
admin:teams:write | POST /admin/teams, PATCH /admin/teams/{teamId}, POST /admin/teams/{teamId}/members, PATCH and DELETE /admin/teams/{teamId}/members/{userId} |
admin:policies:read | GET /admin/policies |
admin:policies:write | PATCH /admin/policies |
The key acts as the person who created it, with their role, and these paths refuse it (ADMIN_ROLE_REQUIRED) as soon as that person is no longer an active owner or admin. The same rules apply as in the console: the seat limit, only an owner changes an owner, at least one active owner, and while your identity provider manages people and teams through SCIM, it suspends and reactivates those people and decides who is in those teams and what they are called (SCIM_MANAGED); roles and team leads stay with Kalima. A refusal carries the console's reason as code, for example SEAT_CAP_REACHED, OWNER_PROTECTED or LAST_OWNER. Every change appears in your company's audit log under "API key" and the key's name. These paths never return recordings, transcripts, or anyone's activity or usage. OAuth tokens (API_KEY_REQUIRED) and keys of private accounts (COMPANY_KEY_REQUIRED) are refused.
- Members:
GET /admin/memberslists active and suspended members, owners first;statusandemailnarrow the list, and it pages withcursorlike the other lists (limit1 to 500, default 100).PATCH /admin/members/{userId}makes one change per request:{ "role": "member" }(admin,memberorviewer) or{ "status": "suspended" }(oractive). Ownership changes hands only by a transfer the new owner accepts, started in the console's settings or in the app. Suspending a person keeps every recording where it is. - Invite:
POST /admin/invitationswithemail,roleand optionallyteamId. The person gets the usual invitation mail, which names the key's creator as the inviting person. - Teams:
POST /admin/teamswithnameand optionallydescription;PATCH /admin/teams/{teamId}renames it.POST /admin/teams/{teamId}/memberswith{ "userId": "...", "role": "lead" }puts an active member in,PATCH /admin/teams/{teamId}/members/{userId}with{ "role": "member" }changes the team lead, andDELETEtakes the person out of the team. Deleting a team stays in the console. - Policies:
GET /admin/policiesreturns the sections of the console's Policies page (data,ai,sharing,members,recording,projectSharing).PATCHtakes the same sections and merges each over what is stored, so a setting you leave out stays as it is. The deletion rules are changed in the console only, because a second factor is needed there: deleting recordings and audio automatically is not part of the body, and aPATCHthat would switch zero data retention on or off is refused withCONSOLE_ONLY.
The request quota is the same as for every other call. If your company allows Kalima only from certain networks, whether for the console alone or for everything, these paths answer NETWORK_NOT_ALLOWED from anywhere else. See Allowed networks.
On a company account, the company's rules also apply to ordinary API keys: if Zapier and the public API or API keys is turned off in the console's Policies, requests with keys of that company's accounts are refused with 403 and ORG_POLICY_BLOCKED. See Policies.
Errors and limits
Every error has the same shape:
{ "error": { "code": "RECORDING_NOT_FOUND", "message": "Recording not found" } }
| Status | Meaning |
|---|---|
400 | Invalid parameter or unsupported audio file |
401 | Missing, invalid or expired credentials, or a removed connection |
402 | No transcription time left for an upload |
403 | Missing scope, a plan without access, a plan limit reached (QUOTA_EXCEEDED, PLAN_LIMIT_REACHED), no edit rights in a project (PROJECT_ROLE_REQUIRED), or a request from a network your company does not allow (NETWORK_NOT_ALLOWED) |
404 | No such recording, project or workspace for this account |
409 | Not possible right now, for example a summary for a recording that is still being transcribed |
413 | The file is too large |
429 | API key request quota or OAuth rate limit reached; retry later |
503 | Briefly unavailable; retry after a short wait |
Revoke a key immediately if it may have been exposed.