Documentation Index
Fetch the complete documentation index at: https://docs.runflow.io/llms.txt
Use this file to discover all available pages before exploring further.
@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.
Install
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 asource that controls where its value comes from:
| Source | Collected from caller? | Passed to buildRequest? |
|---|---|---|
preset | No, baked into the tool’s value field. | Yes. |
runtime | Yes, every call (for example, the source image). | Yes. |
user | Yes, from the Studio UI or programmatically. | Yes. |
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.
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.
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.
baseUrl is set the SDK omits the Authorization header. The proxy injects it before forwarding upstream. See Proxy browser calls server-side below.
Configuration
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 extendingRunflowError:
| Error | When it throws |
|---|---|
RunflowError | HTTP failure, network error, request timeout, bad config, invalid model id, invalid run id, malformed apiKey. |
RunFailedError | A run finished with status_code: failed or canceled. Thrown by runs.wait, runs.poll, and tools.run. Has .run.id, .run.status, .run.error. |
RunTimeoutError | runs.wait / runs.poll did not see a terminal status before timeoutMs. Has .runId, .elapsedMs. |
RunflowError.status and code values:
| Trigger | err.status | err.code |
|---|---|---|
| Missing/invalid API key | 401 | (server-set) |
| Proxy denied (model not in allowlist, origin mismatch, path not allowed) | 403 | (server-set) |
| Payload too large (proxy 32 KB cap by default) | 413 | (server-set) |
| Content-Type not JSON on proxy | 415 | (server-set) |
Rate limit (check Retry-After header) | 429 | (server-set) |
| Upstream timed out via proxy | 504 | (server-set) |
| Per-request timeout (default 30 s) | undefined | request_timeout |
| Network error | undefined | network_error |
Empty model id or ./.. segment | undefined | invalid_model_id |
Run id contains /, ., or .. | undefined | invalid_run_id |
| Malformed apiKey at construction | undefined | invalid_api_key |
| Default extractor found no image URL | undefined | output_parse_error |
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.
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:| Method | Path | Purpose |
|---|---|---|
| POST | /v1/models/{owner}/{slug...}/runs | Dispatch a run. |
| GET | /v1/runs/{uuid} | Poll a run. |
| GET | /v1/health | Public health. |
403 Not allowed. Run IDs are validated as UUIDv4-shape to block path traversal.
CSRF defaults
Non-GET requests are checked against the proxy’sallowedOrigins 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.
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.
Related
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.