Beta
Core / Chat

Chat

Conversation endpoints with tool actions.

The chat surface mirrors the app's assistant: send a message, the response holds until the turn completes (or your timeout), then manage the exchange. One message object shape carries everything — the exchange and its turn.

The exchange object

Every chat response returns one exchange:

{
"data": {
"id": "msg_…",
"created_at": "2026-10-26T09:00:00Z",
"type": "text",
"text": { "body": "Summarize today in two lines" },
"attached_digest_id": null,
"is_completed": true,
"turn": {
"status": "completed",
"reply": { "text": "Here's your two-liner…", "actions": [ { "label": "Searched digests" } ] },
"error": null
}
},
"request_id": "5f0c…"
}

turn.status is the state machine — working, completed, failed, or awaiting_approval:

turn.statusCarriesMeaning
working—The turn is still running (only seen when your timeout expires first)
completedturn.reply — { text, actions: [{ label }] }The assistant's answer
failedturn.error — human stringThe turn failed; fix with the retry action
awaiting_approvalturn.pending_action — { id, label, ask }A tool action needs your approval — answer with approve/deny

List messages

GET/chat/messagesapi.daily-tube.com/v1

Returns the conversation's latest exchanges, newest first — the window is the latest 100 exchanges, paged within it (pageInfo.totalResults still counts all history).

Query parameters

ParameterTypeRules
maxResultsinteger1–100, default 20
pageinteger1-based; 0/negatives return an empty 200

Response

{
"data": {
"items": [ { "id": "msg_…", "created_at": "…", "type": "text", "text": { "body": "…" }, "attached_digest_id": null, "is_completed": true, "turn": { "status": "completed", "reply": { "text": "…", "actions": [] }, "error": null } } ],
"pageInfo": { "totalResults": 42, "resultsPerPage": 20, "page": 1, "totalPages": 3 }
},
"request_id": "5f0c…"
}

GET /chat/messages/{id} returns one exchange by message id — the poll target while a turn runs (404 chat.message_not_found otherwise).

Send a message

POST/chat/messagesapi.daily-tube.com/v1

Request body

FieldTypeRules
typeenumRequired. text or digest
text.bodystringRequired for text — 1–4000 characters after trim
digest.summary_idstringRequired for digest — an owned briefing (internal id or your own share id)
attached_digest_idstringOptional, text only — attaches an owned briefing chip to the message
timeoutintegerHold timeout in seconds, 1–120, default 30

Request

curl -X POST https://api.daily-tube.com/v1/chat/messages \
-H "Authorization: Bearer dtk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"text","text":{"body":"Summarize today in two lines"},"timeout":60}'

Response

200 with the exchange object (above). The response arrives when the turn reaches a terminal state within your timeout — or with turn.status: "working" if it didn't finish in time; poll GET /chat/messages/{id} to keep waiting. A digest or attachment you don't own is a uniform 404 summary.not_found.

Edit and delete

  • PATCH /chat/messages/{id} — edit the last exchange. Body: { "text": { "body": "…" }, "timeout": 30 } — a pair-swap: the old exchange is replaced and a fresh turn runs, returning the new exchange (200). 400 chat.not_last if the target isn't the latest; 400 chat.compacted if it's part of a saved summary.
  • DELETE /chat/messages/{id} — walk back the last exchange. 204, no body. Stops at the compaction boundary (400 chat.compacted).

Message actions

POST/chat/messages/{id}api.daily-tube.com/v1

Request body

ActionBodyEffect
retry{ "action": "retry", "timeout": 60 }Re-run a failed turn — holds like a send and returns the exchange (200). 409 chat.not_retryable on a non-failed turn
approve{ "action": "approve", "action_id": "act_…" }Approve the pending tool action — the turn continues and the response holds until it settles
deny{ "action": "deny", "action_id": "act_…" }Deny it — same hold semantics

approve/deny require the action_id from turn.pending_action and a turn in awaiting_approval (409 chat.no_pending_action).

Errors

Every error returns the same body — code is the stable machine string, message is human: The full registry of every code lives on Errors.

{
"error": { "code": "chat.turn_in_progress", "message": "A turn is already in progress" },
"request_id": "5f0c…"
}
CodeStatusWhen
chat.turn_in_progress409A live turn is running — wait or poll until it settles
chat.unresolved_failed_turn409A failed turn needs retry or delete first
chat.not_retryable / no_pending_action409Action preconditions
chat.compacted / compaction_in_progress400 / 409Compaction boundary
chat.not_last / message_not_found400 / 404Edit/delete guards
summary.not_found404Digest send/attach with a briefing you don't own
chatbot.disabled403Chatbot off for the account
subscription.inactive403Subscription not active
rate_limit.daily_messages429Daily chat cap reached — see Rate limits
validation.invalid_input400Bad type, empty/oversized body, bad timeout, unknown action

Chat sends carry their own daily message cap on top of the per-key budget — Rate limits.