Skip to main content

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:

bash
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

GroupPathWhat 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 searchPOST /api/v1/knowledge/searchSemantic 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.
MCPPOST /api/v1/mcpThe MCP endpoint — same key, JSON-RPC instead of REST.
Webhook triggerPOST /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 /:

bash
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:

bash
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:

bash
# 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:

json
{ "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.

© 2026 Tale by Ruler GmbH — ISO 27001 & SOC 2 certified.

Tale is MIT licensed — free to use, modify, and distribute.