REST API

REST API reference

Start runs, read workflows and results, and manage outbound webhooks from your own systems.

https://flow.fluxosuite.com/api/v1 OpenAPI 3.1 (JSON)Version v1 · 1.0.0

Overview

The Fluxo Flow REST API lives at https://flow.fluxosuite.com/api/v1. It speaks JSON over HTTPS. With it you can list and read workflows, start a run with an input payload (and optionally wait for the result), list runs and read each step's status and output, cancel queued or paused runs, and manage outbound webhook endpoints. Building and editing workflows still happens in the app.

  • Machine-readable spec: OpenAPI 3.1 at https://flow.fluxosuite.com/api/v1/openapi.json (public, no key needed) — import it into Postman, Insomnia or a code generator.
  • Every response carries an X-Request-Id header. Quote it when you contact support.
  • Ids are UUIDs. Times are ISO 8601 in UTC. Fields are camelCase. New fields can appear at any time — ignore ones you don't know.

Authentication and scopes

Workspace owners and admins create API keys in Settings → API & webhooks. A key looks like fxk_live_<key id>_<secret>; it is shown once, and we keep only a salted, peppered hash. The fxk_live_<key id> part is the key's public prefix, shown in the key list so you can tell keys apart. Send the key on every request as Authorization: Bearer fxk_live_… — from your server only. Requests from web browsers are refused (no CORS), and your Fluxo Flow sign-in does not work on /api/v1, just as API keys don't work anywhere else in the app.

ScopeAllows
workflows:readRead workflows. List workflows and read their settings (never secrets).
runs:readRead runs. List runs and read their status, step results and output.
runs:writeStart and cancel runs. Start a workflow run with an input payload, and cancel queued or paused runs.
webhooks:readRead webhooks. List outbound webhook endpoints and their delivery log.
webhooks:writeManage webhooks. Add and remove outbound webhook endpoints.
  • New keys get workflows:read and runs:read unless you tick more. runs:write includes runs:read, and webhooks:write includes webhooks:read.
  • Optional expiry: 30 days, 90 days, 1 year or a date you pick. Expired and revoked keys stop working on their next request (401).
  • The key list shows who created each key, when it was last used and from which country.
  • Keys created before the API launched (fluxo_…) keep working with read-only scopes. Create a new key for write access.
  • Every key belongs to one workspace. Objects in other workspaces always answer 404, never 403, so their existence isn't revealed.
  • If the workspace is suspended, or its owner's account is blocked, its keys get 403.
  • Key creation, revocation, rejected requests and (at most hourly) key use are recorded in the workspace activity log; runs started by a key show the key's name.

Quick start

bash · Check your key
curl https://flow.fluxosuite.com/api/v1/me \
  -H "Authorization: Bearer $FLUXO_API_KEY"
bash · Start a run and wait up to 25 s for the result
curl -X POST https://flow.fluxosuite.com/api/v1/workflows/WORKFLOW_ID/runs \
  -H "Authorization: Bearer $FLUXO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"input": {"customerEmail": "ana@example.com", "amount": 1999}, "wait": true}'
javascript · Node.js 18+: start a run, then poll until it finishes
const base = "https://flow.fluxosuite.com/api/v1";
const headers = { Authorization: `Bearer ${process.env.FLUXO_API_KEY}`, "Content-Type": "application/json" };

let run = await fetch(`${base}/workflows/${workflowId}/runs`, {
  method: "POST",
  headers: { ...headers, "Idempotency-Key": crypto.randomUUID() },
  body: JSON.stringify({ input: { customerEmail: "ana@example.com" } }),
}).then((r) => r.json());

while (["queued", "running"].includes(run.status)) {
  await new Promise((r) => setTimeout(r, 2000));
  run = await fetch(`${base}/runs/${run.id}`, { headers }).then((r) => r.json());
}
console.log(run.status, run.output);
python · Python: start a run and wait for it
import os, uuid, requests

base = "https://flow.fluxosuite.com/api/v1"
headers = {"Authorization": f"Bearer {os.environ['FLUXO_API_KEY']}"}

run = requests.post(
    f"{base}/workflows/{workflow_id}/runs",
    headers={**headers, "Idempotency-Key": str(uuid.uuid4())},
    json={"input": {"customerEmail": "ana@example.com"}, "wait": True},
    timeout=40,
).json()
print(run["status"], run.get("output"))

Runs: lifecycle, waiting and cancelling

