curl --request POST \
--url https://api.recoupable.dev/api/chat \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"messages": [
{
"id": "<string>",
"content": "<string>"
}
],
"chatId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"sessionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"context": {
"contextLimit": 123
}
}
'import requests
url = "https://api.recoupable.dev/api/chat"
payload = {
"messages": [
{
"id": "<string>",
"content": "<string>"
}
],
"chatId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"sessionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"context": { "contextLimit": 123 }
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
messages: [{id: '<string>', content: '<string>'}],
chatId: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
sessionId: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
context: {contextLimit: 123}
})
};
fetch('https://api.recoupable.dev/api/chat', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.recoupable.dev/api/chat",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'messages' => [
[
'id' => '<string>',
'content' => '<string>'
]
],
'chatId' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'sessionId' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'context' => [
'contextLimit' => 123
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.recoupable.dev/api/chat"
payload := strings.NewReader("{\n \"messages\": [\n {\n \"id\": \"<string>\",\n \"content\": \"<string>\"\n }\n ],\n \"chatId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"sessionId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"context\": {\n \"contextLimit\": 123\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.recoupable.dev/api/chat")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"messages\": [\n {\n \"id\": \"<string>\",\n \"content\": \"<string>\"\n }\n ],\n \"chatId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"sessionId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"context\": {\n \"contextLimit\": 123\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.recoupable.dev/api/chat")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"messages\": [\n {\n \"id\": \"<string>\",\n \"content\": \"<string>\"\n }\n ],\n \"chatId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"sessionId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"context\": {\n \"contextLimit\": 123\n }\n}"
response = http.request(request)
puts response.read_body"<string>"{
"status": "error",
"error": "<string>",
"missing_fields": [
"<string>"
]
}{
"status": "error",
"error": "<string>",
"missing_fields": [
"<string>"
]
}{
"status": "error",
"error": "<string>",
"missing_fields": [
"<string>"
]
}{
"status": "error",
"error": "<string>",
"missing_fields": [
"<string>"
]
}{
"status": "error",
"error": "<string>",
"missing_fields": [
"<string>"
]
}Stream Chat
Streams an agent loop running as a durable Vercel Workflow against the session’s sandbox. The agent uses sandbox-only tools (bash, read, write, grep, glob, todo, task, ask_user_question, skill, fetch) — no MCP or Composio. Requires a sandbox provisioned via POST /api/sandbox.
Related endpoints
POST /api/chat/runs— the headless counterpart: it provisions its own session + sandbox and runs without a streaming client. Use that for tasks/cron/programmatic runs; use this for interactive, streaming turns on an existing chat.GET /api/chat/{chatId}/stream— reconnect to an in-progress response.GET /api/chat/runs/{runId}— check whether a headless run finished (status, not content).
curl --request POST \
--url https://api.recoupable.dev/api/chat \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"messages": [
{
"id": "<string>",
"content": "<string>"
}
],
"chatId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"sessionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"context": {
"contextLimit": 123
}
}
'import requests
url = "https://api.recoupable.dev/api/chat"
payload = {
"messages": [
{
"id": "<string>",
"content": "<string>"
}
],
"chatId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"sessionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"context": { "contextLimit": 123 }
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
messages: [{id: '<string>', content: '<string>'}],
chatId: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
sessionId: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
context: {contextLimit: 123}
})
};
fetch('https://api.recoupable.dev/api/chat', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.recoupable.dev/api/chat",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'messages' => [
[
'id' => '<string>',
'content' => '<string>'
]
],
'chatId' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'sessionId' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'context' => [
'contextLimit' => 123
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.recoupable.dev/api/chat"
payload := strings.NewReader("{\n \"messages\": [\n {\n \"id\": \"<string>\",\n \"content\": \"<string>\"\n }\n ],\n \"chatId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"sessionId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"context\": {\n \"contextLimit\": 123\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.recoupable.dev/api/chat")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"messages\": [\n {\n \"id\": \"<string>\",\n \"content\": \"<string>\"\n }\n ],\n \"chatId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"sessionId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"context\": {\n \"contextLimit\": 123\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.recoupable.dev/api/chat")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"messages\": [\n {\n \"id\": \"<string>\",\n \"content\": \"<string>\"\n }\n ],\n \"chatId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"sessionId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"context\": {\n \"contextLimit\": 123\n }\n}"
response = http.request(request)
puts response.read_body"<string>"{
"status": "error",
"error": "<string>",
"missing_fields": [
"<string>"
]
}{
"status": "error",
"error": "<string>",
"missing_fields": [
"<string>"
]
}{
"status": "error",
"error": "<string>",
"missing_fields": [
"<string>"
]
}{
"status": "error",
"error": "<string>",
"missing_fields": [
"<string>"
]
}{
"status": "error",
"error": "<string>",
"missing_fields": [
"<string>"
]
}Authorizations
Your Recoup API key. Learn more.
Body
Chat workflow request
Request body for the sandbox-driven, Workflow-backed chat endpoint. Requires an existing session, chat, and provisioned sandbox. The Bearer key from the Authorization header is forwarded into the sandbox for skill-based callbacks to the Recoup API — no separate access-token field needed.
Conversation history as an array of UIMessage objects, matching the Vercel AI SDK UIMessage shape. The last entry is typically the new user turn — the agent loop reads the entire array and persists the latest user message before the workflow starts.
Show child attributes
Show child attributes
UUID of an existing chat under sessionId. The chat's active_stream_id column is used for compare-and-set race control across concurrent requests.
UUID of the session that owns both the chat and the sandbox. The session must have a non-null sandbox_state — call POST /api/sandbox first if not.
Optional per-prompt context overrides forwarded into the agent loop.
Show child attributes
Show child attributes
Response
Server-Sent Events stream of UI message parts compatible with the Vercel AI SDK createUIMessageStreamResponse. The x-workflow-run-id response header carries the durable workflow run ID for resume/stop operations.
Server-Sent Events stream containing UI message parts
Was this page helpful?
