Skip to main content
@runflow-io/sdk wraps the Runflow REST API in a typed client and a small tool DSL. It uses Web Standards fetch, so the same package runs in Node, Bun, Deno, browsers, and edge workers.
All @runflow-io/* packages are pre-1.0 (currently 0.0.3). Patch releases may change types or runtime behavior. Pin exact versions in production until 1.0.

Install

The SDK has zero runtime dependencies. Node 18+ or any environment with a global fetch.

Server-side: dispatch and wait

rf.models.run(model, body) dispatches and returns immediately with { id, status_code, ... }. rf.runs.wait(id) polls until the run finishes and resolves with the final record. onPoll fires on each poll if you want progress updates. Model ids are owner/slug (or owner/slug/sub). A bare slug like "background-removal" returns HTTP 405 from the API directly and HTTP 403 Not allowed through the proxy. The SDK rejects empty model ids and any segment equal to . or .. at the client with code: invalid_model_id, and URL-encodes every other segment, so it is safe to pass user or LLM-controlled model ids as long as you pair them with an allowedModels policy on the proxy. Defaults for runs.wait: timeoutMs: 180_000, pollIntervalMs: 2_000. If the wait times out, the run is still going upstream; you can extend the deadline or use rf.runs.get(err.runId) to check status separately. To stream every poll yourself, use rf.runs.poll(id), which returns an async iterable. It yields each successful response, retries 5xx silently between yields, and throws RunTimeoutError if the run has not finished within timeoutMs.

The Tools DSL

defineTool binds a model id to a typed input schema, an output schema, and a buildRequest function that maps inputs to the model’s body shape. Once defined, the same object can be dispatched from the SDK or rendered by the Studio.

Input sources

Each input has a source that controls where its value comes from: buildRequest receives every input including presets (the AllInputValues type). Callers only supply non-preset inputs (the RuntimeInputValues type), so the destructured style in the example above works even though style is a preset. Optional user inputs use optional: true and become optional in RuntimeInputValues too.

Input and output builders

Inputs: imageInput, textInput, numberInput, colorInput, selectInput, referenceInput, maskInput, pinInput. Outputs: imageOutput, textOutput, numberOutput, jsonOutput, imageListOutput.
The default extractor only fills the image field. If you declare any other output field without supplying extractOutput, that field will be undefined at runtime even though the TypeScript types claim it exists. Always supply extractOutput for non-image-only schemas.

Dispatch without waiting

rf.tools.dispatch(tool, args) returns { runId, model } immediately if you want to manage polling yourself or relay the id to a callback handler.
Note the asymmetry: rf.models.run(...) returns { id, status_code, ... } (the field is id), while rf.tools.dispatch(...) returns { runId, model }. Both ids are interchangeable when passed to rf.runs.get / rf.runs.wait / rf.runs.poll.

Browser, through a proxy

Never put a Runflow API key in a browser bundle. Mount @runflow-io/proxy on your server, point the SDK at it, and the key stays server-side.
When baseUrl is set the SDK omits the Authorization header. The proxy injects it before forwarding upstream. See Proxy browser calls server-side below.

Configuration

Exactly one of apiKey or baseUrl is required. If both are set, baseUrl wins and the bearer header is omitted. API keys are alphanumeric plus underscore (current format: rf_live_* for inference, rf_svc_* for admin). The constructor rejects keys with hyphens, dots, or other punctuation with code: invalid_api_key. Strip whitespace before passing.

Errors

The SDK throws three error classes, all extending RunflowError: Common RunflowError.status and code values:

Proxy browser calls server-side

@runflow-io/proxy is a Web Standards request handler. It accepts only the run-dispatch and run-poll paths, validates the model against an allowlist, and forwards to api.runflow.io with your key injected.
Always add an authenticate hook in production. The CSRF defaults block browser drive-by attacks but not direct server-to-server callers: anyone with your proxy URL can hit it from curl, a script, or another server and spend your Runflow budget against any allowed model. The hook is shown under Auth, rate limit, telemetry hooks below.

Next.js App Router

Hono, Cloudflare Workers, SvelteKit, Bun, Deno

Express, Fastify, classic Node (req, res)

Auth, rate limit, telemetry hooks

The proxy ships safe defaults (model allowlist, 32 KB body cap, 30 s upstream timeout, masked upstream errors). Layer your own auth, rate limit, and observability with hooks:

Path contract

The proxy accepts only these paths: Everything else returns 403 Not allowed. Run IDs are validated as UUIDv4-shape to block path traversal.

CSRF defaults

Non-GET requests are checked against the proxy’s allowedOrigins policy. The default "same-origin" accepts only requests whose Origin host matches the request Host header. Pass an array of full origins (["https://example.com"]) to allow specific third-party callers.
Two carveouts to know about:
  1. No Origin header = pass. Server-to-server callers (curl, scripts, other backends) do not send Origin, and the proxy lets them through this gate by design. The authenticate hook is the only thing that can block them.
  2. allowedOrigins: false is dangerous. If your authenticate hook reads cookies or session, disabling the origin check lets any third-party site trigger paid runs in a logged-in user’s name. Prefer adding the third-party origin to the array.
By default the proxy also requires Content-Type: application/json on non-GET requests so a malicious page cannot drain credentials via a text/plain CORS simple request. Set requireJsonContentType: false only if you proxy non-JSON workloads.

Embed the Studio

Drop the Studio UI on your site.

Authentication

Bearer header, key rotation.

Runs

Lifecycle and statuses.

Errors

Status codes and the error envelope.