Trigger and download

Start a run for a platform-managed agent whose value is a generated file — PDF report, spreadsheet export, rendered image — and stream the runtime's binary response straight through to the caller. The platform never buffers the body and never stores it; only metadata (filename, mime type, byte count) is recorded on the run row for tracking.

Endpoint

POST /api/agents/{agentId}/runs/trigger-and-download

Auth: Authorization: Bearer <ingest_api_key>

Eligibility

  • managed_by = 'platform'
  • status = 'active'
  • allow_external_triggers = true
  • output_kind = 'file_download' — set this on the agent settings page

Anything else returns 422 with an explanatory message.

Runtime support

The workflow MUST terminate in a Respond to Webhook node configured to return binary data — typically a previous node's binary output bound to "Response Data" — with Content-Type (application/pdf, text/csv, image/png, …) and Content-Disposition (attachment; filename="report.pdf") headers set on the response. The platform passes both headers through to the caller verbatim.

If the workflow returns a JSON body instead (for example because a file-producing node failed), the platform surfaces it as a failed run and returns 502 with the parsed JSON — not a corrupt binary.

Optional X-Outputs response header

The runtime may include X-Outputs: <non-negative integer> on the response. The platform lifts that value into runs.outputs — the same metric column the JSON outputs field populates on the trigger-and-wait endpoint. For a one-file-per-run agent, 1 is the natural value. Anything missing or non-integer is silently dropped (same rule as the async callback flow).

Request

json
{
  "input": { "topic": "Q3 financial summary" },
  "timeoutMs": 120000
}
FieldTypeRequiredDescription
inputobjectnoMerged on top of the agent's saved default input; caller's values win.
userIduuidnoAttribution for runs.created_by. Unknown / non-member values are dropped silently.
timeoutMsnumbernoMaximum time the server may wait for the runtime to begin streaming, in milliseconds. Server further caps at the agent's running_timeout_ms and a platform hard ceiling. Defaults to 60 000 ms when omitted.

Response — 200 OK (streaming)

The runtime began streaming within the wait window. Response headers carry the platform run id and the runtime's content metadata; the response body is the binary file.

HeaderDescription
Content-TypePass-through of the runtime's content type (e.g. application/pdf).
Content-DispositionPass-through of the runtime's disposition. Synthesized as attachment; filename="<derived>" if the runtime omitted it.
Content-LengthPass-through when the runtime sent one (omitted on chunked transfers).
X-Run-IdPlatform-side run id. Use it to correlate with GET /api/agents/{agentId}/runs/{runId}.
Cache-ControlAlways no-store. The bytes are run-specific and uncacheable.
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="q3-summary.pdf"
Content-Length: 124583
X-Run-Id: 1234
Cache-Control: no-store

<binary pdf bytes>

Response — 502 Bad Gateway (runtime returned a JSON error)

The runtime executed but returned a JSON payload instead of a binary file — the typical shape of a failed workflow. The run row is marked failed. The body is JSON, not a binary; do not save it as a file.

json
{
  "error": "WorkflowReturnedJson: { \"error\": \"PDF rendering failed\" }",
  "runId": 1234
}

Other errors

StatusReason
400Invalid request body.
401Missing or invalid bearer token.
403allow_external_triggers is off for this agent.
422Agent is not platform-managed proper runtime environment with output_kind = 'file_download', or is not active.
429Per-agent rate limit exceeded (5/min — same posture as /trigger-and-wait; each call holds a connection for up to the wait window).
504Upstream runtime did not respond within the wait window. The run row is marked failed.
500Runtime invocation failed before streaming could begin.

What lands on the run row

The streamed bytes are never stored on the platform. Instead, runs.output_data carries a small metadata object so the run-detail page can render a summary:

jsonc
{
  "kind": "file_download",
  "filename": "q3-summary.pdf",
  "mimeType": "application/pdf",
  "sizeBytes": 124583
}

runs.outputs is populated from the optional X-Outputs response header (see above). duration_ms and completed_at are stamped when the stream finishes flushing, so they reflect end-to-end generation + transfer time.

Re-download is not available — the file isn't replayable. Trigger another run to regenerate.

Choosing between the three sync endpoints

Use /triggerUse /trigger-and-waitUse /trigger-and-download
Caller doesn't need output inlineOutput is a JSON payload the caller rendersOutput is a binary file the caller hands to the end user
Caller polls or waits for a callbackCaller wants the JSON in the same responseCaller wants the bytes streamed to disk or to a browser
Run may take longer than a few minutesRun completes in seconds or low minutesRun completes in seconds or low minutes

SDK

ts
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_INGEST_KEY!,
  agentId: 42,
  input: { topic: 'Q3 financial summary' },
})

await writeFile(result.filename, Readable.fromWeb(result.body))
console.log(`Saved ${result.filename} (run #${result.runId})`)

Consume result.body exactly once — the SDK does not buffer it. In a web handler, forward it straight to the caller's response so the browser saves the file without any intermediate buffering on your side.