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

  1. Basics
  2. Authentication
  3. Plans
  4. Rate limits
  5. Errors
  6. Quick start
  7. Uploading
  8. Processing status
  9. Reading
  10. Meeting notetaker
  11. MCP
  12. 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.