POST /workflows/{id}/runs queues a run on the same queue and runner as webhook runs (its source is "api"). The input object becomes the trigger's payload — the same shape a webhook body would have; GET /workflows/{id} lists the input fields the workflow reads. The response is 202 with the run's id and status "queued"; the run usually starts within seconds.

StatusMeaning
queuedAccepted, waiting for the runner.
runningExecuting now (or resuming after a review decision).
pausedWaiting at a Human review step (the run detail's review object says which step; it continues after someone decides in the Review Queue) or at a Wait step (the wait object gives the step and resumeAt; it continues on its own).
succeededFinished. output holds the last successful step's output.
failedA step failed, the run hit the server time limit, or it couldn't start. error.message says why.
cancelledCancelled through the API before it ran, or while paused for review.
  • wait: true holds the request open for up to 25 seconds. If the run finishes in time you get 200 with the full run (steps included); otherwise 202 and the run keeps going — poll GET /runs/{id} or subscribe to run.* webhooks.
  • metadata: up to 20 key/value tags (for example your order id) stored on the run and returned with it. They are not passed to the workflow.
  • POST /runs/{id}/cancel cancels queued runs, runs paused for review (the review item is withdrawn) and runs waiting at a Wait step (they never resume). Steps that are executing can't be interrupted yet, so a running run answers 409.
  • Run details include each step's status, duration, output, error, and token usage and cost when recorded. Outputs pass through the same secret scrubber as the workspace export and are capped at 16 KB per step.
  • API runs count toward your plan like any other run, and each plan limits how many runs can be queued or running at once (429 concurrency_limit_exceeded above it).

Rate limits

PlanPer keyPer workspace (all keys)Runs queued or running at once
Free60/min120/min2
Basic120/min240/min5
Pro600/min1,200/min20
Business900/min1,800/min35
Elite1,200/min2,400/min50
Enterprise3,000/min (custom)6,000/min100
  • Every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds until the window resets).
  • Above a limit you get 429 with a Retry-After header (seconds). Back off and retry after it.
  • Too many requests with invalid keys from one IP address are blocked for a while (429 too_many_auth_failures), even if a later key is valid.
  • Enterprise limits are set per contract.

Pagination and filters

Lists return { object: "list", data, hasMore, nextCursor }, newest first. Pass limit (1–100, default 25) and, for the next page, cursor=<nextCursor>. Cursors are opaque; don't build them yourself. GET /runs filters by workflowId, status and since (an ISO 8601 date-time with an offset, e.g. 2026-10-01T00:00:00Z or 2026-10-01T09:00:00+02:00; a time without a zone is rejected). GET /workflows filters by q (name contains).

bash · Failed runs of one workflow since October 1
curl "https://flow.fluxosuite.com/api/v1/runs?workflowId=WORKFLOW_ID&status=failed&since=2026-10-01T00:00:00Z&limit=50" \
  -H "Authorization: Bearer $FLUXO_API_KEY"

Idempotency

Send an Idempotency-Key header (any unique string up to 255 characters; a UUID is ideal) when starting a run. For 24 hours, a retry with the same key and the same body returns the original run instead of starting another one (with the header Idempotent-Replayed: true). Reusing a key with a different body returns 409 idempotency_conflict; retrying while the first request is still being processed returns 409 idempotency_in_progress — wait a second and retry. Keys are shared across all API keys of the workspace.

Errors

Errors use one shape: { "error": { "code", "message", "requestId", "docs" } } (validation errors add details.fields). Branch on code, show message to people, and log requestId. 401 means the key is missing, wrong, expired or revoked; 403 means it lacks the scope, the workspace is suspended, or the request came from a browser; 404 covers objects that don't exist or belong to another workspace; 409 is an idempotency conflict or a run that can't be cancelled; 413 is a body over 1 MB; 415 is a body that isn't application/json; 422 is a field that fails validation; 429 is a rate or concurrency limit; 5xx is our side — retry with backoff.

