Conversational interface via a REST API
Use the conversational REST API when you want to build a chat-style experience (a widget, mobile app, partner portal, or internal tool) on top of an existing thunk workflow. The integration uses the same workflow work item and conversation thread the agent already understands — without email. The same API is also available as an MCP server for MCP clients.
This is different from Inbound requests via a REST API, which starts structured workflow work items. The conversational API is for turn-by-turn chat on a workflow that has a conversation input.
Precondition. The thunk workflow must have exactly one conversation-type input property. Without that, the chat UI and conversational REST API are not available for the thunk.
What you can do
Typical integrator flow:
Confirm the workflow has exactly one conversation input property
Create a thunk API key (same keys as the workflow REST API)
Open the OpenAPI docs for this thunk’s conversational endpoints, or copy the MCP server URL
Start a conversation (first user message) and keep the returned conversation id
Send follow-up messages — either wait for the reply in the same request, or poll messages / subscribe to the event stream
End the workflow when the conversation is finished (
POST /{convoId}/end, or End conversation in the hosted Chat UI)
Optional: attach files on a turn (when User file attachments is enabled on the conversation property), upload files for structured file inputs before start, and (when Structured data is enabled on the conversation property) register structured-data schemas the assistant may return with messages.
1: Confirm the conversation input
In the thunk designer, open the workflow structure and check that there is exactly one input property whose type is Conversation. That property is the chat channel for each workflow work item.
If you need a quick product check: go to Run → Inbound Requests → Chat UI. When the conversation input is present, the Chat UI button is enabled and opens the hosted chat app for the thunk. When it is missing, the Chat UI button stays disabled and an info banner explains that the chat UI is enabled only if the workflow has a conversation input property.
Steps that should not reply to the end user
Chat workflows often include steps that do internal work — looking up an order, calling a business system, or updating structured fields — without talking to the person in the chat.
New steps include the end-user conversation property (typically EndUserConvo) in Input properties and Output properties automatically. For steps that should not reply, open the step’s AI Instructions and remove that conversation from Output properties. When that conversation is not listed under Output properties, the AI agent will not message the end user on that step. Later steps can still reply if they list the conversation in their Output properties.
Keep the conversation property in Output properties on steps that should greet the user, ask a question, share a status update, or otherwise reply in the chat. You may still list the conversation under Input properties if the step needs to read the thread without sending a message.
This is separate from Conversation property settings such as AI messaging, Disable replies, Message writing instructions, AI file attachments, User file attachments, or Step error message on a Conversation property.
Conversation property settings (chat)
Open the conversation input property in the workflow structure to control what the AI and end users can do in the chat:
AI file attachments — when enabled, the AI agent can attach files to outgoing messages.
User file attachments — when enabled (the default), end users can attach files in the hosted Chat UI and through the conversational REST API (
POST /uploadFiles, andattachmentsonPOST /start,POST /…/sendMessage, orPOST /…/sendMessageAndWait). When disabled, the attach control is hidden in the Chat UI and those API calls return an error: "File attachments are not supported on this conversation".Structured data — when enabled, integrators can register JSON schemas the assistant may return with messages (see below).
Form responses — when enabled, the AI agent can attach a form to a message for the person to answer (see Answer a response form below). One-time form submissions makes each form answerable only once.
Step error message — optional text sent to the end user if a step that lists this conversation under Output properties ends with an error. Leave blank to use the default: There was an error handling your message. Steps that do not list the conversation under Output properties do not send it.
Use GET /info at the API root to read structuredDataEnabled and userFileAttachmentsEnabled for the thunk.
2: Create an API key
API keys are shared with the structured workflow REST API. They are not created on the Chat UI tab.
Go to Run → Inbound Requests → API Channel
Open API Keys
Create a key and copy it immediately — it is shown only once. You can leave it with no expiration or choose how long it should last.
For screenshots and revoke steps, see Inbound requests via a REST API (Generate an API Key).
Keys use the thk_… prefix. Send them to the public gateway as:
X-API-Key: {api-key}
Base URL for customer integrations:
https://postern.thunk.ai/api/thunk/{thunk_id}/convo
3: Open the OpenAPI documentation
Each thunk exposes its own conversational OpenAPI document because optional data fields on start match that thunk’s workflow inputs.
Go to Run → Inbound Requests → Chat UI
Click View OpenAPI Documentation
That opens Swagger UI for this thunk’s conversational endpoints (paths such as /start, /{convoId}/sendMessage, /{convoId}/sendMessageAndWait, /{convoId}/messages, /{convoId}/message/{messageId}/ack, /{convoId}/message/{messageId}/formResponse, /{convoId}/end, and /{convoId}/events).
Authorize in Swagger is not your thunk API key. The OpenAPI Authorize control expects a Bearer JWT (a product session token). Use that only for interactive try-it-out. For real integrations through postern.thunk.ai, send X-API-Key: thk_… on the request — do not paste the thunk API key into the Bearer Authorize dialog. (This differs from the workflow REST OpenAPI docs, where Authorize accepts the API key.)
Connect as an MCP server
The same conversational API is also an MCP server, so an MCP client (Claude Desktop, another agent, or an integration host) can call the tools without using REST.
On Run → Inbound Requests → Chat UI, copy MCP Server URL. It looks like:
https://postern.thunk.ai/api/thunk/{thunk_id}/convo/mcp
Authenticate with a thunk API key the same way as REST: send X-API-Key: {api-key} on each request.
MCP hosts such as Claude Desktop do not take an API key. Paste the MCP Server URL into the host; it opens a Thunk sign-in page so you can allow access to that thunk's conversational tools.
The tools match the REST routes: list_conversations, start, send_message, send_message_and_wait, list_messages, get_message, get_convo_info, get_api_info, upload_files, get_structured_data_schemas, set_structured_data_schemas, and end_conversation. Conversation ids that REST puts in the path are tool arguments (convoId). Acknowledging a received message is REST-only (POST /{convoId}/message/{messageId}/ack); there is no MCP tool for it.
There is no event-stream tool. To send a follow-up and wait for the assistant in one call, use send_message_and_wait with timeoutMs (at most five minutes). You can still use send_message and poll get_convo_info until agentWorking is false, then list_messages (optionally with since). The REST GET /{convoId}/events stream is still available if you want push updates from HTTP.
4: Start a conversation
POST /start creates the workflow conversation and sends the first user message. The response includes a convoId used for every later call.
Minimal shape:
{
"conversation": {
"type": "message",
"message": "Hello — I need help with my request."
}
}
You may also send:
data— other workflow input fields (not the conversation property)name— label for the new conversation (optional). If you omit it, the conversation is named with its start time and the start of the first message, for exampleConversation 2026-09-27 21:04 UTC — Where is my order?displayName— label for the human on this turn (optional). Later follow-up turns keep this name if you omit it.conversation.attachments— optional file attachments on the first message (see below)
Example with curl:
curl -X POST \
'https://postern.thunk.ai/api/thunk/{thunk_id}/convo/start' \
-H 'accept: application/json' \
-H 'X-API-Key: {api-key}' \
-H 'Content-Type: application/json' \
-d '{
"conversation": {
"type": "message",
"message": "Hello — I need help with my request."
}
}'
A successful response looks like:
{
"convoId": "{conversation-id}"
}
5: Send messages and read replies
Send a follow-up
POST /{convoId}/sendMessage stores the user turn and returns a messageId:
curl -X POST \
'https://postern.thunk.ai/api/thunk/{thunk_id}/convo/{convoId}/sendMessage' \
-H 'accept: application/json' \
-H 'X-API-Key: {api-key}' \
-H 'Content-Type: application/json' \
-d '{
"message": "Here are more details."
}'
Optional fields: displayName (omit it to keep the name from start), attachments.
Send and wait for the reply
POST /{convoId}/sendMessageAndWait stores the user turn and waits until the assistant has replied and is idle, or until timeoutMs (required, at most five minutes). The response is HTTP 200 in both cases:
curl -X POST \
'https://postern.thunk.ai/api/thunk/{thunk_id}/convo/{convoId}/sendMessageAndWait' \
-H 'accept: application/json' \
-H 'X-API-Key: {api-key}' \
-H 'Content-Type: application/json' \
-d '{
"message": "Here are more details.",
"timeoutMs": 60000
}'
A successful response looks like:
{
"userMessageId": "{message-id}",
"status": "replied",
"messages": [
{
"id": "{assistant-message-id}",
"content": "Thanks — I can help with that.",
"author": "assistant",
"createdOn": "2026-09-11T00:00:00.000Z"
}
]
}
status is replied when the assistant posted at least one new message and went idle within the timeout, or timedOut otherwise. messages are only the new assistant messages from this turn. A timeout does not end the conversation — the user message is stored, and the assistant may still reply later. You can then poll GET /{convoId}/messages or GET /{convoId}/info. Optional fields are the same as sendMessage: displayName, attachments.
List messages
GET /{convoId}/messages returns a page of messages in chronological order (oldest first within the page). The first call (no pageToken) returns the most recent page; use pageToken / nextPageToken for older history.
Useful query parameters:
limit— page sizepageToken— pass the previous response’snextPageTokento continuesince— only messages after this ISO timestamp (exclusive); use the samesinceon every call that includespageToken
curl -X GET \
'https://postern.thunk.ai/api/thunk/{thunk_id}/convo/{convoId}/messages' \
-H 'accept: application/json' \
-H 'X-API-Key: {api-key}'
You can also fetch one message with GET /{convoId}/message/{messageId}, or check conversation state with GET /{convoId}/info (agentWorking and ended). agentWorking stays true while the assistant — or a tool the workflow handed the chat to — is still writing, including the short gap between sequential assistant messages. It becomes false when the next turn is yours, or the conversation has ended.
List your conversations
GET /conversations returns the conversations started by whoever the request is authenticated as, newest first — for example to show a person their earlier chats so they can pick one up again. An end user (signed in, or using their end-user API key) gets only their own conversations. A thunk API key gets the conversations started under its owner's identity. Use limit to choose how many to return (default 50, at most 100).
curl -X GET \
'https://postern.thunk.ai/api/thunk/{thunk_id}/convo/conversations?limit=20' \
-H 'accept: application/json' \
-H 'X-API-Key: {api-key}'
{
"conversations": [
{
"convoId": "…",
"title": "Where is my order?",
"createdOn": "2026-09-25T17:02:11.000Z",
"ended": false
}
]
}
title is the name you passed to POST /start (or the work item's name, when your workflow has a Name field). Pass a convoId to GET /{convoId}/messages to load that conversation. Conversations started before this endpoint was added are not listed.
Acknowledge that a message was received
POST /{convoId}/message/{messageId}/ack records that your client received that message. The first acknowledgement wins; later calls return the same receipt and do not change it.
Optional body:
{
"client": "my-app"
}
client is a short label for your app (letters, digits, ., _, or -). You can omit the body.
A successful response is the same message object as GET /{convoId}/message/{messageId}, now including receivedAt (and receivedBy when the first acknowledgement supplied client):
{
"id": "{assistant-message-id}",
"content": "Thanks — I can help with that.",
"author": "assistant",
"createdOn": "2026-09-11T00:00:00.000Z",
"receivedAt": "2026-09-11T00:00:02.000Z",
"receivedBy": "my-app"
}
List and get also include those fields after an acknowledgement. The hosted Chat UI acknowledges assistant messages when it loads them. In the thunk’s conversation transcript, an assistant message shows a single check when it is stored and a double check after a client acknowledges it — that transcript does not send acknowledgements itself.
curl -X POST \
'https://postern.thunk.ai/api/thunk/{thunk_id}/convo/{convoId}/message/{messageId}/ack' \
-H 'accept: application/json' \
-H 'X-API-Key: {api-key}' \
-H 'Content-Type: application/json' \
-d '{
"client": "my-app"
}'
Acknowledgement is REST-only. MCP clients that need to record a receipt should call this HTTP route.
Answer a response form
When Form responses is on, an assistant message can carry a responseForm: a form the person answers instead of replying in prose. GET /messages and GET /{convoId}/message/{messageId} include it on that message:
{
"id": "{assistant-message-id}",
"content": "Please confirm the delivery details.",
"author": "assistant",
"createdOn": "2026-09-25T00:00:00.000Z",
"responseForm": {
"formSchema": {
"type": "object",
"title": "Delivery details",
"properties": {
"date": { "type": "string", "format": "date", "title": "Delivery date" },
"address": { "type": "string", "title": "Address" }
}
},
"singleResponse": true,
"alreadySubmitted": false,
"receivedResponse": false
}
}
Render formSchema (a JSON Schema whose title is the form heading) as a form. Send the answers with POST /{convoId}/message/{messageId}/formResponse:
curl -X POST \
'https://postern.thunk.ai/api/thunk/{thunk_id}/convo/{convoId}/message/{messageId}/formResponse' \
-H 'accept: application/json' \
-H 'X-API-Key: {api-key}' \
-H 'Content-Type: application/json' \
-d '{
"formData": { "date": "2026-10-02", "address": "1 Main St" }
}'
The answers become the person's next message in the conversation (author: "user"), so they arrive through newMessage and GET /messages like any other turn, and the agent continues from them. The person can still send a normal message instead.
singleResponse is true when One-time form submissions was on when the form was sent. Once such a form has been answered, alreadySubmitted is true and another submission returns 409 with { "success": false, "alreadySubmitted": true }. The hosted Chat UI renders these forms for you.
Live updates (SSE)
GET /{convoId}/events is a long-lived Server-Sent Events stream. After a newMessage event, call GET /messages (optionally with since) to load the text. The stream also emits agentStatus when the assistant starts or stops working (same meaning as GET /info agentWorking, including while a tool still owns the chat). When the assistant stops (agentWorking is false), the event includes respondingToMessageId — the id of the most recent user message in the conversation, the same id you get from GET /messages. That is the turn the assistant was responding to. If the stream has a new assistant line and a stop at the same moment, newMessage is sent first so a client that disconnects when the assistant goes idle still sees that line. Periodic heartbeat keepalives are safe to ignore.
6. End the workflow
When the chat is finished — the end user is done, or your app no longer needs automation — call POST /{convoId}/end. This stops the agent, skips any active and remaining workflow steps (recording an optional reason), and marks the work item complete.
Optional body:
{
"reason": "Customer ended the chat"
}
If you omit reason, the server uses "Conversation ended".
Example:
curl -X POST \
'https://postern.thunk.ai/api/thunk/{thunk_id}/convo/{convoId}/end' \
-H 'accept: application/json' \
-H 'X-API-Key: {api-key}' \
-H 'Content-Type: application/json' \
-d '{
"reason": "Customer ended the chat"
}'
A successful response looks like:
{
"convoId": "{conversation-id}",
"ended": true
}
If the workflow was already ended, the server returns 409 with { "message": "Conversation already ended" }.
Poll GET /{convoId}/info to read ended (and agentWorking) without listing messages.
On the hosted Chat UI (Run → Inbound Requests → Chat UI), end users can click End conversation above the message box for the same effect.
Attachments and file inputs
Message attachments. When User file attachments is enabled on the conversation property, you can send files on POST /start (conversation.attachments), POST /…/sendMessage (attachments), or POST /…/sendMessageAndWait (attachments). Each item is { "name", "data" } where data is a base64 data: URL (the same string browsers produce from FileReader.readAsDataURL). Limits: up to 5 files and 10 MiB total decoded payload per message. When you list messages, attachments include a short-lived contentPointer download URL.
If User file attachments is disabled, message attachments and POST /uploadFiles are rejected with "File attachments are not supported on this conversation". This setting is independent of AI file attachments, which controls whether the AI agent can attach files to its own outgoing messages.
Structured file inputs on start. If the workflow’s non-conversation inputs include file fields, upload first with POST /uploadFiles (requires User file attachments), then put the returned url values into data on POST /start (do not embed large base64 blobs in the start body for those fields).
Structured data (optional)
If the conversation input property has structured data enabled, you can register JSON schemas (on POST /start or via /{convoId}/structuredDataSchemas) describing payloads the assistant may attach to messages. Listed assistant messages may then include a structuredData array. Use GET /info at the API root to see whether structuredDataEnabled and userFileAttachmentsEnabled are true for the thunk. Details and schemas are in the thunk’s OpenAPI document.
Choosing conversational vs workflow REST
Goal | Use |
Chat turns on a workflow conversation | This article (conversational REST API or MCP server) |
Create / poll structured workflow work items | |
Debug a model this thunk can use (not for production) |
