CLI runner (relay run)
Run a Relay YAML workspace's requests and test scripts from the terminal or CI, with pretty, JSON, or JUnit output.
relay run executes a Git-backed YAML workspace from the command line — the same requests and JavaScript test scripts you run in the app, without the window. It’s built for CI: a non-zero exit code fails the build when a request errors or an assertion fails.
The desktop binary is the CLI. There is nothing extra to install — the app you already have responds to relay run.
Quick start
relay run ./my-workspace --env Staging✓ GET https://api.example.com/health → 200 42ms [2/2 tests]✓ POST https://api.example.com/login → 200 88ms [1/1 tests]
2 requests, 2 passed, 0 failed · 3/3 assertions · 131msThe first argument is the workspace directory (the folder that contains relay.yml); it defaults to the current directory, so inside a workspace you can just run relay run --env Staging.
What it runs
- HTTP and GraphQL requests, in the order they load from the workspace.
- Pre-request and test scripts — the same sandboxed JavaScript
pm.*API as the app. Assertions become the pass/fail signal. - Collection defaults — a collection’s auth, headers, scripts, and settings are applied exactly as they are in the app, so a request set to Inherit auth authenticates in CI too.
- Variable chaining: a value a test writes with
pm.environment.set(...)is visible to later requests in the same run, so a login step can hand a token to the requests after it.
Realtime request types (WebSocket, SSE, Socket.IO, gRPC) need a live session and are skipped. A request whose pre-request script calls pm.execution.skipRequest() is also skipped — reported as such, and it does not fail the run.
Scripts get the same sandbox as the app, including pm.crypto and CryptoJS for request signing. Two capabilities are opt-in because they change what a run can do:
pm.sendRequestneeds--allow-send-request. Without it, a script that calls it fails, which keeps a CI run from making unannounced HTTP calls.- Scripts are capped at 2000 ms; raise it with
--script-timeoutwhen a heavy assertion suite or signing step needs longer.
OAuth 2.0 in CI
A run gets its own access token rather than relying on one the app saved. The saved token lives in the machine-local secret store and never reaches a checkout, so relying on it would mean an OAuth-protected collection simply could not run in CI.
Which grants a run can complete on its own:
| Grant | In a run |
|---|---|
| Client Credentials | Fetched from the token endpoint. |
| Password | Fetched from the token endpoint with the configured credentials. |
| Authorization Code / Device Code | Both need a person at a browser. A run swaps the stored refresh token instead, which is how these grants are meant to be renewed unattended. |
All the requests in a run that share one configuration authenticate once — fifty requests do not mean fifty round trips to the token endpoint.
When a grant needs a browser and there is no refresh token to fall back on, the run stops on that request and says so, rather than sending it unauthenticated and reporting a 401 you then have to diagnose.
Keep the client secret out of the workspace and pass it in:
relay run . --env CI --var oauthClientSecret="$OAUTH_CLIENT_SECRET"Variables
Variables resolve exactly as they do in the app, in ascending priority:
- Globals (
--globals <file>,--global-var KEY=VALUE). - Collection variables (collection defaults).
- The selected environment, chosen with
--env <name>. --env-file— aKEY=VALUEfile.--var KEY=VALUE— repeatable, highest priority.
The full set of dynamic variables ({{$guid}}, {{$timestamp}}, {{$randomEmail}}, and the rest — see Environments & variables) is generated per use, identical to the desktop app.
Secrets are the reason --var and --env-file exist: keep them out of the committed workspace and inject them from the CI environment.
relay run . --env CI --var authToken="$API_TOKEN" --env-file .ci.envData-driven runs
Point --data at a CSV or JSON file to run the selected requests once per row. Each row’s columns become variables for that iteration, and the row is also readable in scripts as pm.iterationData.get("column") — the same as Postman/Newman.
relay run . --env CI --data users.csvusers.csv─────────name,roleada,admingrace,engineerA request URL like {{baseUrl}}/users?name={{name}} runs three times (one per row), each with its own name. Values a test writes with pm.environment.set still carry forward between requests; the data row is a read-only overlay on top. --iterations is ignored when --data is set — the row count is the iteration count.
JSON data files accept an array of objects, or an object wrapping the rows under data/rows.
Reporters
Run one or more reporters with --reporters (comma-separated). Each can go to stdout or a file:
| Reporter | Use | File flag |
|---|---|---|
cli (default) | Human-readable lines, a summary, and a failure list, for the terminal. | — (stdout) |
json | A machine-readable summary and per-request results. | --reporter-json-export <file> |
junit | A JUnit XML testsuite, for CI test-report UIs. | --reporter-junit-export <file> |
# CLI summary on screen, JSON and JUnit written to filesrelay run . --env CI --reporters cli,json,junit \ --reporter-json-export report.json \ --reporter-junit-export report.xml--reporter <one> is a shorthand for a single reporter. Naming an export file implies its reporter, so --reporter-junit-export report.xml alone is enough.
Exporting variables
Write the final variable state (including whatever tests set during the run) to a Postman-compatible file:
relay run . --env CI --export-environment final-env.json --export-globals final-globals.jsonThe exported file reads back through --env-file or --globals, and imports into Postman.
Selecting what to run
relay run . --env CI --collection "Billing" # one collection, by namerelay run . --env CI --folder "Billing/Refunds" # a folder subtreeAll flags
| Flag | Meaning |
|---|---|
--workspace <dir> | Workspace directory (or pass it as the first positional argument). |
--env <name> | Environment to resolve variables from. |
--collection <name> | Only run requests in this collection. |
--folder <a/b> | Only run requests under this folder path. |
--data <file> | CSV or JSON data file; each row is one iteration. |
--var KEY=VALUE | Override a variable (repeatable). |
--env-file <path> | KEY=VALUE file that overrides environment variables. |
--globals <file> | KEY=VALUE or JSON file of global variables. |
--global-var KEY=VALUE | Set a global variable (repeatable). |
--reporters <list> | Comma-separated: cli, json, junit. |
--reporter <fmt> | Shorthand for a single reporter. |
--reporter-json-export <file> | Write the JSON report to a file. |
--reporter-junit-export <file> | Write the JUnit report to a file. |
--export-environment <file> | Write final environment variables after the run. |
--export-globals <file> | Write final global variables after the run. |
--timeout <ms> | Per-request timeout, overriding request settings. |
--script-timeout <ms> | Per-script execution timeout (default 2000, max 60000). |
--allow-send-request | Allow pm.sendRequest to make HTTP calls from scripts. |
--delay <ms> | Delay between requests. |
--iterations <n> | Run the selected set n times (ignored with --data). |
--fail-fast, --bail | Stop at the first failing request. |
--insecure, -k | Disable TLS certificate verification for every request. |
--verbose | Print request/response detail for each request. |
Exit codes
| Code | Meaning |
|---|---|
0 | Every request succeeded and every assertion passed. |
1 | At least one request errored or one assertion failed. |
2 | Setup problem — workspace not found, unknown environment, or no requests matched. |
Example: GitHub Actions
- name: API smoke tests run: relay run ./workspace --env CI --reporter junit --var token="${{ secrets.API_TOKEN }}" > results.xmlRelated
- Collection Runner — the same idea inside the app, with a data file and parallelism.
- Git-backed workspaces — the YAML format
relay runreads. - Scripting API — the
pm.*API your test scripts use.
