Skip to main content
Partners submit a task in plain text and follow it to completion over a live SSE stream, using the open A2A protocol instead of a one-off integration.

Discovery

GET /.well-known/agent-card.json — public, no auth. Declares the endpoint URL and the bearer auth scheme a client should use.

Authentication

Authenticate using a Cyberwave API Token from your profile page. The token must belong to the target workspace and include the a2a scope key. Send it on every request:
Use a workspace-scoped API token for the full task lifecycle. User session tokens are not supported for starting tasks.

Endpoint

POST /api/v1/a2a — single JSON-RPC 2.0 endpoint ({jsonrpc, id, method, params}). Auth: Authorization: Bearer <APIToken>, a workspace-scoped partner token (not a user session token). params.metadata.storeId selects the target Environment by the last segment of its slug: for acme/envs/aisle-a3, send aisle-a3. The Environment must belong to the token’s workspace. SendMessage (non-streaming) and GetExtendedAgentCard are recognized but return UnsupportedOperationError — not implemented yet.

Start a task and receive updates

The response to this POST is the update stream; there is no separate GET stream URL to open. curl -N disables output buffering so events appear as they arrive. Keep the connection open until you receive finished. A successful streaming response has Content-Type: text/event-stream. Planning and initial dispatch happen before streaming begins, so the first event may take time to arrive. Check both the HTTP status and response content type: a rejected request can return JSON instead of SSE, including a JSON-RPC error with HTTP 200.

Event format

Each SSE frame contains an event: name and a data: line containing JSON, followed by a blank line. The JSON has its own type field; task payloads are nested inside data.
These are Cyberwave’s current event envelopes. Streaming frames do not wrap their payload in a JSON-RPC result object. Parse the shapes below when integrating a client, even if your A2A library expects standard status or artifact update envelopes.
For example, a successful task could emit the following frames. The identifiers, robot name, and summary are illustrative:
The first task event contains the current state, which may already be terminal for a fast task or a retried request. Do not require an update before accepting finished. Treat finished as the authoritative end of the task; final is only transport bookkeeping and uses the internal lowercase state spelling.

Progress fields and task states

An update is a full current snapshot, not a text delta or a token stream:
  • reasoning.central contains task-level progress entries.
  • reasoning.robots lists robot assignments with robotId, name, role, status, and steps.
  • Robot status values are lowercase: queued, running, completed, failed, cancelled, or blocked. These differ from the task-level state values below.
The current implementation leaves central and steps empty; clients should show task and robot status even when there is no explanatory text. videos is also currently an empty list; video results are not populated by this API. A finished event can report any terminal outcome, so check data.state before marking a task successful.

Stream lifetime and client handling

Progress is checked approximately once a second, and updates are emitted only when task state, robot status, or progress-entry counts change. This is a status stream, not a continuous telemetry feed. Ignore SSE comment lines such as : keep-alive; they are not task events. The current stream loop has a 30-minute limit. If the task is still nonterminal at that limit, it is marked failed, in-flight assignments are canceled, and finished reports TASK_STATE_FAILED with a timeout summary. Use an HTTP client that supports a streaming POST with an Authorization header. The browser’s native EventSource interface does not support this request shape. When implementing a reader:
  1. Buffer incoming bytes until complete SSE frames are available; network chunks do not necessarily align with frames.
  2. Parse the JSON from each frame’s data: field and dispatch on type.
  3. Save data.id from task and update progress from subsequent update events.
  4. On finished, store the state and summary and close the reader.
  5. If the stream errors or closes before finished, check the task and reconnect as described below. A disconnected stream does not mean the task completed or was canceled.

Environment control policy

The selected Environment controls which execution routes A2A may use: Every Environment, new or existing, defaults to Direct twin control and workflows so upgrading never blocks a routine that worked before. Environment authors configure this under Environment settings → Advanced → Control plane access. Choosing Direct twin control only disables workflow execution everywhere for that Environment, not only for A2A — the same manual trigger used by the web app’s Run button and the SDK is gated too. In workflows-only mode, nodes inside an allowed workflow may still command twins; the restriction applies to the external A2A request, not to the workflow’s authored behavior. Only active, non-deleted workflows attached to the selected Environment, visible to the caller, and containing an enabled manual trigger are candidates. If no permitted route matches the request, the task finishes in the rejected state without dispatching a twin command. When A2A starts a workflow, the task’s metadata.workflow object identifies the selected workflow and execution:
The A2A task remains working while the workflow runs and mirrors its terminal state: workflow success becomes completed, an execution error becomes failed, and cancellation becomes canceled.

Reconnect to an existing task

Once you have a task ID, POST SubscribeToTask (or tasks/resubscribe) to the same endpoint with params.id. Use an API token from the same workspace:
Replace the example ID with the data.id from your initial task event. The top-level JSON-RPC id identifies this request; params.id identifies the task. The reattached stream starts immediately with an update containing the current snapshot, not another task event. It then emits changed snapshots and a terminal finished event using the same shapes as the original stream. Resubscription does not replay missed events. There are no SSE event IDs or Last-Event-ID replay support. If the task is already terminal, SubscribeToTask returns -32004 (UnsupportedOperationError) as JSON; use GetTask to read its state. A task can finish between polling and reconnecting, so handle this error even after a nonterminal poll result. If the original connection closed before you received a task ID, retry message/stream with the original message.messageId and request payload to recover the existing task without dispatching it again.

Poll or cancel a task

GetTask (tasks/get) returns a normal JSON response rather than a stream:
Its result has the same task envelope as the initial stream event:
GetTask returns task state and robot identities; it does not return the update reasoning snapshot or the finished summary and videos. Persist the finished payload if your integration needs those results later. To cancel, send the same request with method: "tasks/cancel" (alias CancelTask). It returns a task envelope with TASK_STATE_CANCELED; an attached stream reports the terminal outcome through finished. Canceling an already terminal task returns -32002 (TaskNotCancelableError). Closing your client connection is not a cancellation request.

Error handling

Standard JSON-RPC (-32700 parse error, -32600 invalid request, -32601 method not found, -32602 invalid params, -32603 internal error), plus -32001 (task not found), -32002 (task not cancelable), -32004 (unsupported operation). JSON-RPC errors normally return HTTP 200 with an error object instead of a stream. For example, reconnecting after completion returns:
Missing or invalid authentication returns HTTP 401. A token without the a2a scope returns HTTP 403. Check these HTTP failures as well as JSON-RPC errors. An error event after the SSE response has started is a transport error, not a finished task outcome; poll the task to determine its state.

Retries and idempotency

Always send message.messageId, using a unique value for each new logical task. If a request is retried with the same messageId (e.g. after a dropped SSE connection before a finished event arrived), the API returns the original task instead of dispatching the same robots again. Deduplication is scoped to the workspace, so keep the same ID for retries of one logical request and use a new ID for each new task. A different messageId is always treated as a new task, even if the text is identical to a previous request.
messageId is optional for backward compatibility, but a request that omits it gets no idempotency protection at all: every such call starts a new task and dispatches robots again, even if it is an exact retry of the previous one. Partners should always set messageId.