CodeHTTPMeaning and what to do
authentication_required401Send your API key in the Authorization header: `Authorization: Bearer fxk_live_…`.
invalid_api_key401This API key isn't valid. Check you copied the whole key, or create a new one in Settings → API & webhooks.
api_key_expired401This API key has expired. Create a new key in Settings → API & webhooks.
api_key_revoked401This API key was revoked. Create a new key in Settings → API & webhooks.
insufficient_scope403This API key doesn't have the scope this endpoint needs.
workspace_suspended403This workspace is suspended, so its API keys can't be used. Contact support.
account_blocked403The workspace owner's account isn't eligible to use the API. Contact support.
browser_requests_not_allowed403API keys can't be used from a web browser. Call the API from your server instead.
not_found404Not found.
method_not_allowed405This endpoint doesn't support that HTTP method.
idempotency_conflict409This Idempotency-Key was already used with a different request body. Use a new key for a different request.
idempotency_in_progress409A request with this Idempotency-Key is still being processed. Retry in a moment.
run_not_cancellable409This run can't be cancelled in its current state.
conflict409The request conflicts with the current state of the resource.
payload_too_large413The request body is larger than 1 MB.
unsupported_media_type415Send the request body as JSON with `Content-Type: application/json`.
invalid_json400The request body isn't valid JSON.
validation_failed422Some fields are invalid.
rate_limited429Too many requests. Slow down and retry after the time in the Retry-After header.
upgrade_required_live_runs402Upgrade to run workflows live. The Free plan can build workflows and test them with Dry Run in the app; runs through the API need a paid plan.
concurrency_limit_exceeded429Too many runs are queued or running for this workspace. Wait for some to finish, then retry.
too_many_auth_failures429Too many requests with invalid API keys from this address. Wait before trying again.
internal_error500Something went wrong on our side. Retry, and contact support with the request id if it keeps happening.
service_unavailable503The API isn't available right now. Retry in a moment.

Outbound webhooks

Instead of polling, add an endpoint in Settings → API & webhooks (or POST /webhooks) and we'll POST a signed JSON event to it. Endpoints must be https and publicly reachable; private, loopback and link-local addresses are refused when you add them and again before every delivery, and redirects are not followed.

EventWhen
run.succeededA workflow run finished successfully.
run.failedA workflow run failed or timed out.
run.pausedA run reached a Human review step and is waiting for a decision.
review.decidedSomeone approved or rejected a review step (or it timed out).
workflow.updatedA workflow's steps or settings were saved.
credential.rotatedA credential in the vault was saved.
credential.revokedA credential was removed from the vault.
plan.changedThe workspace's subscription plan changed.
json · Example body (run.failed)
{
  "id": "evt_3f1c9a2b7d4e5f60718293ab",
  "type": "run.failed",
  "event": "run.failed",
  "api_version": "v1",
  "created_at": "2026-10-02T09:30:59.800Z",
  "workspace_id": "0b6e2c1a-9d3f-4e7b-8a2c-1f0e9d8c7b6a",
  "attempt": 1,
  "data": {
    "run": {
      "id": "8f14e45f-ceea-4e6b-9a1c-5f0b4f6c2a10",
      "workflowId": "3b9d6c1e-2f4a-4c55-8e0a-6a7b8c9d0e1f",
      "workflowName": "Invoice approval",
      "status": "failed",
      "source": "api",
      "durationMs": 1840,
      "costUsd": 0.0012,
      "error": "Slack: channel_not_found"
    }
  }
}
  • Headers: Fluxo-Signature, Fluxo-Event, Fluxo-Event-Id and Fluxo-Delivery. X-Fluxo-Signature and X-Fluxo-Event carry the same values for receivers built against the earlier names; prefer the new ones.
  • Respond with any 2xx within 10 seconds. Anything else is retried after 1 min, 5 min, 30 min, 2 h, 6 h and 15 h (7 attempts over about 24 hours), then marked as given up.
  • Retries reuse the event id — de-duplicate on it. Events can arrive out of order; use created_at and the run status.
  • After 20 failed deliveries in a row the endpoint is turned off automatically; owners and admins get an email and a notification, and the activity log records it. Turn it back on from the endpoint's Edit dialog once it's fixed.
  • Send test event delivers a signed webhook.test event right away and shows the response. The delivery log (and GET /webhooks/{id}/deliveries) shows every attempt for 30 days: status code, duration, attempts and the first 1 KB of the response with secrets removed.
  • Rotate secret issues a new signing secret. For 24 hours deliveries carry two v1 signatures (new and old) so you can switch your receiver without missing events.

Verifying webhook signatures

Fluxo-Signature looks like t=1790000000,v1=5257a8…. Compute HMAC-SHA256 with your endpoint's signing secret over the string "<t>.<raw request body>", hex-encode it, and compare it in constant time with each v1 value (there are two during a rotation). Reject timestamps more than 5 minutes old to block replays. Always use the raw body bytes — re-serialized JSON won't match.

javascript · Node.js
import crypto from "node:crypto";

