Skip to main content
Wire your own application to an agent through the conversation bridge’s API door: your system POSTs a message, the bridge runs an agent turn as the route’s execution key, and the answer comes back by a signed HTTPS callback (or inline on a bounded wait). See client conversations for the model.

Two identities: who-may-send vs what-the-agent-may-do

An API route separates two authorities, and you provision both:
  • The invoking key — the API key your system authenticates with when it POSTs a message. The access-control gate authorizes it for the route’s send door; it answers who may send.
  • The execution key — the identity the turn runs as. Its live grants are the turn’s entire authority; it answers what the agent may do.
They are independent. The sender never inherits the agent’s authority, and the agent never runs as the sender.
1

Mint the invoking key

A scoped key your system will send with. Scope it to reach the route and no more.
2

Mint the execution key

A least-privilege key the agent runs as (see owned keys).

Create the API route

An api route carries an HTTPS callback_url — the sink the answer is POSTed to. The URL must be absolute HTTPS; an http:// or relative URL is rejected at create.
The response carries a callback_secret — shown once, here, and never readable again. It signs every callback for this route. Capture it now and store it where your callback receiver can read it.

Send a message

POST to the send door with your invoking key. external_user_id is your handle for the end user — it becomes the conversation’s thread key, so the agent remembers that user’s earlier turns.
The default answer is 202 with the id to correlate the callback against:
The door refuses loudly rather than silently: 404 unknown route, 429 the address rate cap, 503 the thread queue is full, 501 no conversations backend configured, 400 a malformed body, 401 an unauthorized caller.

Sync variant: wait_seconds

Add wait_seconds to wait inline for a fast turn. A turn that finishes within the window answers 200 with the answer embedded and its callback suppressed (so it never double-fires); a turn still running when the wait elapses falls back to 202 and the answer arrives by callback as usual. The window is clamped to the deployment’s sync_wait_max_seconds cap.

Receive the callback

The bridge POSTs the answer to your callback_url as JSON. The body is the answer payload; correlate it by message_id:
On a turn that errored, the shape is identical but status is error and answer is generic client-safe text — never an internal detail:

Verify the X-Tai-Signature

Every callback carries an X-Tai-Signature header: sha256=<hex HMAC-SHA256(callback_secret, raw_request_body)>. Recompute it over the raw bytes and compare in constant time — reject any mismatch. This is what proves the callback came from your deployment and was not tampered with.
Compute the HMAC over the exact bytes you received, before any JSON re-encoding — re-serializing the body changes the bytes and breaks the signature. Reject a request whose signature does not verify; never act on an unverified callback.
Delivery is retried with backoff if your receiver is down, and a permanently undelivered answer ends failed (listed on tai conversations failed) rather than being lost.

See also