API Reference
- Where to find the full AutoTalk API documentation
- How to authenticate requests with your API key
- Key API endpoints and what they do
AutoTalk exposes a public REST API that lets external applications send messages to contacts and work with dynamic data (Dynadata). The API follows the OpenAPI 3.0 specification.
Live API documentation
The full, interactive API documentation is available at:
From there you can browse the documented endpoints, see request and response schemas, and try out calls directly in the browser. The published document does not cover quite everything that is live: where a route listed on this page is missing from it, this page says so on that route.
Authentication
Every request must carry one of three headers — with one exception, the public plan catalog listed below. All three headers resolve to the same company context, share the same rate limits (api_requests monthly meter), and grant access to the same endpoints — they differ only in how the token is issued.
x-api-key (recommended for external integrations)
A long-lived API key (format sk-…) created from the AutoTalk dashboard.
x-api-key: sk-YOUR_API_KEY
To generate one, go to Integrations > API Tokens and click the + button. See API Tokens for step-by-step instructions. This is the header you want for any CRM, helpdesk, script, or custom bridge calling AutoTalk from outside.
x-jwt-token (internal use by AutoTalk agents)
A short-lived JWT signed by AutoTalk and issued by the actions/security/auth/jwt/generate agent workflow action. The payload is {companyId, ips?, dur?, scopes?}; the default lifetime is 300 seconds, optionally bounded by an IP allow-list that is enforced against the caller's real public IP as seen by AutoTalk's edge (forwarded headers such as x-forwarded-for are not trusted).
Turn on advanced mode on that action to also request a scopes list, and the minted token is limited to those capabilities on /v1 and on the MCP server. A token minted without a scope list carries no scopes claim and therefore holds nothing — it authenticates, but every gate refuses it, so always list the capabilities your later steps need. A scope list supplied with advanced mode off is rejected (jwt_scopes_require_advanced) rather than silently dropped, and an unknown scope name is rejected at generation time (jwt_invalid_scopes) rather than ignored.
Every token AutoTalk signs now carries a kind claim naming what it is for, and /v1 accepts only kind: "v1" — a token issued for something else (an OAuth channel-connect redirect, for example) is rejected with 401. Tokens are minted per run and expire in minutes, so there is nothing to migrate. The requirement is unconditional: /v1 refuses a JWT whose kind claim is absent, malformed (present but not a usable string) or not "v1", on every request. There is no environment variable and no rollout step for operators to perform.
x-jwt-token: YOUR_JWT_TOKEN
Agents use this to call /v1/ on behalf of their own company — typically from a JavaScript code step (actions/code/execute with language: "javascript") that receives step(N).jwt as input. There is no user-facing endpoint to mint one; external integrators should use x-api-key instead.
A complete working example is available in Discord Spam Moderator — an actor agent that mints a JWT and uses it to delete Discord spam, warn offenders, and kick repeat violators.
x-auth-token (unified header)
A convenience header that accepts either form. Values starting with sk- are treated as API keys; other values are validated as JWTs (and if the value is ambiguous, JWT is tried first).
x-auth-token: sk-YOUR_API_KEY
# or
x-auth-token: YOUR_JWT_TOKEN
When multiple headers are present, x-jwt-token wins over x-api-key, and both win over x-auth-token.
Treat all three token types like passwords. Never commit them to source control or share them in public channels. If a token is compromised, revoke it (API keys) or wait for expiration (JWTs) and rotate.
Base URL
All API requests use the following base URL:
https://api.autotalk.io/v1
Key endpoints
Below is an overview of the main API areas. For full request/response details, visit the live documentation.
Company
| Method | Path | Description |
|---|---|---|
| GET | /v1/self | Retrieve the authenticated company's profile |
Contacts
| Method | Path | Description |
|---|---|---|
| POST | /v1/contacts/{contactId}/send_message | Send a message to a specific contact |
Dynadata (dynamic data)
Dynadata endpoints let you manage custom data entities (contacts, orders, tickets, or any type your company defines).
| Method | Path | Description |
|---|---|---|
| GET | /v1/dynadata/types | List all available Dynadata types |
| POST | /v1/dynadata/type/{type}/list | List items of a specific type |
| GET | /v1/dynadata/type/{type}/item/{_id} | Retrieve a single item by ID |
| POST | /v1/dynadata/type/{type}/create | Create a new item |
| POST | /v1/dynadata/type/{type}/update | Update an existing item |
| DELETE | /v1/dynadata/type/{type}/item/{_id} | Delete an item by ID |
| POST | /v1/dynadata/type/{type}/validate | Validate an item without saving |
| GET | /v1/dynadata/type/{type}/schema | Get the JSON schema for a type |
| GET | /v1/dynadata/type/{type}/schema/zod | Get the Zod schema for a type |
| POST | /v1/dynadata/type/{type}/executeFunction/{functionName} | Execute a function on a type |
| POST | /v1/dynadata/type/{type}/item/{_id}/executeFunction/{functionName} | Execute a function on a specific item |
log_entries is read-only through the APIcreate, update and delete on the log_entries type are refused on /v1 and on the MCP server for every caller — whatever permissions the API key holds, and for an x-jwt-token too. The response is 403 with code: "log_entry_writes_not_allowed". log_entries is the audit trail the platform writes about your API traffic, so no scope grants the ability to forge or erase rows in it. Reads (list, item, schema) are unaffected.
This is a behaviour change: these writes used to succeed. If you were writing your own records into log_entries, move them to a Dynadata type you own (a custom type, for example) — create/update/delete there are unchanged. The published Discord Spam Moderator example still contains such calls; they now fail, and the example is written so a failed log is non-fatal.
Self-hosted operators can set V1_ALLOW_LOG_ENTRY_WRITES=true on the backend to restore the old behaviour while they migrate an integration. It defaults to false and is not available on the hosted platform.
Storage
Upload files (images, PDFs, audio) and get a first-party {bucket, fullPath} reference you can pass to send_message, Dynadata document fields, or functions like createWhatsappWebEvoProduct. See Uploading files.
| Method | Path | Description |
|---|---|---|
| POST | /v1/storage/upload-url | Reserve a signed upload URL (step 1) |
| POST | /v1/storage/upload-complete | Finalize the upload; returns {bucket, fullPath} (step 2) |
| GET | /v1/storage/url | Get a signed download URL, valid up to 7 days, for a stored object |
Transcriptions
The standalone async transcription API (issue #822). Enqueue a transcription job for a stored audio file and poll for its status and result. Authenticated with your x-api-key.
| Method | Path | Description |
|---|---|---|
| POST | /v1/transcriptions | Enqueue a transcription job |
| GET | /v1/transcriptions/{id} | Fetch a job's status and result |
Media transforms
The standalone async media transform API (issue #247). Enqueue a format conversion for a stored audio or video file, then poll for its status and output. It is the same job queue the Transform media workflow action and the get_transcode_job MCP tool use, so a job enqueued on any surface can be polled from either of the two that read it: GET /v1/transcodes/{id} and the get_transcode_job tool — the workflow action only enqueues, it has no read. Reading is where the overlap stops: canceling and retrying exist only here on /v1. The MCP server registers no cancel and no retry tool, and the job document is read-only to the tenant, so a job started from a workflow or from MCP can be canceled or retried through these two routes and nowhere else.
| Method | Path | Description |
|---|---|---|
| POST | /v1/transcodes | Enqueue a transform job. fullPath and preset are required; bucket is optional and defaults to the platform storage bucket — a single bucket shared by every tenant, with your company's files under their own path prefix — so you rarely need to send it. Presets: mp4, ogg_opus, wav, flac_16k_mono |
| GET | /v1/transcodes/{id} | Fetch a job. job.output.bucket and job.output.fullPath are the converted file, and are filled in only once job.status is done |
| POST | /v1/transcodes/{id}/cancel | Cancel a job that has not finished yet — pending, probing, transcoding or finalizing. A job in any other state is refused |
| POST | /v1/transcodes/{id}/retry | Re-run a failed or canceled job from scratch. Any other status is refused, as is a job whose worker lease is still in the future — every path that writes failed or canceled clears the lease, so that second condition should not normally bite, and both refusals come back as the same transcode_job_not_retryable. The request goes through the same transcoding-quota and queued-jobs checks as enqueueing, and the conversion runs and is charged again unless that same source and preset were already converted, in which case the stored result is reused and nothing is billed |
The presets, the size and duration limits, the per-company queue cap and the failure codes are the same here as in the workflow action — see Transform media.
The published OpenAPI document behind api.autotalk.io/docs does not list the transform endpoints, so a client generated from it will not have them. The routes are live and supported; this table is the reference until the spec catches up.
Speech
Turn text into stored audio, and rewrite written text into its spoken form first. Neither endpoint returns audio bytes: POST /v1/speech stores the audio and hands back a {bucket, fullPath} reference you can pass straight to send_message, a Dynadata file field, or GET /v1/storage/url.
| Method | Path | Description |
|---|---|---|
| POST | /v1/speech | Synthesize text (at most 4096 characters) into a stored audio object. Optional voiceProfileId and format. Returns the stored reference plus size, format and which meter was billed |
| POST | /v1/spoken-text | Rewrite markdown (at most 8000 characters) into the spoken form of that text. Returns text only, no audio |
POST /v1/speech is synchronous — the audio exists when the call returns and there is no job to poll. Markdown reads badly out loud, so run /v1/spoken-text first when the text came from an LLM or a rich-text field. Its output is not bounded in characters, though — only in tokens (1200 of them), which usually lands well under the 4096-character synthesis cap but is not guaranteed to, least of all near the 8000-character input maximum. Check the length of the rewrite before posting it to /v1/speech, and split it if it came back longer.
Usage
| Method | Path | Description |
|---|---|---|
| GET | /v1/usage | The tenant usage snapshot: meter totals, compute time broken down by category, storage and database bytes used, the active plan with its resolved limits, and subscription period info |
Accepts granularity=month (the default) or granularity=day. The window is a UTC calendar period — the current month, or 00:00 UTC today with granularity=day — which is not necessarily your subscription period. The snapshot itself is the one the get_usage MCP tool returns — both surfaces read it from the same helper — but the envelopes differ: MCP hands the snapshot back bare, while /v1 wraps it as {success: true, usage}.
Workflows
Run a manual workflow first-class (issue #218) and read its durable outputs — the evaluated returnExpression as result, output files as storage refs (usable with GET /v1/storage/url), and per-step logs. Use mode: "async" for long runs (e.g. multiple Python code steps) and poll the run endpoint; pass includeStepOutputs: true to also persist per-step snapshots.
Always check ok, not the HTTP status. A step that fails does not abort the workflow, so a run can finish with broken steps: that answers status: "partial" with ok: false, an error of code workflow_steps_failed, and a failedSteps: [{step, code, userMessage}] list. Only status: "success" means the run did what it was supposed to.
When a failure is part of the design — a step whose failure a later step handles via step_error(N)/step_ok(N), or an optional side effect — tick allow failure on that step in the workflow editor. Its failure is then rolled up as {step, code, userMessage, handled: true} and reported exactly as before, but it no longer forces status: "partial": a run whose only failures are handled ones stays status: "success", ok: true. Any step failure that is NOT declared this way still makes the run partial.
| Method | Path | Description |
|---|---|---|
| POST | /v1/workflows/{id}/run | Run a workflow (mode: "sync" returns {ok, result, files, logs, executionTimeMs, runId}; mode: "async" returns 202 {runId}) |
| GET | /v1/workflows/{id}/runs/{runId} | Fetch a run's status and durable outputs |
Public (no authentication)
| Method | Path | Description |
|---|---|---|
| GET | /v1/public/plans | The public plan catalog. Pass sellableOnly=true to return only the plans currently on sale |
This is the only endpoint on this page that resolves no company and takes no auth header. Everything else needs one of the three headers above.
Like the transform endpoints above, this route is not in the published OpenAPI document either, so a client generated from it will not have this operation. The route is live; the spec has not caught up.
Example request
Here is an example of sending a text message to a contact using curl:
curl -X POST https://api.autotalk.io/v1/contacts/CONTACT_ID/send_message \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "text",
"body": {
"text": "Hello! How can we help you today?"
}
}'
Next steps
- API Tokens -- Generate and manage your API keys
- Webhooks -- Receive event notifications from AutoTalk
- Workflows -- Automate tasks inside AutoTalk