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. Sessions hang off a deployed workflow (or an ephemeral TTL-bound deployment for dev runs) and expose their bidirectional frame stream over the WebSocket sub-resource documented at the end of this page.

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": "deployed",
"ownerId": "user_456",
"organizationId": "org_xyz",
"sharedWith": [],
"isPublic": false,
"nodes": [ /* node graph */ ],
"edges": [ /* edges */ ],
"createdAt": "2026-04-10T09:00:00Z",
"updatedAt": "2026-05-12T14:22:00Z"
}

Streaming Session Object

{
"session_id": "sess_abc123",
"workflow_id": "wf_abc123",
"deployment_id": "dep_xyz",
"status": "active",
"ownerId": "user_456",
"organizationId": "org_xyz",
"metadata": { "callerId": "+15551234567" },
"correlationId": "corr_789",
"dispositionSchemaVersion": "v1",
"estimatedDurationSeconds": 600,
"started_at": "2026-05-16T09:30:00Z",
"ready_at": "2026-05-16T09:30:02Z",
"ended_at": null,
"ws_url": "wss://app.strongly.ai/api/v1/streaming/sessions/sess_abc123/ws",
"ws_token": "<short-lived JWT>"
}

GET /api/v1/streaming-workflows

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

Scope: streaming-workflows:read

Query Parameters

ParameterTypeRequiredDescription
searchstringNoSearch by name or description
statusstringNoFilter by workflow status
limitintegerNoMax results (default 50, max 200)
offsetintegerNoPagination offset
sortstringNoSort field(s), -prefix for descending

Response 200 OK

{
"count": 4,
"limit": 50,
"offset": 0,
"items": [ /* Streaming Workflow objects */ ]
}

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, newest first.

Scope: streaming-workflows:read

Path Parameters

ParameterTypeRequiredDescription
idstringYesWorkflow ID

Response 200 OK

[
{
"_id": "dep_xyz",
"workflow_id": "wf_abc123",
"status": "ready",
"cpu": "1000m",
"memory": "2Gi",
"max_concurrent_sessions": 10,
"idle_timeout_seconds": 300,
"max_session_duration_seconds": 3600,
"created_at": "2026-05-12T14:22:00Z",
"updated_at": "2026-05-12T14:23:10Z"
}
]

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
offsetintegerNoPagination offset
sortstringNoSort field(s), -prefix for descending

Response 200 OK

{
"count": 12,
"limit": 50,
"offset": 0,
"items": [ /* Streaming Session objects */ ]
}

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

Deploy a streaming workflow. Routes through the unified workflows.deploy method, which branches on workflowType.

Scope: streaming-workflows:deploy

Path Parameters

ParameterTypeRequiredDescription
idstringYesWorkflow ID

Request Body

{
"cpu": "1000m",
"memory": "2Gi",
"disk": "20Gi",
"gpu": 0,
"gpu_type": null,
"idle_timeout_seconds": 300,
"max_session_duration_seconds": 3600,
"max_concurrent_sessions": 10
}
FieldTypeRequiredDescription
cpustringNoCPU request (Kubernetes form, e.g. 1000m)
memorystringNoMemory request (e.g. 2Gi)
diskstringNoEphemeral disk request (e.g. 20Gi)
gpuintegerNoGPU count
gpu_typestringNoGPU type / accelerator class
idle_timeout_secondsintegerNoIdle-session shutdown threshold
max_session_duration_secondsintegerNoHard cap on session length
max_concurrent_sessionsintegerNoPer-deployment concurrency cap

Response 201 Created

Returns the deployment record.


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

Undeploy a streaming workflow. Routes through the unified workflows.undeploy method.

Scope: streaming-workflows:deploy

Path Parameters

ParameterTypeRequiredDescription
idstringYesWorkflow ID

Response 200 OK

{ "workflowId": "wf_abc123", "status": "undeployed" }

POST /api/v1/streaming-sessions

Start a streaming session for a deployed streaming workflow. If the workflow has no deployment, the controller provisions an ephemeral TTL-bound deployment so dev "Run" works identically to batch.

Scope: streaming-sessions:write

Request Body

{
"workflowId": "wf_abc123",
"metadata": { "callerId": "+15551234567" },
"dispositionSchemaVersion": "v1",
"estimatedDurationSeconds": 600,
"correlationId": "corr_789"
}
FieldTypeRequiredDescription
workflowIdstringYesWorkflow to run
metadataobjectNoFree-form metadata attached to the session
dispositionSchemaVersionstringNoDisposition schema version the caller expects
estimatedDurationSecondsnumberNoEstimated session duration (used for the budget gate)
correlationIdstringNoCorrelation ID for tracing

