API reference
How to call Tale from outside — authentication, the endpoint inventory, pagination, the async run and turn loops, and the error model. The single source of truth for the REST surface.
5 min read
The Tale API is the surface integrators use when they are outside the product and want to script it: knowledge resources, automations and their runs, chat threads, agents, and skills, all as JSON over HTTPS with an API key in a header. The same key also opens the MCP endpoint — this page covers the REST half.
This page is the canonical inventory of the surface, the auth model, and the error shape. Field-level request and response schemas live in the OpenAPI document your instance serves at /docs — load it there when you need every property; read this page to understand how the API behaves.
A worked request
The shortest useful request — list the organization's automations — is one curl:
curl -sS "https://your-host.example.com/api/v1/automations" \
-H "Authorization: Bearer $TALE_API_KEY"A successful response is a page: { "page": [ { "name": "billing/dunning", "latest": 3, "deployedVersion": 2 } ], "isDone": true, "continueCursor": null }. Every list endpoint answers this same envelope — pass continueCursor back as ?cursor= to fetch the next page, and cap page size with ?limit=.
Authentication
API keys are minted in the product by anyone with Admin or Developer permissions — API keys covers the panel. A key is shown once at creation and never again; it belongs to the user who minted it and to that user's organization.
Pass the key as a bearer token: Authorization: Bearer <key>. The organization context comes from the key — a key cannot be used outside its issuing organization, and everything the key touches is scoped there. What the key may do follows the key holder's role: reads and mock runs need membership, while starting live work and editing what is deployed needs the developer capability. Where that matters, the endpoint notes below say so.
Endpoint groups
| Group | Path | What it covers |
|---|---|---|
| Automations | /api/v1/automations/... | List, read versions, start runs, read run history, bind and unbind triggers. |
| Runs | /api/v1/runs/{runId} | One durable run in full — status, output, trace, effects — and POST .../cancel. |
| Threads | /api/v1/threads/... | The key holder's chat threads: create, read messages, send a message, poll the turn. |
| Agents | /api/v1/agents/... | List, read, create or replace, delete the organization's agents. |
| Skills | /api/v1/skills/... | Same shape as agents, for skills. |
| Knowledge entries | /api/v1/knowledge-entries/... | Topic-keyed facts: list, create, supersede, delete. |
| Knowledge search | POST /api/v1/knowledge/search | Semantic retrieval over the organization's indexed knowledge. |
| Documents | /api/v1/documents/... | Knowledge-base documents: CRUD plus POST .../retry-indexing. |
| Websites | /api/v1/websites/... | Crawled sources: CRUD plus .../pages, .../sync, .../search. |
| Products | /api/v1/products/... | Product catalog entries: CRUD. |
| Contacts | /api/v1/contacts/... | Contact records: CRUD plus POST /api/v1/contacts/bulk. |
| MCP | POST /api/v1/mcp | The MCP endpoint — same key, JSON-RPC instead of REST. |
| Webhook trigger | POST /api/automations/webhook/<token> | Start a deployed automation from outside; the Webhooks page. |
Automation names in URLs
An automation's name is a /-separated path — billing/dunning — and a path cannot travel inside one URL segment. In every /api/v1/automations/{name}/... URL, write the name with __ in place of each /:
curl -sS "https://your-host.example.com/api/v1/automations/billing__dunning/runs" \
-H "Authorization: Bearer $TALE_API_KEY"Responses always carry the real name ("name": "billing/dunning"); the __ form exists only in URLs. Agent and skill slugs are flat and need no encoding.
Start a run, then poll it
A run is durable and may take minutes, so starting one answers 202 with the run's identity, not its result:
curl -sS -X POST "https://your-host.example.com/api/v1/automations/billing__dunning/runs" \
-H "Authorization: Bearer $TALE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "input": { "customerId": "cus_123" } }'
# → 202 { "runId": "...", "version": 2, "name": "billing/dunning", "mode": "live" }Poll GET /api/v1/runs/{runId} until status leaves queued/running/waiting; the finished run carries output, the per-node trace, and the effects it produced. POST /api/v1/runs/{runId}/cancel stops a run at its next node boundary — work a node already completed is not undone.
mode defaults to live. A live run acts on the organization's behalf, so it needs a key whose holder has the developer capability; {"mode": "mock"} runs against deterministic mocks and needs only membership. Starting a run needs no trigger — the API key is the entitlement. An automation with no deployed version answers 409; deploy a version whose tests pass and the same call goes through.
projectId names the project the run operates in — the project its task and document tools act on. Omit it and the run is organization-wide, except that an automation bound to a single project runs in that one automatically; an automation bound to several accepts only a projectId among them, and refuses any other.
Send a message, then poll the turn
Chat is the same 202-then-poll shape. Create a thread, post a message, poll the generation, then read the messages:
# 1. A thread of your own
curl -sS -X POST "https://your-host.example.com/api/v1/threads" \
-H "Authorization: Bearer $TALE_API_KEY" \
-H "Content-Type: application/json" -d '{}'
# → 201 { "id": "<threadId>" }
# 2. Send a message — on this API the model is always explicit, never auto-selected
curl -sS -X POST "https://your-host.example.com/api/v1/threads/<threadId>/messages" \
-H "Authorization: Bearer $TALE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "content": "Summarise this quarter for me.", "model": "<a model your org has configured>" }'
# → 202 { "threadId": "...", "status": "accepted", "model": "...", "poll": "/api/v1/threads/<threadId>/generation" }
# 3. Poll until idle, then read
curl -sS "https://your-host.example.com/api/v1/threads/<threadId>/generation" \
-H "Authorization: Bearer $TALE_API_KEY"
# → 200 { "status": "streaming" } … then { "status": "idle" }{"status": "idle"} means no turn is running — read GET /api/v1/threads/{id}/messages for the reply. A turn that fails before producing output still surfaces: the failure lands as an assistant message carrying the error, never silently. Threads listed and read over the API are the key holder's own; a second user's threads are invisible to your key even inside the same organization.
Error model
Every non-2xx response carries one flat envelope:
{ "error": "Automation not found" }Branch on the HTTP status; the message is for humans:
- 400 — malformed request: a missing required field, a wrong type, an unparseable body.
- 401 — missing or invalid API key.
- 403 — the key is valid but its holder's role lacks the capability (live runs, trigger writes, cancels).
- 404 — the resource does not exist in your organization, or belongs to someone else's thread.
- 409 — the state refuses the action: no deployed version, a duplicate topic or email, a turn already running.
- 413 — the body is too large (the webhook trigger caps at 256 KB).
- 429 — rate limit exceeded; see Rate limits.
- 500 — internal error.
Two deletion semantics exist, on purpose. Unbinding an automation's trigger (DELETE .../triggers) answers 204 whether or not a trigger existed — it is an idempotent "make it so". Deleting a resource (DELETE /api/v1/agents/{slug}) answers 404 when nothing existed — you asked to remove a thing that is not there.
Versioning
The API is versioned by URL prefix — today /api/v1/ — and evolves additively inside it: new endpoints and new optional fields appear, existing shapes stay. A breaking change would ship under a new prefix. The OpenAPI document at /docs always describes the running instance.
Where this fits
This page is the REST half of the outside surface. The MCP endpoint exposes the same platform to MCP clients — automation authoring lives there, not in REST. The Webhooks page covers the inbound trigger that starts runs without a key. If you are building inside the product — agents, automations, custom tools — the Platform tab is your day-to-day; this page is for outside.