interlocute.ai beta

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.

Node-scoped API keys add a layer of isolation: even if a key is compromised, it can only access the single node it was issued for. A 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.
Capability support is model-specific. The Runtime validates every explicit value before provider work and returns 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.
Search is optional. If the provider, model or node/thread capability does not support it, the message still runs without search. Check 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.
Quoted contexts require the node's prompt configuration to include quoted context support. If the node does not have this capability enabled, the quoted contexts are accepted but may not influence the response.

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
  }]
}
Postman / cURL users: use the 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.
For 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..."
}
The 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
  }
}
A completed receipt with an 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.

For direct Async, start polling at 1 second and back off to 3–5 seconds. Economy follows the provider's batch completion window. Follow the acceptance's 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=....

Cancellation delivery uncertainty returns 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.

Timeout guidance: If you are calling the buffered JSON mode (no streaming) from a server-side integration, consider using 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.

Store the 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.
Retry on 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:

  1. Reserve — credits are estimated and reserved based on message length and expected output.
  2. Execute — the node processes the request.
  3. 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

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.
  • externalCorrelationId selects a conversation; externalInvocationId identifies 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.
Poll until 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.