CLI — restura-cli
restura-cli runs your collections headlessly with the same protocol implementations as the desktop and web apps. Drop it into CI to assert your API, get JUnit XML for your dashboard, ship an HTML report to your team.

How it fits
Section titled “How it fits”flowchart LR CI[CI runner] -->|exec| CLI[restura run] CLI -->|load| COL[(OpenCollection<br/>dir / .yaml)] CLI -->|exec| H[HTTP / GraphQL] CLI -->|exec| G[gRPC Connect] CLI -->|exec| S[SSE] CLI -->|exec| M[MCP] CLI -->|exec| W[WebSocket] H --> R1[tui · live · json · junit · html · stats] G --> R1 S --> R1 M --> R1 W --> R1 R1 -->|exit code| CI classDef cli fill:#7b6ef6,stroke:#4f46e5,color:#fff; classDef proto fill:#6366f122,stroke:#7b6ef6,color:#7b6ef6; class CLI cli; class H,G,S,M,W proto;
Install
Section titled “Install”npm install -g restura-cli# or, no install:npx restura-cli run ./my-collectionRequires Node.js 24+.
Quick start
Section titled “Quick start”Export a collection from the Restura app (File → Export → OpenCollection directory), then:
restura run ./my-collection --reporter junit --reporter-output junit=results.xmlExit code is 0 when every request passed, 1 if any failed, 2 on internal errors (missing collection, bad flags).
Interactive mode
Section titled “Interactive mode”Run restura (or restura run) with no collection in a terminal to launch a guided wizard — pick the collection, reporters, output paths, an optional env file, and whether to allow localhost — then it runs.

