The same text as Markdown, for AI agents and other systems: https://deep-thought.cloud/api/reference.md · API overview
Deep Thought Cloud API reference
Last updated: 2026-10-08, server 2.37.x. Overview: https://deep-thought.cloud/en/api/. This file: https://deep-thought.cloud/api/reference.md (Markdown, for people and for AI agents).
Deep Thought Cloud turns recordings and documents into a searchable knowledge base: transcripts, speakers, summaries, people and organisations, actions and insights. This reference covers what an integration needs: authentication, uploading, waiting for processing, and reading the results. It is a curated subset of the API; everything not listed here is internal or may change without notice.
Contents
- Basics
- Authentication
- Plans
- Rate limits
- Errors
- Quick start
- Uploading
- Processing status
- Reading
- Meeting notetaker
- MCP
- Endpoint summary
Basics
| Base URL | https://api.deep-thought.cloud/api/v1 |
| Format | JSON in and out, except uploads (multipart/form-data) |
| Times | ISO-8601, UTC |
| Transport | HTTPS only |
Every path below is relative to the base URL. A source (a recording, document or text) is
identified by its key, called source_id or cache_key in responses (for example
DOC_20261001_044444_0367b3dd_82afbf5d).
Authentication
Send a token on every request:
Authorization: Bearer <token>
Device token (use this today)
A device token starts with dtd_. Deep Thought issues it for your account; it is shown once, so
store it as a secret. It does not expire by default and can be revoked at any time in the Web App
under Settings, Devices. Revocation takes effect on the next request.
A device token carries scopes:
| Scope | Allows |
|---|---|
kb:read |
Every GET request, and POST /search |
kb:write |
Everything else: uploads, tags, actions, the meeting notetaker. Implies kb:read |
A request outside the token’s scopes answers 403 {"error": "insufficient_scope", "required": ["kb:write"], ...}.
A token with only kb:read cannot upload and cannot use POST /chat.
API keys (Enterprise, coming)
API keys are built for server integrations and will replace device tokens for them: one key per
system, scoped, revocable, created by an account owner or admin in the Web App. A key looks like
dtk_<key_id>_<secret> and is sent the same way (Authorization: Bearer dtk_...). They are not
switched on yet and no date is set. When they are, moving from a device token to a key changes
only the token you send.
Passwords from scripts are being retired
Signing in with e-mail and password (POST /auth/login) and HTTP Basic are for the Deep Thought
apps. Do not build an integration on them. Using them from a script is being retired for every
plan: the planned dates are a notice on 2026-10-16 and the end on 2026-11-16. From the end date a
script gets 401 {"error": "basic_auth_retired"} or 401 {"error": "password_login_app_only"}.
Until then such responses may carry Deprecation and Sunset headers.
Plans
Each feature belongs to a plan: Free, Premium or Enterprise. The endpoints below are marked with
the lowest plan that includes them. A request for a feature your plan does not include answers
402 with {"error": "plan_required", "plan": "premium", "feature": "<feature>"}. The current
plan contents are published, without authentication, at GET /public/plans and
GET /public/features.
| Plan | Includes (API view) |
|---|---|
| Free | Audio and video upload, transcripts, speakers, tags, listing and reading your sources |
| Premium | Free, plus text, document and image upload, summaries and structured extraction, people and organisations, actions, insights and verticals, semantic search and chat, MCP, the meeting notetaker |
| Enterprise | Premium, plus API keys for your own systems (when switched on), integrations, multi-user accounts |
Rate limits
Limits are counted per client IP address and per endpoint, in fixed windows:
| Endpoints | Limit |
|---|---|
Authenticated requests, /sources/upload included |
50 per second |
Sign-in requests (/auth/*) |
5 per minute |
Every response carries the current state:
| Header | Meaning |
|---|---|
X-RateLimit-Limit |
The limit for this endpoint in the current window |
X-RateLimit-Remaining |
Requests left in the window |
X-RateLimit-Reset |
When the window resets, in Unix epoch seconds |
Retry-After |
Seconds to wait before retrying |
Over the limit the answer is 429 with a Retry-After header and
{"error": "rate_limit_exceeded", "retry_after": <seconds>, "limit": "50 per 1 second"}.
Wait at least retry_after seconds before the next request.
Errors
An error answers with an HTTP status and a JSON body with an error field: a short code
(plan_required) or a sentence. Many add message. Read the status code first.
| Status | Meaning | What to do |
|---|---|---|
400 |
The request is malformed or a required field is missing | Fix the request |
401 |
No token, an unknown or revoked token | Check the Authorization header |
402 |
plan_required: the feature is not in your plan |
See Plans |
403 |
insufficient_scope (token scopes), or not_allowed (owner or admin only) |
Use a token with the scope |
404 |
Not found, or not yours. For a source key, see the key can change | |
409 |
Conflict, for example a duplicate | |
413 |
The file is larger than the upload limit (250 MB). Refused before it reaches the API, so the body may not be JSON | Split or compress the file |
415 |
The file type is not accepted, or its content does not match its extension (file_validation_failed) |
|
429 |
rate_limit_exceeded |
Wait Retry-After seconds |
5xx |
A server-side problem. 503 may carry Retry-After |
Retry with backoff |
Quick start
Upload a recording, wait for the transcript, fetch it. Set DT_TOKEN to your token first.
export DT_TOKEN="dtd_..." # your token; never commit it
API=https://api.deep-thought.cloud/api/v1
# 1. Upload. A new file answers 202 with a task_id.
TASK=$(curl -s -X POST "$API/sources/upload" \
-H "Authorization: Bearer $DT_TOKEN" \
-F "file=@meeting.m4a" -F "language=sv" -F "tags=customer-x" \
-F "content_source=meeting" \
-F 'content_source_metadata={"meeting_started_at":"2026-10-08T09:00:00Z"}' \
| jq -r '.task_id // empty')
# 2. Wait for the source key, then for the transcript.
until KEY=$(curl -s "$API/sources/tasks/$TASK" -H "Authorization: Bearer $DT_TOKEN" \
| jq -r '.result.cache_key // .cache_key // empty') && [ -n "$KEY" ]; do sleep 10; done
until [ "$(curl -s "$API/sources/$KEY/status" -H "Authorization: Bearer $DT_TOKEN" \
| jq -r '.status.transcription.status')" = "completed" ]; do sleep 30; done
# 3. Fetch the source: transcript, title, summary and extraction.
curl -s "$API/sources/$KEY" -H "Authorization: Bearer $DT_TOKEN" \
| jq '.result | {title: .llm_title, text: (.diarization_text_named // .diarization_text // .text)}'
If the upload answers {"status": "duplicate_upload", "cache_key": ...} instead, the account
already has this file and cache_key is its key; skip to step 2’s second loop.
Uploading
POST /sources/upload
multipart/form-data. Scope kb:write.
| Field | Required | Description |
|---|---|---|
file |
Yes | The file. At most 250 MB |
language |
No | Language hint, for example sv or en. Default: detected |
tags |
No | Comma-separated tags, for example customer-x,q4 |
content_source |
No | What the source is: meeting, voice_memo, interview, phone_call, lecture, presentation, podcast or email. Any other value is ignored |
content_source_metadata |
No | A JSON object, at most 16 KB, stored with the source. Malformed JSON or a larger value is ignored, never refused |
File types and plans
| Kind | Extensions | Plan |
|---|---|---|
| Audio | .mp3, .m4a, .wav, .aac, .flac, .ogg, .webm, .caf |
Free |
| Video (the audio is transcribed) | .mp4, .m4v, .mov, .mkv, .avi, .webm |
Free |
| Text | .md, .markdown, .txt, .vtt |
Premium |
| Documents | .pdf, .docx, .doc |
Premium |
| Images (text is read from the image) | .jpg, .jpeg, .png, .gif, .webp |
Premium |
A text file that contains code (a pasted snippet in meeting notes, for example) is still accepted
as text. Below Premium a text, document or image upload answers 402 with
"feature": "non_audio_sources".
Dating a source. By default a source is dated by its own metadata (an embedded recording time, or a date in the file name), else by the upload time. To give the date yourself, for example when you upload a meeting after the fact, send:
content_source=meeting
content_source_metadata={"meeting_started_at": "2026-10-08T09:00:00Z"}
meeting_started_at is ISO-8601 in UTC, with the Z; a value without an offset is read as UTC.
This works for audio, video, text and documents. The source’s date is then returned as
recording_started_at, and recording_time_source says which rule set it (treat an unknown
value as “the upload date”). The source list is still ordered by upload time.
The size limit. GET /info reports the limit that is enforced: limits.max_file_size in
bytes (262144000) and limits.max_file_size_mb ("250MB"). GET /auth/user-limits reports the
same number for your account. A larger file answers 413.
Response. A new file answers 202:
{"status": "queued", "task_id": "abc123-def456", "queue_position": 3,
"estimated_wait_time": 300, "filename": "meeting.m4a"}
It carries no source key yet; get it from GET /sources/tasks/<task_id>. A file the account
already holds answers {"status": "duplicate_upload", "cache_key": "...", "transcription_status": "...", "title": "..."}.
POST /sources is the same endpoint under a second path.
Processing status
GET /sources/tasks/<task_id>
The upload task. status is pending, processing, completed (with result, which carries
cache_key) or failed (with error). Poll it until you have the source key.
GET /sources/<source_id>/status
Where each processing stage of a source stands:
{"success": true, "cache_key": "DOC_...",
"status": {
"transcription": {"status": "completed"},
"llm_extraction": {"status": "completed"},
"aci": {"status": "pending", "processed": false},
"diarization": {"status": "completed", "speaker_count": 3},
"semantic_index": {"status": "pending", "indexed": false}}}
Each stage is pending, processing or completed.
The transcript is ready when transcription.status is completed; aci is the structured
extraction (Premium) and finishes later. The Free plan never runs aci, so do not wait for it there.
Polling advice. Poll every 15 to 30 seconds, not faster. Processing time grows with the
recording’s length and the queue; for a long recording, wait minutes rather than seconds before
the first status call. Respect Retry-After on a 429. There is no push notification of completion
today: poll.
The key can change
When transcription finishes, a source can be stored under a new DOC_... key and the upload’s
key then answers 404. Use the key from the task result (result.cache_key), and on a 404
for a source you just uploaded, upload the same file again: it answers duplicate_upload with
the current key, without processing it twice.
Reading
All reads need kb:read.
Sources
GET /sources (Free). Your sources, newest upload first.
| Parameter | Description |
|---|---|
limit |
Default 50, at most 100 |
before |
Paging: pass the previous page’s next_cursor to get older items |
after |
A cursor: only items newer than it (for refreshing) |
tag |
One tag. _untagged returns sources without tags |
kind |
recording, video, document, email, podcast, integration or other |
content_source |
For example meeting or voice_memo |
client |
The uploading client, for example api or trillian |
person |
A speaker’s person_id (from GET /speakers/persons) |
include_deleted |
true to include sources in the Trash |
The response is {"files": [...], "count": n, "has_more": bool, "next_cursor": "..."}. Page by
passing next_cursor as before until has_more is false.
GET /sources/<source_id> (Free). One source in full, under result: the transcript and
everything extracted from it.
| Field | Contents | Plan |
|---|---|---|
text |
The plain transcript | Free |
diarization_text |
The transcript with speaker labels (SPEAKER_00, …) |
Free |
diarization_text_named |
The transcript with speaker names, where they are known | Free |
diarization_speakers_named |
Label to name map | Free |
original_filename, language, duration, tags, content_source, recording_started_at |
Metadata | Free |
llm_title, llm_keywords |
Title and keywords | Free |
llm_summary |
Summary | Premium |
people, organizations, locations |
Who and what is mentioned | Premium |
actions |
Actions and decisions found in the source | Premium |
For “who said what”, read diarization_text_named, then diarization_text, then text.
GET /sources/<source_id>/info (Free). Metadata only, without the transcript; cheaper for
lists.
POST /sources/<source_id>/tags, DELETE /sources/<source_id>/tags (Free, kb:write).
Body {"tags": ["customer-x"]}.
Search and chat
POST /search (Premium). Semantic search across your sources. Allowed with kb:read.
{"query": "what was decided about the launch date", "n_results": 5, "tag_filter": ["customer-x"]}
n_results is at most 20. Each result carries transcription_id (the source key), title,
text (the matching passage) and score.
POST /chat (Premium, kb:write). A question answered from your sources, with the sources it
used. Body {"message": "...", "tag_scope": "customer-x", "conversation_id": "..."};
tag_scope and conversation_id are optional (send the returned conversation_id back to
continue a conversation). The answer is message.content, the sources message.sources.
Tags and speakers
| Endpoint | Plan | Returns |
|---|---|---|
GET /tags |
Free | The tags used on your sources |
GET /tags/definitions |
Free | Your tag definitions, with usage counts |
GET /speakers/persons |
Free | The people your account knows by voice: person_id, display_name, aliases, doc_count |
GET /transcriptions/<source_id>/speakers |
Free | The speakers of one source and suggested names |
People, organisations and topics
| Endpoint | Plan | Returns |
|---|---|---|
GET /entities |
Premium | People, organisations, projects and topics across your sources. Filters type, tag |
GET /entities/<entity_id> |
Premium | One of them: name, aliases, type, how often it appears |
GET /entities/<entity_id>/related |
Premium | What it is connected to, and the sources it appears in |
Insights and verticals
| Endpoint | Plan | Returns |
|---|---|---|
GET /insights |
Premium | Insights: how a tag’s content has developed over time. Filters tag_slug, status |
GET /insights/<insight_id> |
Premium | One insight |
GET /contexts |
Premium | Your verticals: the running picture of each tag, feed and person |
GET /contexts/<scope_type> |
Premium | The verticals of one type: tag, feed or person |
GET /contexts/<scope_type>/<scope_key> |
Premium | One vertical in full, with its summary |
Actions
| Endpoint | Plan | Does |
|---|---|---|
GET /actions |
Premium | Your actions. Filters status (pending, completed, cancelled), priority, limit (at most 500) |
GET /actions/<action_id> |
Premium | One action |
POST /actions/create |
Premium, kb:write |
Create an action. Body {"description": "...", "priority": "high", "due_date": "2026-10-15", "assigned_to": "..."}; only description is required |
PUT /actions/<action_id> |
Premium, kb:write |
Update one, for example {"status": "completed"} |
Below Premium, GET /actions still returns existing actions with
{"plan": {"available": false}}, and creating one is refused.
Account
| Endpoint | Plan | Returns |
|---|---|---|
GET /info |
Free | The API, your account, and the upload limit in force |
GET /auth/user-limits |
Free | The largest upload your account can make |
GET /public/plans, GET /public/features |
No token needed | What each plan includes |
Meeting notetaker
Premium and Enterprise. The notetaker joins a Zoom, Google Meet or Teams meeting, records it, and the recording becomes a source like any upload. Connecting a calendar is done in the Web App (Settings); the API reads the calendars and the meetings.
POST /recall/bots (kb:write). Send the notetaker to a meeting now.
curl -s -X POST "$API/recall/bots" -H "Authorization: Bearer $DT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"meeting_url": "https://meet.google.com/abc-defg-hij"}'
meeting_url is the full meeting link. Answers 201 {"ok": true, "bot": {...}}; the notetaker
joins within seconds. 400 meeting_url_required, 402 plan_required (or cap_reached when the
month’s meeting hours are used), 502 create_bot_failed.
| Endpoint | Returns |
|---|---|
GET /recall/bots |
Your notetakers and their status. ?limit= at most 200. A finished one names its recording (cache_key) |
GET /recall/bots/<bot_id> |
One notetaker |
DELETE /recall/bots/<bot_id> |
Removes one that has not joined yet, or takes it out of the meeting. The recording is kept |
GET /recall/upcoming |
Upcoming calendar meetings and whether the notetaker will join each: decision, decision_reason. ?days= (default 14, at most 28). Read-only for integrations |
GET /recall/calendars |
Your connected calendars: provider, status (active, or disconnected, which needs reconnecting in the Web App) |
MCP
AI assistants (Claude and other MCP clients) connect to the same knowledge base through MCP.
| Server | https://mcp.deep-thought.cloud/mcp (Streamable HTTP) |
| Authentication | OAuth 2.1 with dynamic client registration: the client opens a browser, you sign in and approve. The client then holds a token revocable in the Web App under Settings, Devices |
| Scopes | kb:read (default), kb:write (opt in), graph:read (opt in) |
| Plan | Premium |
| Tool | Scope | Does |
|---|---|---|
ping |
none | Check the connection and that the token is valid |
ask |
kb:read |
A question in plain language, answered from your sources, with citations |
search |
kb:read |
Semantic search across your sources, optionally narrowed by tag |
list_sources |
kb:read |
Your sources, most recent first, optionally by tag |
get_source_extraction |
kb:read |
One source, structured: what it is, who and what it mentions, its decisions, actions, quotes and open questions. No transcript |
get_source_transcript |
kb:read |
One source’s full transcript, with speaker names where they are known |
list_speakers |
kb:read |
The people your account knows by voice |
list_tags |
kb:read |
The tags you have set on your sources |
list_entities |
kb:read |
The people, organisations, projects and topics across your sources |
get_entity |
kb:read |
One of them: name, aliases, type and how often it appears |
get_entity_related |
kb:read |
What it is connected to, and the sources it appears in |
get_entity_neighborhood |
graph:read |
Its connections one to three steps out |
graph_search |
graph:read |
Find people, organisations and topics by name |
list_verticals |
kb:read |
The verticals that have grown from your tags, feeds and people |
get_vertical_summary |
kb:read |
One vertical’s current picture across all its sources |
list_insights |
kb:read |
Insights: how a tag’s content has developed, in time order |
get_insight |
kb:read |
One insight |
create_action |
kb:write |
Create an action item. The only tool that writes |
Endpoint summary
| Method | Path | Plan | Scope |
|---|---|---|---|
POST |
/sources/upload (also /sources) |
Free (audio, video); Premium (text, documents, images) | kb:write |
GET |
/sources/tasks/<task_id> |
Free | kb:read |
GET |
/sources/<source_id>/status |
Free | kb:read |
GET |
/sources |
Free | kb:read |
GET |
/sources/<source_id> |
Free (extraction fields Premium) | kb:read |
GET |
/sources/<source_id>/info |
Free | kb:read |
POST, DELETE |
/sources/<source_id>/tags |
Free | kb:write |
POST |
/search |
Premium | kb:read |
POST |
/chat |
Premium | kb:write |
GET |
/tags, /tags/definitions |
Free | kb:read |
GET |
/speakers/persons |
Free | kb:read |
GET |
/transcriptions/<source_id>/speakers |
Free | kb:read |
GET |
/entities, /entities/<entity_id>, /entities/<entity_id>/related |
Premium | kb:read |
GET |
/insights, /insights/<insight_id> |
Premium | kb:read |
GET |
/contexts, /contexts/<scope_type>, /contexts/<scope_type>/<scope_key> |
Premium | kb:read |
GET |
/actions, /actions/<action_id> |
Premium | kb:read |
POST |
/actions/create |
Premium | kb:write |
PUT |
/actions/<action_id> |
Premium | kb:write |
POST |
/recall/bots |
Premium | kb:write |
GET |
/recall/bots, /recall/bots/<bot_id> |
Premium | kb:read |
DELETE |
/recall/bots/<bot_id> |
Premium | kb:write |
GET |
/recall/upcoming, /recall/calendars |
Premium | kb:read |
GET |
/info, /auth/user-limits |
Free | kb:read |
GET |
/public/plans, /public/features |
None (no token) | none |
Questions: support@deep-thought.cloud.