Skip to main content

Streaming Workflows

Manage streaming workflows and their long-lived sessions (voice, telephony, WebRTC, WebSocket pipelines). Streaming workflows are the same workflows documents as batch; the workflowType: "streaming" filter selects them. A session runs either on the workflow's production deployment or, as a draft, on the caller's own test worker, and exposes its bidirectional frame stream over the WebSocket sub-resource documented at the end of this page. For building and deploying streaming workflows in the UI, see Streaming Workflows.

All endpoints require authentication via X-API-Key header and the appropriate scope.


Streaming Workflow Object​

{
"_id": "wf_abc123",
"name": "voice-intake",
"description": "Voice agent that triages inbound calls.",
"workflowType": "streaming",
"status": "active",
"version": 1,
"ownerId": "user_456",
"ownerName": "Jane Smith",
"organizationId": "org_xyz",
"sharedWith": [],
"isPublic": false,
"isTemplate": false,
"nodes": [ /* node graph */ ],
"connections": [ /* connections */ ],
"scopes": {},
"settings": {
"autoSave": true,
"timeout": 300000,
"retryAttempts": 3,
"enableLogging": true,
"logLevel": "info",
"recording": { "enabled": false }
},
"tags": [],
"createdAt": "2026-04-10T09:00:00Z",
"lastUpdated": "2026-05-12T14:22:00Z"
}

This is the same document the Workflows endpoints return, with workflowType: "streaming".

Streaming Session Object​

{
"_id": "sess_abc123",
"sessionId": "sess_abc123",
"deploymentId": "dep_xyz",
"workflowId": "wf_abc123",
"workflowVersionId": "ver_003",
"organizationId": "org_xyz",
"userId": "user_456",
"status": "active",
"startedAt": "2026-05-16T09:30:00Z",
"connectedAt": "2026-05-16T09:30:02Z",
"endedAt": null,
"durationSeconds": null,
"turnCount": 4,
"audioSecondsIn": 42.5,
"audioSecondsOut": 38.1,
"summary": null,
"disposition": null,
"transcriptLength": 8,
"errorCount": 0,
"fatalError": null,
"failureReason": null,
"idleTimeoutSeconds": 300,
"maxDurationSeconds": 3600,
"correlationId": "corr_789",
"metadata": { "callerId": "+15551234567" },
"createdAt": "2026-05-16T09:30:00Z",
"updatedAt": "2026-05-16T09:31:10Z"
}

status is one of pending, scheduling, ready, connecting, active, draining (not finished) or ended, failed, orphaned (finished). A failed session's failureReason says why, and the last state_history entry carries the full cause. deployment_failed means the session's pod could never start (for example, Kubernetes reports CreateContainerConfigError or ImagePullBackOff). The session fails as soon as its deployment does, rather than waiting in pending. The WebSocket URL and token are returned only when the session is started (see POST /streaming-sessions); they are not part of the session record.


GET /api/v1/streaming-workflows​

List streaming workflows accessible to the caller (owned, shared, or open to all users within the org).

Scope: streaming-workflows:read

Query Parameters

ParameterTypeRequiredDescription
qstringNoSearch by name or description
statusstringNoFilter by workflow status
limitintegerNoMax results (default 50, max 200)
cursorstringNometa.nextCursor of the previous page; omit for the first page
sortstringNoSort field(s), -prefix for descending

Response 200 OK

{
"data": [ /* Streaming Workflow objects */ ],
"meta": {
"total": 4,
"limit": 50,
"nextCursor": null,
"requestId": "req_abc123"
}
}

GET /api/v1/streaming-workflows/:id​

Get a single streaming workflow by ID.

Scope: streaming-workflows:read

Path Parameters

ParameterTypeRequiredDescription
idstringYesWorkflow ID

Response 200 OK

Returns the full Streaming Workflow object.


GET /api/v1/streaming-workflows/:id/deployments​

List deployments for a streaming workflow, most recently updated first. A deployment's status is one of deploying, deployed, undeploying, undeployed or failed; the workflow's production deployment is the newest row with ephemeral not true (ephemeral: true rows are builder test workers).

Scope: streaming-workflows:read

Path Parameters

ParameterTypeRequiredDescription
idstringYesWorkflow ID

Response 200 OK

