Chat API Reference
Complete reference for the POST /chat endpoint — request schema, response modes (sync, streaming, async, and scheduled), authentication, threads, attachments, quoted contexts, and error handling.
Overview
The POST /chat endpoint is the primary interface for sending messages to an Interlocute node
and receiving responses. It supports buffered JSON, real-time streaming (SSE), provider-managed Async, Scheduled, and Economy.
See Routes for all addressing patterns. The examples below use the alias (subdomain) form.
OpenAI chat uses the Responses API automatically, including Economy. Your Interlocute request format stays the same: choose an allowed model when needed; do not select Chat Completions or send a provider-specific request body.
Authentication
The /chat endpoint supports three authentication modes:
Tenant API Key
Pass your API key as a Bearer token: Authorization: Bearer YOUR_API_KEY.
Tenant-scoped keys can access any node owned by the tenant. Node-scoped keys are restricted to a single node.
JWT Token
For browser-based or first-party integrations. The JWT is validated by middleware before the request reaches the node.
Anonymous
If the node operator has enabled anonymous chat, no credentials are required. Anonymous access is disabled by default and must be explicitly enabled per node.
403 Forbidden is returned if a node-scoped key is used against a different node.
See Auth & Keys for key management details.
Request
Send a JSON body with Content-Type: application/json.
| Field | Type | Required | Description |
|---|---|---|---|
| externalInvocationId | string | null | No | Optional ID for one Async job or Economy enrollment, unique per node. Identical retries return the original acceptance; changed intent returns 409. Case-sensitive, 1–256 alphanumeric or -_.:/ characters. See Tracking jobs with your own ID. |
| content | string | Yes | The user's message text. |
| threadId | string | null | No | Omit or pass null to start a new thread. Pass an existing thread ID to continue a conversation. |
| externalCorrelationId | string | null | No | Client-supplied alias for thread resolution, scoped per node. When provided (and threadId is null), the system looks for an existing thread with this alias. If found it is reused; if not, a new thread is created and stamped with the alias. Enables sticky sessions without persisting Interlocute thread GUIDs on your side. Max 256 chars, alphanumeric + -_.:/. |
| clientMessageId | string | null | No | Client-supplied identifier for the assistant reply. Useful for hydrating prompt metadata without fetching the full thread. |
| quotedContexts | array | null | No | Quoted context references scoped to this turn. See Quoted Contexts below. |
| attachments | array | null | No | File attachments (images, documents) via URL or base64 data URL. See Attachments below. |
| options | object | null | No | Per-request options: response mode, model selection, capability-driven inference controls, web search, and geolocation. See Options below. |
Minimal request
{
"content": "Hello! How can you help me today?"
}Full request
{
"content": "Summarize the highlighted paragraph.",
"threadId": "thr_abc123",
"clientMessageId": "msg_client_456",
"externalCorrelationId": "session-user-42",
"quotedContexts": [
{
"sourceMessageId": "msg_789",
"sourceRole": "assistant",
"quotedText": "The deployment completed successfully at 14:32 UTC.",
"sourceTimestamp": "2025-01-15T14:32:00Z"
}
],
"attachments": [
{
"name": "screenshot.png",
"contentType": "image/png",
"url": "https://cdn.example.com/screenshots/deploy-result.png"
}
],
"options": {
"webSearchEnabled": true,
"searchContextSize": "medium"
}
}Options
API calls default options.cognitiveSynthesisEnabled to
false. Set it to true
to request optional Thread cognitive-state synthesis after primary completion. This applies to
Sync, streaming, Async, Scheduled and Economy. Synthesis uses the normal Thread processor and
its separately recorded cognitive usage; it does not hold the answer or completed receipt open.
Optional synthesis is best-effort and can be interrupted by a host restart without automatic retry.
Human UI chat retains its synthesis default. The accepted choice is part of request identity;
repeat retries with the same options.
The options object provides per-request control over response delivery, model tools, and execution behaviour.
All fields are optional and default to off or null.
Response mode
| Field | Type | Default | Description |
|---|---|---|---|
| responseMode | string | null | null |
Controls how the response is delivered. All non-sync modes return 202 Accepted with an invocationId for polling.
See Async modes.
"sync" or null: default synchronous.
"async": provider-managed background execution; retrieve the result through Chit.
"scheduled": process at scheduledAtUtc.
"economy": submit one isolated new turn through the provider batch path. Use the Batch API for a collection of items.
|
| scheduledAtUtc | string (ISO 8601) | null | null |
UTC time to process the message. Required when responseMode is "Scheduled".
Must be in the future and within 7 days. Ignored for other modes.
|
Model selection
By default a conversation runs on the model configured on the node. Supplying
model pins the thread to a different one — the same override the
console's thread settings drawer applies, so the choice is visible and editable there afterwards.
The pin persists for the life of the thread, so later turns need not repeat it. Re-sending the same
model is a no-op; sending a different model to a thread that is already pinned returns
409 Conflict rather than silently switching or silently ignoring.
The provider is derived from the model id — there is no provider field.
| Field | Type | Default | Description |
|---|---|---|---|
| model | string | null | null |
Model to run this conversation on, e.g. "gpt-5-mini".
Must be permitted by the node and cheaper than the node's default — see the two rules below.
|
Model selection is enabled by default and needs no node configuration, because the cost rule below means a caller can only ever make a request cheaper than the node already runs.
Selection may only move down in cost. The chosen model must come from the same provider as the node's default and be no more expensive on either input or output, and strictly cheaper on at least one. The comparison uses published per-token prices, not the model's name.
Requiring both directions matters because a model can be cheaper one way and dearer the other:
gpt-5.2 ($1.75 in / $14.00 out) undercuts
gpt-5 ($2.00 / $8.00) on input while costing more on output. Whether
it would actually be cheaper for your workload depends on a token mix the API cannot predict, so it is refused.
Names are not a reliable guide either way. gpt-5.6-luna
($0.20 / $1.20) is cheaper than gpt-5-mini ($0.25 / $2.00) despite
carrying neither "mini" nor "nano", and claude-sonnet-5 ($2 / $10)
undercuts the older claude-sonnet-4-6 ($3 / $15). A node whose
default is already the cheapest model available has nothing to select.
A node owner can narrow this further, or close it off entirely, in the node's capability declaration.
allow is optional — omit it to permit any model the cost rule
already allows.
{
"runtime": {
"chat": {
"model": {
"overridable": true,
"allow": ["gpt-5-mini", "gpt-5-nano"]
}
}
}
}| Status | When |
|---|---|
| 403 | The model is not cheaper than the node's default on both input and output, is from a different provider, has no price on record, or the node has narrowed or disabled selection in its capability declaration. |
| 400 | No configured provider publishes a model with that id. |
| 409 | The thread is already pinned to a different model. The response names the existing pin. |
| 503 | A provider catalogue was unavailable, so the model could not be verified. Retry shortly. |
A rejected model never creates a thread, so a failed call leaves nothing behind to clean up.
{
"content": "Summarise this in one line.",
"options": {
"model": "gpt-5-mini"
}
}Inference controls
The Runtime API exposes the same canonical inference controls used by the console. Send them under
options on POST /chat or
POST /nodes/{nodeId}/chat; these are API inputs, not UI-only settings.
Each field is a sparse per-invocation override. Omit a field to inherit the effective Node/Thread posture.
A per-request override does not mutate the Thread defaults.
| Field | Type | When omitted | Description |
|---|---|---|---|
| reasoning.effort | string | null | Inherit | One of none, minimal, low, medium, high, xhigh, or max when the selected model advertises it. Providers/models may support only a subset. |
| reasoning.mode | string | null | Inherit | standard or pro where the selected model supports the mode. |
| reasoning.thinking | string | null | Inherit | disabled or adaptive where the selected model advertises thinking support. This is not raw chain-of-thought access. |
| maxOutputTokens | integer | null | Inherit | Positive output ceiling, no greater than the selected model's advertised maximum. Omit to inherit the configured budget. Reasoning may consume this budget before any answer text is emitted. The same effective value is used for credit reservation and provider submission. |
| temperature | number | null | Inherit | Sampling temperature when the selected model/reasoning posture supports it. The supported range is capability-defined. |
| topP | number | null | Inherit | Nucleus-sampling probability when the selected model/reasoning posture supports it. The supported range is capability-defined. |
400 with an invalid_inference_control or
unsupported_inference_control problem when a value is invalid or unsupported.
It does not silently drop an explicit control. Do not send temperature or top-p merely because a provider supports
them for another model or reasoning posture.
curl -X POST https://api.interlocute.ai/nodes/nd_abc123/chat \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Summarise this report in five concise bullets.",
"options": {
"model": "gpt-5.6-terra",
"reasoning": {
"effort": "high",
"mode": "pro"
},
"maxOutputTokens": 1200
}
}'
For an Anthropic model whose catalog advertises adaptive thinking, the same public shape is used:
"reasoning": { "effort": "medium", "thinking": "adaptive" }.
Use the model's advertised capabilities rather than assuming that an OpenAI mode or effort value transfers
to another provider.
The effective posture is available to API consumers in the completed invocation receipt. Poll the returned
pollUrl, or call GET /nodes/{nodeId}/chit/{invocationId},
and read executionSettings:
"executionSettings": {
"provider": "OpenAIResponses",
"model": "gpt-5.6-terra",
"reasoning": { "effort": "high", "mode": "pro" },
"maxOutputTokens": 1200
}This is the effective provider-facing snapshot, not a copy of a UI selection. Omitted controls remain absent when no explicit effective value exists; provider/model and any inherited controls are shown when they are known.
Web search
When webSearchEnabled is true,
the selected provider's native web-search tool is attached when the effective node/thread capability and model support it.
Writing “search the web” in the prompt does not enable the tool. Enabling it allows the model to search;
it does not require a search. Supported adapters are OpenAIResponses
and Anthropic, for models that advertise native search support.
| Field | Type | Default | Description |
|---|---|---|---|
| webSearchEnabled | boolean | false |
Offers optional provider-native search for this message. Unavailable search continues as ordinary chat with a grounding note. Structured citations and search activity remain separate from answer text. |
| searchContextSize | string | null | "medium" |
Controls how much web context the model retrieves per search. "low" is faster and cheaper; "high" pulls more results for comprehensive answers. Only applies when webSearchEnabled is true. |
| useGeolocation | boolean | false |
When true, the user_location parameter is set on the web search tool, geo-biasing results toward the user's location. Pair with the userCity/userRegion/userCountry fields for accuracy. |
| userCity | string | null | null | Caller-supplied approximate city for geo-biasing (e.g., "New York"). The API does not infer browser location. Pair with useGeolocation. |
| userRegion | string | null | null | Approximate region or state (e.g., "New York", "California"). Only used when useGeolocation is true. |
| userCountry | string | null | null | ISO 3166-1 alpha-2 country code (e.g., "US", "GB"). Only used when useGeolocation is true. |
responseMetadata.grounding for the outcome.
Sync JSON and SSE return this metadata; Async and Scheduled Chit polling return it on the assistant output.
searchRequested records the caller's intent;
searchEnabled records whether the tool was offered;
searchPerformed records provider-reported activity.
When unavailable, searchUnavailableReason is
capability_disabled, provider_unsupported
or model_unsupported, with a human-readable note.
An enabled tool with no reported search has a note saying so. Older messages may omit these new fields.
"responseMetadata": {
"grounding": {
"searchRequested": true,
"searchEnabled": false,
"searchPerformed": false,
"searchUnavailableReason": "model_unsupported",
"note": "Web search is unavailable for this model. Continued without web search."
}
}Async and Scheduled preserve the same search options, including search depth and supplied location. Scheduled uses durable queue delivery; Direct Async uses provider-managed background execution. Both preserve normal chat execution policy and conversation semantics. Depth and location remain provider-specific hints. Economy mode explicitly rejects search options.
// Minimal web search
{
"content": "What is the current status of the SpaceX Starship program?",
"options": {
"webSearchEnabled": true
}
}
// With caller-supplied geo-biasing
{
"content": "What are the best coffee shops near me?",
"options": {
"webSearchEnabled": true,
"searchContextSize": "low",
"useGeolocation": true,
"userCity": "Seattle",
"userRegion": "Washington",
"userCountry": "US"
}
}Quoted Contexts
Quoted contexts let you reference specific content from earlier in the conversation (or from another source) to give the node precise context for the current turn. This is especially useful for "reply to this" or "explain this paragraph" interactions.
| Field | Type | Description |
|---|---|---|
| sourceMessageId | string | The ID of the message being quoted. |
| sourceRole | string | Role of the quoted message: user or assistant. |
| quotedText | string | The highlighted or selected text being quoted. |
| sourceTimestamp | string (ISO 8601) | When the original message was created. |
Attachments
Attachments let you send files (images, documents, etc.) alongside your message. Each attachment supports two input modes — provide exactly one per attachment:
url— a public HTTP(S) URL; the server fetches the file for you (easiest for Postman & API clients)dataUrl— inline base64 data URL (what browser JavaScript produces from FileReader)
| Field | Type | Description |
|---|---|---|
| name | string | The file name (e.g., report.pdf). |
| contentType | string | MIME type (e.g., image/png, application/pdf). Optional for url mode (inferred from response). |
| url | string | Public HTTP(S) URL. Mutually exclusive with dataUrl. |
| dataUrl | string | Base64-encoded data URI: data:{contentType};base64,.... Mutually exclusive with url. |
| sizeBytes | integer | File size in bytes. Required for dataUrl. Optional for url (server determines). |
URL attachment example
{
"content": "What does this floor plan show?",
"attachments": [{
"name": "floorplan.png",
"contentType": "image/png",
"url": "https://cdn.example.com/floorplan.png"
}]
}Base64 data URL example
{
"content": "Describe this screenshot.",
"attachments": [{
"name": "screenshot.png",
"contentType": "image/png",
"dataUrl": "data:image/png;base64,iVBORw0KGgo...",
"sizeBytes": 24576
}]
}url field — just paste a public link to your file. No base64 encoding needed.
Pre-signed S3/GCS URLs and Azure Blob SAS tokens work too.
url mode, the server fetches the file synchronously before processing your chat message.
Keep files under 10 MB for optimal latency. Supported file types depend on the underlying model's capabilities.
Response modes
The /chat endpoint supports four response modes.
Sync and streaming are selected by the Accept header;
async modes are selected via options.responseMode.
Buffered JSON (default)
The default mode. The server waits for the complete response, then returns a single JSON object. No special headers are required.
curl -X POST https://my-node.interlocute.ai/chat \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"content": "Hello!"}'Streaming (SSE)
Set Accept: text/event-stream to receive tokens as they are generated.
The response is a Server-Sent Events stream with the following event sequence:
curl -N -X POST https://my-node.interlocute.ai/chat \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{"content": "Write a haiku about AI."}'1
[META] event
Sent first. Contains a JSON payload with requestId,
nodeId, threadId,
inputMessageId, and outputMessageId.
The content field is empty in this event.
2
data: token events
Each generated token is sent as a data: line. Concatenate all tokens
to build the complete response.
3
[DONE] event
Sent last. Indicates the stream is complete. Close your connection after receiving this event.
!
[ERROR] event
Sent if an error occurs during streaming. Contains a JSON payload with an error field.
The stream ends after this event.
Example SSE stream
data: [META]{"requestId":"req_abc","nodeId":"nd_123","threadId":"thr_456","inputMessageId":"msg_in_1","outputMessageId":"msg_out_1","content":""}
data: Silicon
data: dreams
data: awake
data: ,
data: thoughts
data: bloom
data: like
data: spring
data: [DONE]Deferred modes (Async, Scheduled, Economy)
All non-sync modes are deferred: the server validates the request and returns 202 Accepted
with an invocationId you can poll for status and output. Async and scheduled requests
follow their normal conversation semantics. Economy is an isolated single-item batch submission: identifiers are allocated at
acceptance, but the thread is materialized only after a successful provider result.
| Mode | When it runs | Use case | Status |
|---|---|---|---|
| Async | Provider-managed background execution | Avoid timeouts and fire-and-forget work | Available |
| Scheduled | At scheduledAtUtc |
"Send this at 9 AM", delayed campaigns | Available |
| Economy | Provider-managed batch lifecycle | One isolated, non-urgent chat turn | Available |
Async — provider-managed background processing
Direct Async is available for the effective OpenAI Responses provider. Other providers return
async_not_supported before primary execution.
HTTP 202 confirms the provider execution is accepted and durably linked to your original Chit.
The accepting request ends without waiting for completion. Interlocute collects the result only when
you read that Chit; no automatic monitoring or Direct Async callback collects it for you.
Polling that Chit refreshes the requested execution on demand; a Runtime restart or lost connection
does not cancel it. A 503 provider_execution_persistence_unresolved
includes the original invocation ID and poll URL. Keep those IDs and observe that work; do not resubmit it.
New managed Async receipts have no host expiry deadline. Provider retention still governs results
Interlocute has not collected; a failed read is an error, never a fabricated completion.
curl -X POST https://my-node.interlocute.ai/chat \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Analyze this quarter's revenue trends in detail.",
"options": { "responseMode": "async" }
}'Scheduled — process at a future time
curl -X POST https://my-node.interlocute.ai/chat \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Good morning! Here is your daily briefing.",
"options": {
"responseMode": "scheduled",
"scheduledAtUtc": "2025-07-16T09:00:00Z"
}
}'Economy — one isolated provider-batch turn
Supply externalInvocationId to safely repeat the same Economy POST
after an ambiguous acknowledgment. The same Node, key and request recover the original batch/item acceptance;
changed content, inference controls or resolved provider/model return 409. The acceptance echoes your key.
Poll the returned pollUrl; Economy does not use Async's
/chit/by-external-id lookup. Conversation
externalCorrelationId remains unsupported.
Economy returns an acceptance receipt after its Batch and inputs are durably saved and credit is reserved;
provider submission continues afterward. Keep the returned batchId
as the immediate status handle because the individual Chit may not exist until preparation finishes.
A client disconnect does not cancel accepted work. See
preparation failures and safe retry
for the ten-minute, demand-checked preparation limit, refunds and safeToResubmit.
Once the item receipt exists, poll its returned
pollUrl to request progress and collect the answer when available.
Result retrieval happens when you ask or when a configured provider completion webhook arrives;
Interlocute does not periodically poll providers.
Supply an optional callbackUrl and optional
callbackSigningSecret directly on your request; no consumer registration is needed.
see batch callbacks
for receipt delivery and acknowledgement. Completion callbacks apply to Economy only.
Frequent polls may reuse saved status. Uncollected results have no Interlocute age cutoff;
retrieval depends on upstream availability. Once collected, results stay available locally.
See result availability and expiry.
Economy accepts a new, isolated first turn only. It does not accept an existing thread, attachments,
scheduled time, or web search options. An allowed options.model
selects a batch-capable model through the Node's existing override policy; omission uses the Node default. For a collection
of independent turns, use the Batch API.
A disallowed, unavailable or non-batch-capable model is rejected before acceptance with an HTTP error,
a stable code and an explanatory detail.
Rejected requests have no accepted Chit. If provider processing fails after acceptance, the item's
receipt and Chit show its terminal status and error.
curl -X POST https://my-node.interlocute.ai/chat \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Classify this customer message.",
"externalInvocationId": "classification:customer-1001",
"options": { "responseMode": "economy" }
}'The Economy acceptance includes your retry key and the item polling URL:
{
"disposition": "deferred",
"responseMode": "economy",
"externalInvocationId": "classification:customer-1001",
"batchId": "economy_...",
"invocationId": "inv_01...",
"nodeId": "nd_abc123",
"threadId": "thr_01...",
"status": "queued",
"pollUrl": "nodes/nd_abc123/chit/inv_01..."
}202 response
{
"disposition": "deferred",
"invocationId": "01JX4K7M2N...",
"nodeId": "nd_abc123",
"threadId": "thr_xyz789",
"status": "queued",
"responseMode": "scheduled",
"scheduledAtUtc": "2025-07-16T09:00:00Z",
"pollUrl": "nodes/nd_abc123/chit/01JX4K7M2N..."
}responseMode and scheduledAtUtc fields
are echoed in the 202 response. For Async mode,
scheduledAtUtc is omitted. Economy responses also include a
batchId; use the Batch API lifecycle and results endpoints to follow it.
Polling for results
Use the pollUrl from the 202 response to check status. It is a relative
path on the Interlocute Runtime API, not a callback URL and not a URL on your worker or Function app. Resolve it
against the Runtime API origin. For example, the returned
nodes/nd_abc123/chit/01JX4K7M2N... becomes
https://api.interlocute.ai/nodes/nd_abc123/chit/01JX4K7M2N....
Do not construct a follow-up URL from threadId or
outputId: the polling receipt is the Runtime API result.
# JSON (default) — full invocation receipt
curl https://my-node.interlocute.ai/chit/01JX4K7M2N... \
-H "Authorization: Bearer $API_KEY"
# Plain text — single human-readable sentence
curl "https://my-node.interlocute.ai/chit/01JX4K7M2N...?format=plain" \
-H "Authorization: Bearer $API_KEY"
# Markdown — compact status table
curl "https://my-node.interlocute.ai/chit/01JX4K7M2N...?format=markdown" \
-H "Authorization: Bearer $API_KEY"
The JSON response is an invocation receipt with status
(queued, running,
cancel_requested, completed,
failed, canceled,
unresolved for direct Async), a terminal flag,
timing fields, an outputs[] array, and a
usage object. On a completed deferred chat, the
assistantMessage.content field contains the complete assistant response.
outputId and threadId are stable
correlation identifiers; they are not a separate message-fetch contract.
// Completed receipt
{
"invocationId": "01JX4K7M2N...",
"status": "completed",
"terminal": true,
"threadId": "thr_xyz789",
"startedAtUtc": "2025-07-15T10:00:01Z",
"completedAtUtc": "2025-07-15T10:00:08Z",
"durationMs": 7200,
"outputs": [
{
"kind": "userMessage",
"outputId": "msg_in_001",
"threadId": "thr_xyz789",
"content": "Analyze this quarter's revenue trends in detail."
},
{
"kind": "assistantMessage",
"outputId": "msg_out_001",
"threadId": "thr_xyz789",
"content": "Revenue grew 18% quarter over quarter..."
}
],
"usage": {
"inputTokens": 12,
"outputTokens": 28,
"totalTokens": 40,
"estimated": false
}
}assistantMessage but no
content is not a usable deferred-chat completion. Treat it as a Runtime
compatibility error; do not guess an undocumented message URL, switch to synchronous chat, or publish an empty result.
The usage object is field-for-field identical to the one on the
synchronous response, so the same reported numbers are available whichever response mode you use when known.
Missing usage or economics means the amount is unknown. Cancellation preserves any recorded amounts;
a terminal unresolved receipt does not mean zero cost.
Retry-After header and any receipt retry guidance;
a queued or running receipt is not a completed answer.
For Scheduled mode, don't start polling until after scheduledAtUtc.
Cancel a direct Async chat
Closing your client connection or stopping polling does not cancel an accepted Async invocation. Send an authenticated POST using the invocation ID returned at acceptance, then keep reading the original receipt:
# Request cancellation (no request body)
curl -X POST "https://my-node.interlocute.ai/chit/01JX4K7M2N.../cancel" \
-H "Authorization: Bearer $API_KEY"
# Continue reading the same receipt
curl "https://my-node.interlocute.ai/chit/01JX4K7M2N..." \
-H "Authorization: Bearer $API_KEY"
HTTP 202 with cancel_requested means the provider acknowledged
cancellation and the execution remains active. HTTP 200 confirms provider cancellation;
HTTP 409 means another terminal outcome won. Repeating the cancellation command is safe.
With an external ID, use POST /chit/by-external-id/cancel?externalInvocationId=....
503 cancellation_delivery_uncertain.
Retry cancellation or read the original Chit; do not replay the paid invocation. Confirmed cancellation
does not mean zero charges. Missing usage and economics remain unknown.
See the cancellation contract
for authentication, confirmation fields, recovery and errors. Economy uses
parent batch cancellation;
Scheduled and recipe work retain their own cancellation controls.
When to use async modes
Non-streaming JSON callers behind proxies → Async
Buffered JSON holds the connection open until the full response is ready. Behind Azure Front Door (240 s timeout) or typical HTTP clients (30–60 s), complex prompts risk a gateway timeout. Async acknowledges after provider acceptance and durable identity persistence.
Webhook and serverless integrations → Async
Zapier, n8n, and cloud functions often enforce short timeouts. Submit work via Async, then poll or process asynchronously.
Delayed messages and campaigns → Scheduled
"Send this daily briefing at 9 AM", "Process this report Monday morning".
Set scheduledAtUtc and the message is held until that time.
Maximum 7 days ahead.
Batch and fire-and-forget workflows → Batch API
Submit independent items once, then monitor the parent lifecycle and page through results by your custom IDs — ideal for background processing pipelines.
One non-urgent chat turn → Economy
Economy enrolls one isolated new turn in the supported provider batch path. Provider terms, completion windows, and pricing vary; use the Batch API for multiple items.
Async mode by default. The Interlocute Runtime API sits behind Azure Front Door,
which enforces a 240-second idle timeout. Complex prompts with web search, large context windows, or reasoning
models can approach this limit. Async releases the connection after durable provider acceptance;
recover an uncertain acceptance using the original invocation ID instead of resubmitting.
Streaming (SSE) mode is not affected — it sends tokens continuously, keeping the connection alive.
Response schema
The buffered JSON response (and the [META] event in streaming mode) follows this structure:
| Field | Type | Description |
|---|---|---|
| requestId | string | Server-generated correlation ID for tracing and support. |
| nodeId | string | The node that processed this request. |
| threadId | string | The thread ID for this conversation. Save this to continue the thread in subsequent requests. |
| inputMessageId | string | The ID assigned to your user message. |
| outputMessageId | string | The ID assigned to the assistant's reply. |
| content | string | The assistant's full response text. Empty in streaming [META] events. |
| usage | object | null |
Token usage for the turn. Contains inputTokens,
outputTokens, totalTokens,
and estimated. Null in the streaming
[META] event — usage is only known once the turn completes,
so read it from the terminal [RESULT] event or the buffered
response body.
|
Example response (buffered)
{
"requestId": "req_a1b2c3d4",
"nodeId": "nd_abc123",
"threadId": "thr_xyz789",
"inputMessageId": "msg_in_001",
"outputMessageId": "msg_out_001",
"content": "Hello! I'm your support assistant. I can help you with order lookups, account questions, and troubleshooting.",
"usage": {
"inputTokens": 12,
"outputTokens": 28,
"totalTokens": 40,
"estimated": false
}
}Reading token usage
inputTokens and outputTokens are the
counts the model provider itself reported for the turn. Where a provider bills reasoning as output —
OpenAI's reasoning models, Anthropic extended thinking — outputTokens
already includes those reasoning tokens; do not add them separately.
estimated is false on the normal path.
It is true only when the provider returned no usage for the turn and the
counts fall back to a character-count approximation. If you meter or bill on these numbers, check this flag.
A null usage object means no usage was
recorded at all — it never means the turn consumed zero tokens.
Thread lifecycle
Threads are the unit of conversation state. Understanding how they work helps you build multi-turn integrations.
New thread
Omit threadId (or pass null).
A new thread is created automatically. The response includes the new threadId —
save it to continue the conversation.
Continue a thread
Pass an existing threadId. The node resumes the conversation with full
history context. The thread must belong to the same tenant and node.
Validation
If the provided threadId doesn't exist, or belongs to a different
tenant or node, the request fails with 404 Not Found.
threadId from the first response and pass it in all subsequent messages.
This is the standard pattern for multi-turn conversations.
Error responses
Errors are returned in the RFC 7807 Problem Details format:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "Bad Request",
"detail": "Message is required.",
"status": 400
}| Status | Meaning | Common causes |
|---|---|---|
| 400 | Bad Request | Missing content/message, empty body, or invalid JSON. |
| 401 | Unauthorized | Missing or invalid API key / JWT token (and node does not allow anonymous access). |
| 403 | Forbidden | Node-scoped API key used against a different node than the one it was issued for. |
| 404 | Not Found | Node not found, chat not enabled on this node, thread not found, or thread tenant/node mismatch. |
| 429 | Too Many Requests | Prepaid credit balance exhausted. Top up your account to resume usage. |
| 500 | Internal Server Error | Provider error or unexpected runtime failure. Include the requestId in support requests. |
5xx errors with exponential backoff. Do not retry 4xx errors without fixing the request.
During streaming, errors are delivered as [ERROR] SSE events instead of HTTP status codes (since headers have already been sent).
Credit enforcement
Interlocute uses a prepaid credit model. Before a chat request is processed:
- Reserve — credits are estimated and reserved based on message length and expected output.
- Execute — the node processes the request.
- Finalize — actual token usage is reconciled. Overestimates are refunded; underestimates are adjusted.
If your credit balance is insufficient, the request is rejected with 429 Too Many Requests
before any processing occurs. Credits are automatically refunded if the request fails or the client disconnects mid-stream.
Examples
Minimal: new thread
curl -X POST https://my-node.interlocute.ai/chat \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Hello! What can you do?"
}'Continue a thread with streaming
curl -N -X POST https://my-node.interlocute.ai/chat \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{
"content": "Tell me more about that.",
"threadId": "thr_abc123"
}'Full request: attachments + quoted contexts
curl -X POST https://my-node.interlocute.ai/chat \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "What does the highlighted section of this document mean?",
"threadId": "thr_abc123",
"clientMessageId": "my-client-id-001",
"quotedContexts": [
{
"sourceMessageId": "msg_prev_reply",
"sourceRole": "assistant",
"quotedText": "Revenue grew 15% quarter-over-quarter.",
"sourceTimestamp": "2025-01-10T09:00:00Z"
}
],
"attachments": [
{
"name": "q4-report.pdf",
"contentType": "application/pdf",
"dataUrl": "data:application/pdf;base64,JVBERi0xLjQK...",
"sizeBytes": 102400
}
]
}'Async: submit and poll
# 1. Submit (returns 202 immediately)
RESPONSE=$(curl -s -X POST https://my-node.interlocute.ai/chat \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Generate a detailed market analysis for Q3.",
"externalCorrelationId": "batch-job-42",
"options": { "responseMode": "async" }
}')
# 2. Keep the server-provided Runtime-relative polling URL
POLL_URL=$(echo "$RESPONSE" | jq -r '.pollUrl')
# 3. Poll until local observation settles, including after a cancellation request.
while true; do
STATUS=$(curl -s "https://api.interlocute.ai/$POLL_URL" \
-H "Authorization: Bearer $API_KEY")
TERMINAL=$(echo "$STATUS" | jq -r '.terminal // false')
if [ "$TERMINAL" = "true" ]; then
break
fi
sleep 3
done
# 4. Consume assistant output only for completed work; otherwise read the failure guidance
echo "$STATUS" | jq '{status: .status,
errorCode: .errorCode,
errorMessage: .errorMessage,
failureKind: .failureKind,
retryable: .retryable,
failureAction: .failureAction,
retryAfterSeconds: .retryAfterSeconds,
output: (if .status == "completed" then
[.outputs[]? | select(.kind == "assistantMessage") | .content]
else [] end)}'
Provider-credit exhaustion is reported as status: failed with
failureKind: provider_account,
retryable: false, and a safe
failureAction. The provider's diagnostic response is not exposed.
A retrieved terminal receipt is still returned with HTTP 200; use terminal
to stop polling rather than assuming only completed is terminal.
cancel_requested is still active. A terminal
unresolved outcome must not be replayed automatically; keep its ID for later readback.
If a GET returns 503, retry that GET with backoff. To cancel this job, use the
cancellation route.
Scheduled: send a message at a future time
curl -X POST https://my-node.interlocute.ai/chat \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Good morning! Here is your daily briefing.",
"threadId": "thr_abc123",
"options": {
"responseMode": "scheduled",
"scheduledAtUtc": "2025-07-16T09:00:00Z"
}
}'Triggers & automated invocations
The same chat processing pipeline powers scheduled and event-driven triggers. When a trigger fires, it invokes the node's chat capability with a system-generated message. Triggers support several thread modes that control how conversations are organized:
- New thread per run — each trigger execution creates a fresh thread
- Singleton per trigger — all executions of a trigger share a single, long-lived thread
- Fixed thread ID — the trigger always targets a specific, pre-existing thread
Deferred chat, triggers, and synchronous API calls all share the same invocation receipt infrastructure.
Every execution — regardless of mode — appears in the invocations log with a source
label (api-chat, schedule,
event, etc.) and full attribution (API key name, IP, user agent).
Use the invocationId or requestId
to correlate any execution back to its source.
Which response mode should I use?
| Scenario | Recommended mode | Why |
|---|---|---|
| Interactive chat UI (browser) | Streaming (SSE) | Users see tokens appear in real-time. Connection stays alive via continuous data flow. |
| Simple server-to-server, fast prompts | Buffered JSON | Single JSON response, easy to parse. Fine when prompts complete in <30 s. |
| Server-to-server, complex/long prompts | Async | Releases the connection after durable provider acceptance; poll the original receipt for the result. |
| Webhook / Zapier / n8n | Async | Short platform timeouts. Submit, get ID, process result asynchronously. |
| Batch processing | Batch API | Submit N independent items once, monitor one lifecycle, then page through correlated results. |
| Delayed messages ("send at 9 AM") | Scheduled | Message is held until scheduledAtUtc. Max 7 days. |
| Mobile / unreliable network | Async | Resilient to network drops. Poll when reconnected. |
| Non-urgent bulk summarization | Batch API | Use the supported provider batch path and follow the runtime lifecycle; provider terms vary. |
Next steps
- Batch API Reference — submit, monitor, cancel, and retrieve item results
- Chit API Reference — node discovery, invocation polling, and cancellation
- API Examples — copy-paste starters in cURL, C#, and JavaScript
- Auth & Keys — credential setup and key scoping
- Triggers — scheduled and event-driven execution
Track an async job with your own ID
Give one submission an externalInvocationId so your worker can
find its receipt even if the HTTP acknowledgement is lost. Use a new ID for each intended job.
IDs are unique within a node; the same ID on a different node is independent.
Integrations sharing a node can use prefixes such as orders:job-123.
curl -X POST https://my-node.interlocute.ai/chat \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"externalInvocationId": "orders:job-123",
"externalCorrelationId": "orders:conversation-42",
"content": "Summarize this status: deployment completed.",
"options": { "responseMode": "Async" }
}'The acknowledgement returns both identifiers. The ordinary internal-ID poll URL still works:
{
"disposition": "deferred",
"invocationId": "01JX4K7M2N...",
"externalInvocationId": "orders:job-123",
"nodeId": "your-node-id",
"threadId": "thread-456",
"status": "queued",
"responseMode": "async",
"externalCorrelationId": "orders:conversation-42",
"pollUrl": "nodes/your-node-id/chit/01JX4K7M2N..."
}curl -G https://my-node.interlocute.ai/chit/by-external-id \
-H "Authorization: Bearer YOUR_API_KEY" \
--data-urlencode "externalInvocationId=orders:job-123"- Retry the same ID with the same request to recover the original invocation. It returns 202 with the current status and does not start another execution.
- Changing content, thread selection, attachments, or options with the same ID returns
409 external_invocation_conflict. - A completed or failed invocation remains the same job. A deliberate new attempt needs a new external ID.
externalCorrelationIdselects a conversation;externalInvocationIdidentifies one operation.- Omit or set the field to null for the ordinary submission path. Empty IDs are invalid. Sync and Scheduled reject a supplied invocation alias with 400.
terminal is true. IDs remain reserved and are not automatically reusable.
Unknown aliases return 404; an alias whose dispatched receipt is no longer retained returns 410.
Interrupted preparation fails after its ten-minute lease expires on retry or lookup.
Direct Async executes at the provider and is recovered on demand through the same receipt after Runtime restart.
An alias never reruns dispatched work. See the Chit lookup reference.
To cancel by this same ID, POST /chit/by-external-id/cancel?externalInvocationId=orders%3Ajob-123
with your bearer token. Repeated cancellations preserve the original intent and receipt; they do not release the ID for reuse.
See cancellation responses and late outcomes.