Environment variable names in these examples follow the per-agent convention —
CXPA_<AGENT>_AGENT_API_KEY,CXPA_<AGENT>_INGEST_API_KEY. Keys are scoped per agent, so a single shared variable cannot serve two of them; name each after the agent it belongs to. The samples below useREVENUE_CHATas that agent.
Exports
| Symbol | Kind | Purpose |
|---|---|---|
triggerRun | function | Start a platform-managed run (async — returns runId immediately). |
triggerRunAndWait | function | Start a run and block until it terminates or a wait window elapses; returns the agent output inline. |
triggerRunAndDownload | function | Start a run and stream the runtime's binary response back as a ReadableStream — for agents whose output is a file. |
registerRun | function | Register an externally-triggered run and receive a credentials token. |
ingestRun | function | Report status for a standalone agent. |
createAgentClient | factory | Build a runtime client bound to a single (runId, agentApiKey). |
The trigger payload
Every platform-triggered run arrives at your task with this envelope. It is the same shape whichever surface started the run — Run Now, a schedule, a zone app, an external trigger or a webhook.
type CxpaPayload<TInput> = {
/** Platform run id. Its presence means the platform owns the run row. */
runId: string
/** The input for this run — see the tutorial for how each trigger path composes it. */
input?: TInput
/** integration_id → connection, for the agent's active connections. */
connectionIds?: Record<string, { integrationId: string; connectionId: string }>
/** 60-minute JWT authorising credential resolution. */
credentialsToken?: string
}
connectionIds and credentialsToken arrive together or not at all — an agent with no active connections receives neither. The agent API key is never in the payload; read it from the runtime environment.
triggerRun(options)
Wraps POST /api/agents/{agentId}/runs/trigger.
import { triggerRun } from '@cxpa/sdk/agent'
const { runId, status } = await triggerRun({
baseUrl: process.env.CXPA_API_URL!,
ingestApiKey: process.env.CXPA_REVENUE_CHAT_INGEST_API_KEY!,
agentId: 42,
input: { url: 'https://example.com' },
})
triggerRunAndWait(options)
Wraps POST /api/agents/{agentId}/runs/trigger-and-wait. Synchronous variant of triggerRun — the call blocks until the run reaches a terminal state or the server-side wait window elapses, then returns either the full Run row (with output_data) or a { timedOut: true } placeholder.
Use it for agents whose value is the output payload itself: PDF reports, generated documents, AI-rendered responses.
import { triggerRunAndWait } from '@cxpa/sdk/agent'
const result = await triggerRunAndWait({
baseUrl: process.env.CXPA_API_URL!,
ingestApiKey: process.env.CXPA_REVENUE_CHAT_INGEST_API_KEY!,
agentId: 42,
input: { topic: 'Q3 financial summary' },
timeoutMs: 120_000, // server further caps at agent's max-run-duration
})
if (result.timedOut) {
// Wait window elapsed; the run is still in flight. Fall back to polling.
console.log('still running:', result.runId)
} else if (result.status === 'completed') {
// result.output_data carries the agent payload
} else {
// 'failed' | 'cancelled'
throw new Error(`Run ${result.status}: ${result.error}`)
}
When to choose this over triggerRun
Use triggerRun | Use triggerRunAndWait |
|---|---|
| Caller doesn't need output inline | Caller hands the output straight to the end user |
| Run may take longer than ~5 minutes | Run completes in seconds or low minutes |
| You'll poll or wait for a callback yourself | You want one HTTP request that returns the result |
triggerRunAndDownload(options)
Wraps POST /api/agents/{agentId}/runs/trigger-and-download. For agents whose output is a generated file (PDF report, spreadsheet export, rendered image) rather than a JSON payload. The platform streams the runtime's response body straight through and never buffers it.
Only valid for platform-managed agents with output_kind = 'file_download' whose runtime supports streaming binary output. The platform records run metadata only (filename, mime type, byte count); the file bytes are not stored.
import { triggerRunAndDownload } from '@cxpa/sdk/agent'
import { writeFile } from 'node:fs/promises'
import { Readable } from 'node:stream'
const result = await triggerRunAndDownload({
baseUrl: process.env.CXPA_API_URL!,
ingestApiKey: process.env.CXPA_REVENUE_CHAT_INGEST_API_KEY!,
agentId: 42,
input: { topic: 'Q3 financial summary' },
})
await writeFile(result.filename, Readable.fromWeb(result.body))
// `result.runId`, `result.contentType`, `result.contentLength` available too.
The returned body is a ReadableStream<Uint8Array> — pipe it once. In a server-side handler the stream is typically forwarded straight to the caller's own HTTP response so the user's browser saves the file without any intermediate buffering on your side.
When to choose this over triggerRunAndWait
Use triggerRunAndWait | Use triggerRunAndDownload |
|---|---|
| Output is a JSON payload | Output is a binary file (PDF, spreadsheet, image) |
You want output_data inline in JSON | You want the bytes streamed to disk or to a browser |
registerRun(options)
Wraps POST /api/agents/{agentId}/runs/register.
import { registerRun } from '@cxpa/sdk/agent'
const { runId, credentialsToken, connectionIds } = await registerRun({
baseUrl: process.env.CXPA_API_URL!,
ingestApiKey: process.env.CXPA_REVENUE_CHAT_INGEST_API_KEY!,
agentId: 42,
externalRunId: 'run_abc123',
})
credentialsToken authorises credential resolution; connectionIds is the map it is spent against, keyed by integration_id:
const cxpa = createAgentClient({
baseUrl, apiKey: process.env.CXPA_REVENUE_CHAT_AGENT_API_KEY!, runId, credentialsToken,
})
const cred = await cxpa.resolveCredential(connectionIds!.openai.connectionId)
Both fields are optional and arrive together or not at all — an agent with no active connections receives neither, since there would be nothing to resolve. The shape matches what the platform sends in the task payload when it triggers a run itself, so the same resolution code works on either path.
ingestRun(options)
Wraps POST /api/agents/{agentId}/runs/ingest. For managed_by = 'standalone' agents only.
import { ingestRun } from '@cxpa/sdk/agent'
await ingestRun({
baseUrl,
ingestApiKey,
agentId,
externalRunId: 'exec_001',
status: 'completed',
output: { pages: 42 },
outputs: 42,
})
createAgentClient(options)
Returns a client bound to one (runId, agentApiKey). Use it from inside a runtime task.
import { createAgentClient } from '@cxpa/sdk/agent'
const cxpa = createAgentClient({
baseUrl,
// From the runtime environment — the payload never carries the agent key.
apiKey: process.env.CXPA_REVENUE_CHAT_AGENT_API_KEY!,
runId: triggerPayload.runId,
credentialsToken: triggerPayload.credentialsToken,
})
The trigger payload carries runId, input, connectionIds and credentialsToken. The agent API key is the one factor it never contains — set it as an environment variable on the runtime. Keys are scoped per agent, so give each agent its own variable rather than sharing one.
Methods
| Method | HTTP under the hood |
|---|---|
started() | POST /callback { type: 'started' } |
output({ output, outputs? }) | POST /callback { type: 'output' } (intermediate) |
complete({ output, outputs? }) | POST /callback { type: 'output', complete: true } |
fail({ message, name?, stack? }) | POST /callback { type: 'output', failed: true, error } |
waiting({ tokenId, description, url?, output? }) | POST /callback { type: 'waiting' } — optional url overrides the {{run_url}} notification link |
completeWaitpoint(payload?) | POST /api/runs/{runId}/waitpoint (resume a paused HITL run) |
resolveCredential(connectionId) | GET /api/credentials/{id}/resolve |
resolveCredential requires credentialsToken to have been passed at construction.
Errors
Every function and client method on this subpath throws ApiError on a non-2xx response — the same class the zone client uses. It is exported from @cxpa/sdk/agent and from @cxpa/sdk.
import { createAgentClient, ApiError } from '@cxpa/sdk/agent'
try {
await cxpa.resolveCredential(connectionId)
} catch (err) {
if (err instanceof ApiError && err.status === 401) {
// Key or credentials token rejected — see the endpoint's error table.
}
throw err
}
err.status is the HTTP status and err.message carries the platform's error string where one was returned. Status meanings are per endpoint — see Errors.