{
"data": [
{
"_id": "streaming-wf_abc123",
"deploymentId": "streaming-wf_abc123",
"workflowId": "wf_abc123",
"workflowName": "voice-intake",
"status": "deployed",
"resources": { "cpu": "1", "memory": "2Gi", "disk": "5Gi", "gpu": "0", "gpuType": null },
"idleTimeoutSeconds": 300,
"maxSessionDurationSeconds": 3600,
"maxConcurrentSessions": 10,
"autoscaling": {
"enabled": true,
"minReplicas": 0,
"maxReplicas": 4,
"targetSessionsPerPod": 4,
"scaleDownCooldownSeconds": 300
},
"activeSessionCount": 2,
"totalSessionCount": 37,
"lastDeployedAt": "2026-05-12T14:22:00Z",
"createdAt": "2026-05-12T14:22:00Z",
"updatedAt": "2026-05-12T14:23:10Z"
}
],
"meta": {
"requestId": "req_abc123"
}
}

GET /api/v1/streaming-workflows/:id/sessions​

List streaming sessions for a workflow.

Scope: streaming-sessions:read

Path Parameters

ParameterTypeRequiredDescription
idstringYesWorkflow ID

Query Parameters

ParameterTypeRequiredDescription
statusstringNoFilter by session status. Pass active to match any non-terminal state (pending / scheduling / ready / connecting / active / draining).
limitintegerNoMax results
cursorstringNometa.nextCursor of the previous page; omit for the first page
sortstringNoSort field(s), -prefix for descending

Response 200 OK

{
"data": [ /* Streaming Session objects */ ],
"meta": {
"total": 12,
"limit": 50,
"nextCursor": null,
"requestId": "req_abc123"
}
}

POST /api/v1/streaming-workflows/:id/deploy​

Deploy a streaming workflow. The graph is validated first (one trigger, at least one response, required inputs and connectors wired, every connection between declared ports that share a frame type); a graph that fails is refused with the problems listed. The call then returns as soon as the deploy is queued, and the deployment proceeds in the background. Poll GET /streaming-workflows/:id/deployments (or the statusUrl in the response) until the deployment's status is deployed.

Settings the call leaves out keep the values of the workflow's previous deploy, else the defaults below.

Scope: streaming-workflows:deploy

Path Parameters

ParameterTypeRequiredDescription
idstringYesWorkflow ID

Request Body

{
"cpu": "1",
"memory": "2Gi",
"disk": "5Gi",
"gpu": "0",
"idleTimeoutSeconds": 300,
"maxSessionDurationSeconds": 3600,
"maxConcurrentSessions": 10,
"autoscaling": {
"enabled": true,
"minReplicas": 0,
"maxReplicas": 4,
"targetSessionsPerPod": 4,
"scaleDownCooldownSeconds": 300
}
}
FieldTypeRequiredDescription
cpustringNoWorker CPU (e.g. 1). Give it with memory; omit both to size the worker from the nodes in the graph
memorystringNoWorker memory (e.g. 2Gi), with cpu
diskstringNoWorker disk (e.g. 5Gi)
gpustringNoGPU count as a string (e.g. "1")
gpuTypestringNoGPU type, when gpu is more than 0
idleTimeoutSecondsintegerNoA session with no activity this long is ended. 60 to 86400, default 300
maxSessionDurationSecondsintegerNoA session is ended at this length. 300 to 604800, default 3600
maxConcurrentSessionsintegerNoMost sessions at once; a start beyond it is refused. 1 to 10000, default no limit
autoscalingobjectNoScaling, below. Default: enabled: false

autoscaling

FieldTypeDescription
enabledbooleanfalse: one worker, started by the first session and stopped after the cooldown once the last session ends. true: minReplicas to maxReplicas workers, one per targetSessionsPerPod sessions
minReplicasinteger0 to 20, default 0. 0 stops every worker when there are no sessions; the next session starts one
maxReplicasinteger1 to 50, at least minReplicas, default 10
targetSessionsPerPodinteger1 to 100, default 10
scaleDownCooldownSecondsinteger60 to 900, default 300

Response 202 Accepted​

The workflow, as GET /streaming-workflows/:id shows it, deploymentStatus queued. Poll its deployments until a replica is ready.


POST /api/v1/streaming-workflows/:id/undeploy​

Undeploy a streaming workflow.

Scope: streaming-workflows:deploy

Path Parameters

ParameterTypeRequiredDescription
idstringYesWorkflow ID

Response 200 OK​

The workflow, as GET /streaming-workflows/:id shows it, back to draft.


POST /api/v1/streaming-sessions​

Start a streaming session. A production session runs the workflow's deployed version on its deployment, as the user who deployed it. A draft session runs the workflow's saved definition on the caller's own test worker (the builder's Run), as the caller; it never uses the deployment. Without mode, a deployed workflow's session is production and an undeployed workflow's is draft. Starting a production session for a workflow that is not deployed is refused, as is a session beyond the deployment's maxConcurrentSessions.

