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.

PropertyValuesNotes
scopesrun, read_runsDefaults to ['run']. A database constraint rejects any other value, so a typo fails at write time rather than silently granting nothing.
expires_attimestamp / nullOptional expiry. Set one on keys for a specific integration.
revoked_attimestamp / nullRevocation is immediate — the next request fails closed.
rotated_fromkey idSet on a replacement key, so you can see what superseded what.
last_used_iptextSource IP of the last request — an unused key is obvious, a stolen one traceable.
reject_approvalsbooleanWhen set, runs that hit an approval node fail rather than hanging waiting for a human who isn't watching.
webhook_secrettextUsed to sign callbacks — see below.
callback_urlurlDefault 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.

StateWhat deployed runs execute
PublishedThe pinned snapshot, which currently matches the canvas.
Draft aheadThe pinned snapshot. Your canvas changes are NOT live until you publish.
Serving the live canvasThe 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

HeaderRequiredNotes
Authorization: Bearer sk_swarm_…Yes
Content-Type: application/jsonYes
Idempotency-KeyNoClient-chosen. Makes a retry return the original result instead of re-running.

Request body

FieldTypePurpose
inputstringThe free-text input for the swarm's input node.
inputsobjectFor 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.
asyncbooleantrue returns immediately and delivers the result to the callback.
callback_urlurlOverrides the key's default callback for this run.

Synchronous run

bash
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" }
      }'
200 response
{
  "output": "The customer reports a faulty hinge …",
  "runId": "5f1c0f0e-…"
}

Asynchronous run

bash
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"
      }'
202 response
{
  "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

CodeMeaningWhat to do
200Run finished (sync)Read output and runId.
202Accepted (async)Wait for the callback.
401Missing, invalid, disabled or expired keyCheck the key; do not retry blindly.
402Budget cap exceededSpend ceiling hit — see Budgets.
404Swarm not foundThe key's swarm was deleted.
409A run with this Idempotency-Key is still in progressBack off and retry; do not start a second run.
422Idempotency-Key reused with a different bodyA client bug — either reuse the exact body or use a new key.
429Rate limit exceededSlow 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

Networks fail after the server has already done the work. Without idempotency, your retry logic plus the platform's willingness to run means one customer event can trigger three model runs — three times the cost and three conflicting outputs. Derive the key from the thing you are processing (order-48213-summary), never from a random value per attempt, or every retry is a fresh run.
SituationResult
Same key, same body, first call finished200 with the original stored response
Same key, same body, first call still running409 — do not start a second run
Same key, DIFFERENT body422 — rejected loudly rather than returning a mismatched result

Idempotency records are kept for a bounded window and then purged.

Limits

LimitPurpose
Rate limitRequests per key per interval → 429.
ConcurrencySimultaneous runs per key — the one protecting your provider quota.
Run timeoutWall-clock ceiling, so a looping graph cannot run forever.
Budget capSpend ceiling per key → 402. See Budgets.

These limits hold across every instance

Rate limits and concurrency slots are counted in Postgres, not in each app process, so the number configured is the number enforced however many instances sit behind your load balancer. If the database is briefly unreachable an instance falls back to counting locally and logs that it has — the limit weakens for that moment rather than disappearing.

Webhook callbacks

Each delivery carries these headers:

HeaderValue
X-AgentSwarms-Eventswarm.run.completed
X-AgentSwarms-TimestampUnix seconds, included in the signed material
X-AgentSwarms-Signaturesha256=<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

javascript
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.