Integrate & ship
API & webhooks
One endpoint runs a swarm from your own code: POST /api/swarm/run. Scoped keys, idempotent retries, and signed callbacks for anything slower than a request.
API keys
Create one from the swarm's Deploy dialog. Keys look like sk_swarm_… and the secret is shown once — it is stored hashed and cannot be recovered, only rotated.
| Property | Values | Notes |
|---|---|---|
scopes | run, read_runs | Defaults to ['run']. A database constraint rejects any other value, so a typo fails at write time rather than silently granting nothing. |
expires_at | timestamp / null | Optional expiry. Set one on keys for a specific integration. |
revoked_at | timestamp / null | Revocation is immediate — the next request fails closed. |
rotated_from | key id | Set on a replacement key, so you can see what superseded what. |
last_used_ip | text | Source IP of the last request — an unused key is obvious, a stolen one traceable. |
reject_approvals | boolean | When set, runs that hit an approval node fail rather than hanging waiting for a human who isn't watching. |
webhook_secret | text | Used to sign callbacks — see below. |
callback_url | url | Default callback for this key; can be overridden per request. |
Which version your key runs
The canvas edits a draft. API keys and schedules run the published snapshot, so saving a half-finished edit cannot change what your integration receives. Creating a swarm's first key or schedule publishes the current graph automatically — a new deployment is never pointed at nothing.
After that, rolling out a change is deliberate: edit and save on the canvas, then press Publish in the Deploy dialog. Until you do, the dialog shows Draft ahead and deployed callers keep getting the previous version. Publishing pins whatever is saved, so save before you publish — the dialog says so if the canvas has unsaved edits.
| State | What deployed runs execute |
|---|---|
| Published | The pinned snapshot, which currently matches the canvas. |
| Draft ahead | The pinned snapshot. Your canvas changes are NOT live until you publish. |
| Serving the live canvas | The draft itself — every save is immediately live. Only happens on swarms deployed before publishing existed, or if you press Unpin. |
Unpin is available if you want the old behaviour, where saves reach production immediately. Sub-swarms follow the same rule: an Execute Swarm node inside a headless run executes the child's published graph, not its draft.
POST /api/swarm/run
Headers
| Header | Required | Notes |
|---|---|---|
Authorization: Bearer sk_swarm_… | Yes | — |
Content-Type: application/json | Yes | — |
Idempotency-Key | No | Client-chosen. Makes a retry return the original result instead of re-running. |
Request body
| Field | Type | Purpose |
|---|---|---|
input | string | The free-text input for the swarm's input node. |
inputs | object | For a swarm whose input node declares a typed form: one key per field name. |
history | {role, content}[] | Prior turns (user / assistant). This is what turns a swarm into a multi-turn chatbot. |
async | boolean | true returns immediately and delivers the result to the callback. |
callback_url | url | Overrides the key's default callback for this run. |
Synchronous run
curl -X POST https://your-instance.example.com/api/swarm/run \
-H "Authorization: Bearer $AGENTSWARMS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-48213-summary" \
-d '{
"input": "Summarise ticket 48213 and suggest a reply",
"inputs": { "priority": "high" }
}'{
"output": "The customer reports a faulty hinge …",
"runId": "5f1c0f0e-…"
}Asynchronous run
curl -X POST https://your-instance.example.com/api/swarm/run \
-H "Authorization: Bearer $AGENTSWARMS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "Produce the weekly summary",
"async": true,
"callback_url": "https://api.example.com/hooks/agentswarms"
}'{
"accepted": true,
"callback_url": "https://api.example.com/hooks/agentswarms"
}Use async for anything that takes longer than a request should — a long graph, an approval gate, a large document.
Status codes
| Code | Meaning | What to do |
|---|---|---|
| 200 | Run finished (sync) | Read output and runId. |
| 202 | Accepted (async) | Wait for the callback. |
| 401 | Missing, invalid, disabled or expired key | Check the key; do not retry blindly. |
| 402 | Budget cap exceeded | Spend ceiling hit — see Budgets. |
| 404 | Swarm not found | The key's swarm was deleted. |
| 409 | A run with this Idempotency-Key is still in progress | Back off and retry; do not start a second run. |
| 422 | Idempotency-Key reused with a different body | A client bug — either reuse the exact body or use a new key. |
| 429 | Rate limit exceeded | Slow down and retry. |
Idempotency
Send an Idempotency-Key and a retry with the same key returns the original result instead of running the swarm again.
Why it works this way
order-48213-summary), never from a random value per attempt, or every retry is a fresh run.| Situation | Result |
|---|---|
| Same key, same body, first call finished | 200 with the original stored response |
| Same key, same body, first call still running | 409 — do not start a second run |
| Same key, DIFFERENT body | 422 — rejected loudly rather than returning a mismatched result |
Idempotency records are kept for a bounded window and then purged.
Limits
| Limit | Purpose |
|---|---|
| Rate limit | Requests per key per interval → 429. |
| Concurrency | Simultaneous runs per key — the one protecting your provider quota. |
| Run timeout | Wall-clock ceiling, so a looping graph cannot run forever. |
| Budget cap | Spend ceiling per key → 402. See Budgets. |
These limits hold across every instance
Webhook callbacks
Each delivery carries these headers:
| Header | Value |
|---|---|
X-AgentSwarms-Event | swarm.run.completed |
X-AgentSwarms-Timestamp | Unix seconds, included in the signed material |
X-AgentSwarms-Signature | sha256=<hex> — HMAC-SHA256 over <timestamp>.<body> using the key's webhook secret |
Delivery is retried up to 3 times, with a 15-second timeout per attempt.
Verifying the signature
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody, headers, secret) {
const ts = headers["x-agentswarms-timestamp"];
const sig = headers["x-agentswarms-signature"]; // "sha256=<hex>"
// Reject old timestamps FIRST — this is what stops a captured
// delivery being replayed at you weeks later.
const age = Math.abs(Date.now() / 1000 - Number(ts));
if (!Number.isFinite(age) || age > 300) return false;
const expected =
"sha256=" + createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(sig ?? "");
// Constant-time: a plain === leaks the answer through timing.
return a.length === b.length && timingSafeEqual(a, b);
}- Sign over the raw body, before any JSON parsing — re-serialising changes the bytes and the signature will not match.
- Respond 2xx quickly and do the work asynchronously; slow endpoints get retried.
- Make your handler idempotent — a retry may deliver the same result twice.
Outbound safety
Callback URLs are checked before delivery: private, loopback and link-local addresses — including cloud metadata endpoints — are refused. The same guard covers every outbound request the platform makes on your behalf, including web_browse, swarm HTTP nodes and MCP endpoints, so a URL chosen by a model cannot be used to reach inside your network.
Runs that hit an approval node
A swarm with an approval node will wait for a human. For an unattended integration that is a hang, so set reject_approvals on the key and such runs fail fast instead. Approvals fail closed either way — a run never proceeds past a gate on a timeout.
Seeing what happened
Every API-triggered run produces a full trace attributed to the key that started it, visible in Traces, with cost attributed in Analytics. When an integration misbehaves, start there rather than in your own logs — the trace shows the resolved prompt and every tool call.
Calling a notebook instead of a swarm
Notebooks in the Developer workspace can be published the same way: click Publish on a notebook to mint an nbk_… key, then POST /api/notebook/run with {"inputs": {…}}. The request body reaches the notebook's entrypoint function and its return value comes back as the response; long runs hand back a runId to poll at /api/notebook/run/status. Use it when the logic is Python that already works in a notebook and does not need to become a swarm first.