Scope: streaming-sessions:write

Request Body

{
"workflowId": "wf_abc123",
"metadata": { "callerId": "+15551234567" },
"dispositionSchemaVersion": "v1",
"estimatedDurationSeconds": 600,
"correlationId": "corr_789"
}
FieldTypeRequiredDescription
workflowIdstringYesWorkflow to run
modestringNoproduction or draft, see above
metadataobjectNoFree-form metadata attached to the session
dispositionSchemaVersionstringNoDisposition schema version the caller expects
correlationIdstringNoCorrelation ID for tracing

Response 201 Created

{
"data": {
"sessionId": "sess_abc123",
"wsUrl": "wss://app.strongly.ai/api/v1/streaming/sessions/sess_abc123/<session token>/ws",
"wsToken": "<session token>"
},
"meta": {
"requestId": "req_abc123"
}
}

wsUrl is the WebSocket address for the session, with wsToken (a short-lived session token) already in its path. When the caller is an app or a workflow running on the platform, wsUrl is the platform's internal address, which the caller can reach directly, and the response also carries publicWsUrl, the public address to hand to the caller's own browser users or a phone carrier. Connect as described in WebSocket -- /api/v1/streaming/sessions/:sessionId/:wsToken/ws.


GET /api/v1/streaming-sessions​

List streaming sessions accessible to the caller.

Scope: streaming-sessions:read

Query Parameters

ParameterTypeRequiredDescription
workflowIdstringNoFilter by workflow ID
statusstringNoFilter by status. Pass active to match any non-terminal state.
limitintegerNoMax results
cursorstringNometa.nextCursor of the previous page; omit for the first page
sortstringNoSort field(s), -prefix for descending

Response 200 OK

{
"data": [ /* Streaming Session objects */ ],
"meta": {
"total": 5,
"limit": 50,
"nextCursor": null,
"requestId": "req_abc123"
}
}

GET /api/v1/streaming-sessions/:id​

Get a single streaming session.

Scope: streaming-sessions:read

Path Parameters

ParameterTypeRequiredDescription
idstringYesSession ID

Response 200 OK

Returns the full Streaming Session object. Returns 404 not-found for a session the caller cannot access, so the response never reveals whether another organization's session exists.


GET /api/v1/streaming-sessions/:id/handoffs​

List the multi-agent handoff history for a session (read-only). The caller must have access to the session.

Scope: streaming-sessions:read

Path Parameters

ParameterTypeRequiredDescription
idstringYesSession ID

Response 200 OK

{
"data": {
"handoffs": [
{
"_id": "ho_abc123",
"sessionId": "sess_abc123",
"fromAgent": "triage",
"toAgent": "billing",
"reason": "billing question",
"startedAt": "2026-05-16T09:31:00Z",
"completedAt": "2026-05-16T09:31:02Z"
}
]
},
"meta": {
"requestId": "req_abc123"
}
}

DELETE /api/v1/streaming-sessions/:id​

End a streaming session.

Scope: streaming-sessions:write

Path Parameters

ParameterTypeRequiredDescription
idstringYesSession ID

Response 204 No Content


POST /api/v1/streaming-sessions/:id/inject​

Inject a text message into a running session -- useful for surfacing operator messages, simulated user turns, or out-of-band instructions on top of the live audio/video stream.

Scope: streaming-sessions:write

Path Parameters

ParameterTypeRequiredDescription
idstringYesSession ID

Request Body

{
"text": "Please ask the caller for their account number.",
"role": "system"
}
FieldTypeRequiredDescription
textstringYesMessage body to inject
rolestringNoRole to attribute the message to (e.g. system, user, assistant)

Response 200 OK

{
"data": {
"sessionId": "sess_abc123",
"podStatus": 200,
"body": {
"success": true,
"injected": "Please ask the caller for their account number."
}
},
"meta": {
"requestId": "req_abc123"
}
}

GET /api/v1/streaming-sessions/:id/transcript​

Paginated transcript for a session, sorted oldest-first.

Scope: streaming-sessions:read

Path Parameters

ParameterTypeRequiredDescription
idstringYesSession ID

Query Parameters

ParameterTypeRequiredDescription
limitintegerNoMax results
cursorstringNometa.nextCursor of the previous page; omit for the first page

Response 200 OK

{
"data": [
{
"_id": "txn_abc",
"sessionId": "sess_abc123",
"role": "user",
"text": "I'd like to check my balance.",
"emittedAt": "2026-05-16T09:30:05Z"
}
],
"meta": {
"total": 38,
"limit": 50,
"nextCursor": null,
"requestId": "req_abc123"
}
}