Response 201 Created

{
"session_id": "sess_abc123",
"ws_url": "wss://app.strongly.ai/api/v1/streaming/sessions/sess_abc123/ws",
"ws_token": "<short-lived JWT>"
}

The ws_token is a short-lived JWT minted by the controller. Use it to authenticate the WebSocket upgrade described in WebSocket — /api/v1/streaming/sessions/:sessionId/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
offsetintegerNoPagination offset
sortstringNoSort field(s), -prefix for descending

Response 200 OK

{
"count": 5,
"limit": 50,
"offset": 0,
"items": [ /* Streaming Session objects */ ]
}

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 sessions outside the caller's tenant (cross-tenant existence is masked per ADR-S2).


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

List multi-agent handoff history for a session (read-only mirror of streaming_handoffs). Tenant-scoped: the caller must have access to the underlying session.

Scope: streaming-sessions:read

Path Parameters

ParameterTypeRequiredDescription
idstringYesSession ID

Response 200 OK

{
"handoffs": [
{
"_id": "ho_abc123",
"session_id": "sess_abc123",
"from_agent": "triage",
"to_agent": "billing",
"reason": "billing question",
"started_at": "2026-05-16T09:31:00Z",
"completed_at": "2026-05-16T09:31:02Z"
}
]
}

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

{ "injected": true }

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
offsetintegerNoPagination offset

Response 200 OK

{
"count": 38,
"limit": 50,
"offset": 0,
"items": [
{
"_id": "txn_abc",
"session_id": "sess_abc123",
"role": "user",
"text": "I'd like to check my balance.",
"emitted_at": "2026-05-16T09:30:05Z"
}
]
}

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

Session metric windows, sorted oldest-first.

Scope: streaming-sessions:read

Path Parameters

ParameterTypeRequiredDescription
idstringYesSession ID

Response 200 OK

[
{
"_id": "met_abc",
"session_id": "sess_abc123",
"window_start": "2026-05-16T09:30:00Z",
"window_end": "2026-05-16T09:30:10Z",
"asr_words": 18,
"tts_chars": 64,
"barge_in_count": 0
}
]

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

[
{
"_id": "err_abc",
"session_id": "sess_abc123",
"code": "stt.timeout",
"message": "STT call exceeded watchdog",
"node_id": "stt-1",
"timestamp": "2026-05-16T09:30:08Z"
}
]

WebSocket — /api/v1/streaming/sessions/:sessionId/ws

The session is the resource; /ws is the sub-resource that initiates a WebSocket upgrade. Meteor is the only public edge — external clients never reach the cluster-internal streaming-gateway or the pod directly. The proxy authenticates the upgrade, then pipes frames bidirectionally with no transcoding.

Path Parameters

ParameterTypeRequiredDescription
sessionIdstringYesSession ID (UUID-shaped: [A-Za-z0-9_-]{1,128})

Authentication

Pass the ws_token returned by POST /api/v1/streaming-sessions in one of:

  • Header (programmatic clients):
    Authorization: Bearer <ws_token>
  • Query string (browser WebSocket constructor, which cannot set headers):
    wss://app.strongly.ai/api/v1/streaming/sessions/<sessionId>/ws?ws_token=<ws_token>

The ws_token is a short-lived JWT signed with the platform JWT secret. The gateway validates it on connect.

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 1–19 digit numeric string; malformed values are ignored.

Frame Protocol

The connection carries raw frames in both directions with no transcoding at the proxy. 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. Application-level keepalive: the proxy sends WS pings every 15s to keep the upgrade alive under L4 connection tracking.

Close Codes

CodeMeaningClient Behavior
1000Normal closure (session ended cleanly)Done.
1011Proxy error (e.g. upstream connect failure after retry budget exhausted)Do not retry on this connection.
1012Session was reassigned to a different podReconnect — retryable.
1013Gateway/pod at capacity or cold-start in progressReconnect with exponential backoff — retryable.

The proxy itself transparently retries upstream 1012 and 1013 closes during the initial connect budget; clients only see them 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 UnauthorizedMissing or malformed ws_token
404 Not FoundUnknown session, or session is in a terminal state (only pending / scheduling / ready / connecting / active are proxyable)
500 Internal Server ErrorAuth lookup failed unexpectedly