restura # bare → wizardrestura run # also → wizard when no collection is givenBy default an interactive run shows the live tui dashboard; piped output and CI fall back to plain line output (live). See Reporters.
Supported collection formats
Section titled “Supported collection formats”The loader auto-detects three layouts:
| Layout | Detected when… |
|---|---|
| OpenCollection directory (preferred) | the target directory contains opencollection.yml (or .yaml) |
| OpenCollection bundled file | the target path ends in .yaml / .yml |
| Legacy file-collection (deprecated) | the target directory contains _collection.yaml |
The legacy format prints a stderr deprecation warning the first time it’s loaded.
Supported protocols
Section titled “Supported protocols”| Protocol | Notes |
|---|---|
| HTTP / REST | Full support |
| GraphQL | Runs as HTTP with body type graphql |
| gRPC | Via Connect protocol (JSON-encoded, no proto compilation needed) |
| SSE | Captures events for --sse-duration ms, or until --sse-events N |
| MCP | Single JSON-RPC POST per request |
| WebSocket | Executor available; not yet wired into the dispatcher |
Command reference
Section titled “Command reference”restura run [collection] [options][collection] accepts a directory (any supported layout) or a bundled .yaml / .yml file. Omit it in a terminal to pick one via the interactive wizard; in CI it is required.
| Flag | Default | Description |
|---|---|---|
--env <file> | JSON or YAML env file. ${VAR} placeholders are expanded from process.env. | |
--reporter <list> | auto | Comma-separated: tui, live, json, junit, html, stats. Defaults to tui in a terminal, live otherwise (piped / CI). |
--output <file> | Shorthand for single file reporter. | |
--reporter-output <kv...> | Per-reporter output: --reporter-output junit=results.xml html=report.html. | |
--bail | false | Stop on first failure. |
--timeout <ms> | 30000 | Per-request timeout. |
--allow-localhost | false | Permit requests to localhost / 127.0.0.1. Off by default (SSRF guard). |
--folder <path> | Only run requests under this folder path (slash-joined). | |
--include <pattern...> | Substring or glob (e.g. users/*). Repeatable. | |
--exclude <pattern...> | Same syntax as --include. Applied after. | |
--data <file> | CSV (with header row) or JSON array. Runs the collection once per row. | |
--max-iterations <n> | Cap iterations when a --data file is large. | |
--retry <n> | 0 | Retry attempts per failing request. |
--retry-on <list> | network,5xx | Triggers: network, 5xx, 4xx, or specific status codes (429,503). |
--sse-duration <ms> | 5000 | How long to keep SSE streams open. |
--sse-events <n> | Stop SSE early after N events. | |
--insecure | false | Skip TLS certificate verification (self-signed / staging). |
--ca <file> | PEM CA bundle to trust (private CA) without disabling verification. | |
--client-cert <file> | PEM client certificate for mutual TLS (mTLS). | |
--client-key <file> | PEM client private key for mutual TLS. | |
--cert-passphrase <value> | Passphrase for an encrypted --client-key. | |
--proxy <url> | HTTP(S) proxy URL. Overrides HTTP_PROXY / HTTPS_PROXY; composes with the TLS flags. |
OWS workflows
Section titled “OWS workflows”restura workflow run <workspace> <workflow-id> [--env ci.env] [--timeout 30000]The workspace must be an OpenCollection directory with opencollection.yml and a complete workflows/<workflow-id>/ artifact set. The command accepts only Restura’s safe OWS profile: do, set, wait, timeout/cancellation, and binding-only HTTP calls to saved requests. It rejects inline endpoints, credentials, non-HTTP calls, and controls without a bounded runtime before dispatching any network request.
Scripts and assertions
Section titled “Scripts and assertions”Pre-request and test scripts run in a sandboxed QuickJS WASM VM — no DOM, no filesystem, no network escape; 10 MB memory cap, 5 s execution timeout. See Scripts for the full Postman API surface.
name: Get usermethod: GETurl: "{{API_BASE}}/users/1"testScript: | pm.test("status is 200", () => pm.response.to.have.status(200)); pm.test("response has name", () => { pm.expect(pm.response.json()).to.have.property("name"); });When a test script runs and defines any pm.test(...) assertion, those drive pass/fail. Otherwise pass/fail falls back to the transport outcome (HTTP 2xx, gRPC OK, etc.).
Variables set inside a script (pm.environment.set('K', 'v')) propagate to subsequent requests in the same run.
Variables
Section titled “Variables”Three layered sources, in order of precedence (later wins):
--envfile- Collection variables (declared in
opencollection.ymlor_collection.yaml) - Iteration row (when
--datais set)
Substitutions use {{NAME}}. Unknown vars are left in place so the upstream sees them and you notice the gap.
Dynamic helpers
Section titled “Dynamic helpers”Postman-compatible {{$random*}} / {{$timestamp}} helpers are expanded after user var substitution:
| Helper | Example |
|---|---|
{{$randomUUID}} | f4d2e3... |
{{$timestamp}} | 1700000000 |
{{$isoTimestamp}} | 2026-05-22T13:42:00Z |
{{$randomEmail}} | alice.42@example.com |
{{$randomFirstName}} | Olivia |
{{$randomIP}} | 192.0.2.4 |
Full list in shared/variables/dynamic.ts.
Data-driven runs
Section titled “Data-driven runs”restura run ./users-api --data ./users.csv --reporter junit --reporter-output junit=junit.xmlusername,rolealice,adminbob,viewercharlie,editorEach row exposes username and role as variables, overriding any same-named env or collection variable for that iteration only. Reporter output groups results by iteration index.
Reporters
Section titled “Reporters”The default is chosen automatically: the live tui dashboard when stdout is a terminal (and NO_COLOR is unset), live lines otherwise (piped output / CI).
Live, in-place dashboard with a progress bar and a spinner on the in-flight request. The default in an interactive terminal; on completion it collapses to a clean failure list + summary.
Plain one-line-per-request progress — the default when output is piped or in CI (no ANSI cursor control). Force it with --reporter live if the dashboard misbehaves.
Full RunResult dumped as JSON. Path required (--output or --reporter-output json=...). Pipe into anything.
JUnit XML for CI dashboards (GitLab, Azure DevOps, Jenkins, GitHub Actions test reporters). One <testcase> per request.
Self-contained HTML page with embedded data + summary table. Great for sharing failures.
Latency percentiles (p50/p90/p95/p99) + throughput at the end of the run.
The live reporter — also what CI logs get — prints one line per request:

Combine with a comma: --reporter live,junit --reporter-output junit=results.xml.
Exit codes
Section titled “Exit codes”| Code | Meaning |
|---|---|
0 | Every request passed AND at least one request ran |
1 | One or more requests failed or errored (or no requests matched after filtering) |
2 | Internal error: missing collection, bad reporter name, IO failure, … |
Agent eval
Section titled “Agent eval”restura agent eval <suite-or-bundle.json> runs a versioned Agent Suite or portable Agent Bundle in CI and prints a compact JSON summary. It exits 0 only when every trial and every configured baseline gate passes; evaluated failures, errors, and cancellations exit 1; invalid input or unsupported configuration exits 2.
restura agent eval ./orders.agent-bundle.json --output agent-report.jsonThe CLI is intentionally narrower than the desktop AI Lab: it supports stateless OpenAI Responses and Anthropic Messages with environment credentials, deterministic fixtures, and an optional runtime manifest for selected saved HTTP and MCP tools. It refuses desktop secret handles, arbitrary base URLs, judge graders, and sandbox providers. A runtime manifest can only expose explicitly listed read-only HTTP requests and read-only MCP tools with an allowlist; --allow-localhost remains off by default.
Example: GitHub Actions
Section titled “Example: GitHub Actions”name: API smoke testson: [push, pull_request]jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: '24' } - run: npx restura-cli run ./api-tests --env env.staging.json --reporter live,junit --reporter-output junit=results.xml - uses: dorny/test-reporter@v1 if: always() with: name: API smoke tests path: results.xml reporter: java-junitTroubleshooting
Section titled “Troubleshooting”| Symptom | Likely cause / fix |
|---|---|
No recognised collection layout | Target needs opencollection.yml / .yaml or legacy _collection.yaml. Re-export from the Restura app. |
Invalid URL | URL after {{var}} resolution isn’t a valid absolute URL. Check --env is loaded and names match. |
Localhost URLs are not allowed | Add --allow-localhost for local upstreams. Off by default to prevent SSRF in shared CI. |
gRPC requests return UNKNOWN | Upstream doesn’t speak Connect protocol. Restura’s CLI uses Connect-over-HTTP, not gRPC-over-HTTP/2 binary. |
a secret handle ref is unresolvable in CLI | Your auth uses a desktop-only secret handle. Re-export with inline values for CI use. |
Development
Section titled “Development”# from cli/npm installnpm test # vitestnpm run type-check # tsc --noEmitnpm run build # tsup → dist/The CLI imports from the parent project at compile-time via path aliases (@/, @shared/); cli/tsconfig.json controls which parent modules participate in type-checking.
Related
Section titled “Related”- Scripts — the Postman
pm.*API. - Workflows — request chaining (also runs in CLI).
- OpenCollection — the collection format.
- AI Lab — desktop authoring and execution for agent suites.
- ADR 0005 — CLI runner.