GET /api/v1/streaming-sessions/:id/errors​

List streaming errors recorded for a session, oldest-first.

Scope: streaming-sessions:read

Path Parameters

ParameterTypeRequiredDescription
idstringYesSession ID

Response 200 OK

{
"data": [
{
"_id": "err_abc",
"sessionId": "sess_abc123",
"code": "stt.timeout",
"message": "STT call exceeded watchdog",
"nodeId": "stt-1",
"timestamp": "2026-05-16T09:30:08Z"
}
],
"meta": {
"requestId": "req_abc123"
}
}

WebSocket -- /api/v1/streaming/sessions/:sessionId/:wsToken/ws​

The session is the resource; /ws is the sub-resource that initiates a WebSocket upgrade. Clients connect only to this public endpoint, never to the session's pod directly. The endpoint authenticates the upgrade, then passes frames in both directions with no transcoding.

Path Parameters

ParameterTypeRequiredDescription
sessionIdstringYesSession ID ([A-Za-z0-9_-]{1,128})
wsTokenstringYesThe wsToken returned by POST /api/v1/streaming-sessions

Authentication

The session token travels in the URL path, not in a header or query string, so the same URL works for browsers (whose WebSocket constructor cannot set headers), programmatic clients and telephony carriers that strip query strings. Use the wsUrl returned by POST /api/v1/streaming-sessions as is:

wss://<your-instance>/api/v1/streaming/sessions/<sessionId>/<wsToken>/ws

The wsToken is short-lived and valid only for this session.

Resume Cursor

After a disconnect, clients may pass ?resume_from=<watermark> to ask the upstream pod to replay buffered JSON frames from that watermark. The cursor must be a numeric string of 1 to 19 digits; malformed values are ignored.

Frame Protocol

The connection carries raw frames in both directions with no transcoding. The Strongly streaming frame schema (audio, transcript, tool-call, error, disposition, etc.) is defined alongside the pipeline node catalog; see the streaming pipeline reference for the full frame roster. Keepalive: WebSocket pings are sent every 15 seconds so an idle connection stays open.

Close Codes

When the session side closes, the client connection is closed with the same code and reason.

CodeMeaningClient Behavior
1000Normal closure (session ended cleanly)Done.
1011The connection to the session failed after the upgradeDo not retry on this connection.
1012Session was reassigned to a different podReconnect -- retryable.
1013Session capacity reached or cold start in progressReconnect with exponential backoff -- retryable.

During the initial connect the upgrade is held open while the session's pod starts (from zero this can take minutes): 1006, 1011, 1012 and 1013 from the pod side are retried for you for as long as the session is still waiting for its pod (pending, scheduling, ready or connecting). Clients only see these codes if they occur after the upgrade is live.

Pre-Upgrade Failure Responses

The upgrade may fail before the WebSocket is established. These come back as plain HTTP responses:

StatusMeaning
401 UnauthorizedMalformed path, or missing or malformed wsToken
404 Not FoundUnknown session, or session is in a terminal state (only pending / scheduling / ready / connecting / active can be connected to)
500 Internal Server ErrorThe session lookup failed unexpectedly
502 Bad GatewayConnecting to the session failed
503 Service UnavailableThe session stopped waiting for its pod before it connected: it failed or ended (read GET /streaming-sessions/:id for failureReason), or the pod side refused the connection

GET /api/v1/streaming-sessions/:id/status​

Whether the deployment behind a session is ready: sessionId, session (status, workflowId, deploymentId, startedAt, endedAt) and deployment (its deploymentId, deploymentName, namespace, status and health). The session is ready once deployment.health.replicas.ready is at least 1.

Scope: streaming-sessions:read

ParameterTypeRequiredDescription
idstringYesSession id

Response 200 OK


GET /api/v1/streaming-sessions/:id/logs​

The latest log lines of the pod behind a session.

Scope: streaming-sessions:read

ParameterTypeRequiredDescription
idstringYesSession id
linesintegerNoHow many lines (at most 2000)
containerstringNostreaming-worker (default), auth-proxy or egress-proxy

Response 200 OK: the log lines.


GET /api/v1/streaming-sessions/:id/recordings​

A session's recordings, when it ran with settings.recording.enabled: each one's audio, transcript and manifest keys and their bucket.

Scope: streaming-sessions:read

ParameterTypeRequiredDescription
idstringYesSession id
limitintegerNoResults per page
cursorstringNometa.nextCursor of the previous page

Response 200 OK: a page of recordings.