// Express: app.post("/fluxo", express.raw({ type: "application/json" }), handler)
export function verifyFluxoSignature(rawBody, header, secret, toleranceSec = 300) {
  const parts = header.split(",").map((p) => p.split("="));
  const t = parts.find(([k]) => k === "t")?.[1];
  const sigs = parts.filter(([k]) => k === "v1").map(([, v]) => v);
  if (!t || sigs.length === 0) return false;
  if (Math.abs(Date.now() / 1000 - Number(t)) > toleranceSec) return false; // replay guard
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return sigs.some((s) =>
    s.length === expected.length && crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected))
  );
}

// In your handler (rawBody is the exact bytes received, as a string):
// if (!verifyFluxoSignature(rawBody, req.get("Fluxo-Signature"), process.env.FLUXO_WEBHOOK_SECRET)) return res.sendStatus(400);
// const event = JSON.parse(rawBody); // de-duplicate on event.id
python · Python
import hashlib, hmac, time

def verify_fluxo_signature(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    items = [p.split("=", 1) for p in header.split(",") if "=" in p]
    t = next((v for k, v in items if k == "t"), None)
    sigs = [v for k, v in items if k == "v1"]
    if not t or not sigs:
        return False
    if abs(time.time() - int(t)) > tolerance:  # replay guard
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(expected, s) for s in sigs)

# Flask:
# if not verify_fluxo_signature(request.get_data(), request.headers["Fluxo-Signature"], os.environ["FLUXO_WEBHOOK_SECRET"]):
#     abort(400)

Versioning and deprecation

  • The version is in the path: /api/v1. Breaking changes only ever ship as a new version (/api/v2).
  • Within v1 we may add endpoints, optional parameters, response fields, enum values and webhook events without notice. Build clients that ignore unknown fields.
  • If a v1 endpoint is deprecated, we announce it in the changelog and keep it working for at least 12 months. Its responses carry Deprecation and Sunset headers in the meantime.

Deprecation and Sunset headers

  • Deprecation: @<unix seconds> (RFC 9745) — the moment the endpoint (or the whole API version) was deprecated.
  • Sunset: <HTTP date> (RFC 8594) — when it stops working. Absent until a date is set.
  • Link: <https://flow.fluxosuite.com/docs/api#deprecations>; rel="deprecation" — where to read about the change (for a whole version: #version-<version>).
  • The headers are added to every response of an affected endpoint, successful or not. Log them in your client so you notice before the sunset date; the changelog announces each deprecation too.

Building a third-party app (OAuth)

  • Register your app in the developer portal (/developer-portal): name, redirect URIs and the scopes it needs. You get a client id and a client secret (shown once). Apps are reviewed before other workspaces can install them.
  • Send the user to /oauth/authorize?response_type=code&client_id=…&redirect_uri=…&scope=…&state=…&code_challenge=…&code_challenge_method=S256 (PKCE with S256 is required). An owner or admin picks the workspace and approves.
  • Exchange the code: POST /api/oauth/token with grant_type=authorization_code, code, redirect_uri and code_verifier, authenticating with your client id + secret (HTTP Basic or form fields). The access token works like an API key on /api/v1 with the approved scopes. It expires after an hour (expires_in: 3600); the response also carries a refresh_token.
  • Renew: POST /api/oauth/token with grant_type=refresh_token and refresh_token (optionally a narrower scope). Each refresh token works once: store the new refresh_token from every response. Presenting a spent refresh token again revokes the connection (we treat it as stolen). Refresh tokens last 90 days from their last use.
  • Disconnect: POST /api/oauth/revoke with token=<access token or refresh token>. Workspace admins can also revoke the app in Settings → API & webhooks; both show up in the workspace activity log.

Security checklist

  • Keep keys in a secret manager or server environment variables — never in browser code, mobile apps or source control.
  • Give each system its own key with only the scopes it needs, and set an expiry.
  • Revoke a key the moment you suspect it leaked (Settings → API & webhooks, or Danger Zone → Revoke all API keys).
  • Verify every webhook signature and timestamp before trusting the body.
  • Run outputs can contain your business data; treat API responses like the data in the app.

Other ways to integrate

  • Inbound without an API key: give a workflow a Webhook trigger, activate it, and POST JSON to its URL with the secret header (see Webhooks & triggers).
  • Outbound from inside a workflow: add an HTTP Request, Webhook Call or Publish Output step to send data anywhere at any point.

Endpoints

Paths are relative to https://flow.fluxosuite.com/api/v1. Each endpoint lists the scope its key needs.

Account

Workflows

Runs

Webhooks

Last updated . New to Fluxo Flow? See what it does and how pricing works.