Skip to content

fp CLI reference

The fp binary runs Fetchpier workspaces without Electron. It supports interactive terminal output, machine-readable reports, stable exit codes, and a programmatic Node.js API.

A workspace is a folder containing .fetchpier/settings.json. Requests are the .http files under its requests/ folder; environments are the folders under environments/.

CommandDescriptionNeeds a workspace
fp run [paths...]Alias for fp request run.Yes
fp request run [paths...]Run .http files and/or folders. With no path, runs the workspace’s requests/.Yes
fp env listPrint each environment’s folder name, one per line — the names --env accepts.Yes
fp env currentPrint the environment a run would select (--env > FETCHPIER_ENV > default).Yes
fp init [name] [--template rest|graphql]Create a workspace in ./<name>, or in the current folder when name is omitted, with a starter request (rest by default).No
fp --help, fp <command> --helpHelp for the program or a command.No
fp -V, fp --versionPrint the CLI identity, version and copyright notice.No

When a command needs a workspace and none is found, it exits with code 4.

fp --version prints the product name, the CLI version, and the project’s copyright line to stdout. Invoking fp with no arguments prints the same banner above the usage text on stderr and keeps the usage exit code. No other invocation prints a banner, so a JSON report piped from stdout is never preceded by one. The CLI’s version number is independent of the desktop application’s.

  • No path: every .http file under the workspace’s requests/ folder, recursively — the same set from any directory, inside or outside the workspace.

  • Explicit paths (files or folders, as many as you like) are relative to the current directory, like any other command-line tool, so shell completion always produces a valid argument. If a path is missing from the current directory but exists inside the workspace, the error suggests the path to type:

    $ fp run requests/auth/login.http --workspace my-api
    Error: Path not found: requests/auth/login.http
    Did you mean my-api/requests/auth/login.http? Paths are relative to the current directory.
  • Folders are walked in name order; .runs/, .fetchpier/ and .git/ are skipped.

  • A .http file may hold several requests (### separators); only the first is executed, with a notice naming the file.

  • A target folder (or the workspace’s requests/) with no .http file stops the run with exit code 19: a run that sends nothing is not a success.

fp init creates .fetchpier/, a default environment with a baseUrl variable, requests/example.http, and a .gitignore (only if none exists). Existing files are never overwritten, and it fails with exit code 4 if the target already is a workspace. It ends by printing the command to run next (fp run --workspace my-api, or fp run when initializing the current folder), which works as printed from the same directory.

OptionEffect
-w, --workspace <path>Select the workspace explicitly.
-e, --env <name>Select an environment by folder name (case-insensitive).
--env-var KEY=VALUEOverride a variable for this run; repeatable. A script that later sets the same variable wins from then on.
--persist-envSave script variable changes (fp.environment.set / remove) to the local values, exactly as the desktop does. Without it they apply to the rest of the run only.
--bailStop after the first failed request — failed test, script error, file with no request, or a request that got no response.
--delay <ms>Wait between requests (whole number, 0 or more).
--timeout <ms>Per-request timeout. Default 30000 (30 s, like the desktop); 0 disables it — the only way to.
--proxy <url>Send every request through this proxy (http://user:pass@host:port). Wins over HTTPS_PROXY / HTTP_PROXY.
--no-proxyIgnore HTTPS_PROXY / HTTP_PROXY / NO_PROXY for this run. Cannot be combined with --proxy.
--insecureDisable TLS certificate verification for this run.
--reporter-json <file>Write JSON; use - for stdout, which silences the human reporter.
--reporter-json-no-bodyOmit response bodies from JSON.
--reporter-junit <file>Write JUnit XML.
--reporter-html <file>Write a standalone HTML report.
--reporter-html-only-failuresOpen the HTML report with its failures filter already applied.
--save-historyRecord each request that got a response in the workspace history (requests/.runs/), like the desktop activity log.
--suppress-exit-codeReturn 0 for run outcomes; fatal setup errors remain nonzero.
--show-secretsDo not redact credentials and cookies in the output. Use with care.
--asciiUse ASCII instead of Unicode glyphs.
--no-colorDisable ANSI colors; NO_COLOR and FORCE_COLOR are also honored.
--silentSuppress stdout while retaining stderr.
-v, -vv, -vvvIncrease verbosity (see below).

--timeout and --delay accept whole numbers from 0 to 2147483647 (the runtime’s timer limit, about 24.8 days); anything else is a usage error (exit 3). A single-value option given twice keeps its last value, the usual command-line convention — two --reporter-json write only the last one.

Levels add up, and are the same for one or many requests:

LevelAdds
defaultThe request and response lines, and every script’s fp.log / warn / error output.
-vEach request’s test list, the full URL instead of its path, and whether it went via proxy or direct.
-vvRequest and response headers.
-vvvRequest and response bodies, each cut at 64 KB (the JSON report has the full body).

Redaction and secret masking apply at every level. A binary body is never dumped to the terminal: -vvv prints one line with its size and type instead (a compressed response that was decoded is judged by its decoded content). Other text is printed as received.

  • Workspace: --workspace > FETCHPIER_WORKSPACE > upward discovery of a folder containing .fetchpier/settings.json, starting at the current directory and stopping above your home directory.
  • Environment: --env > FETCHPIER_ENV > default, matched case-insensitively against the environment’s folder name. The CLI never reads the desktop’s active-environment preference, which keeps CI predictable.
  • Variables: environment values, then per-machine local values, then --env-var overrides, then whatever earlier scripts of the run set. {{$env.NAME}} references resolve from a .env file at the workspace root, then from the process environment.

fp env list prints a warning for any environment folder whose config.json cannot be read. fp env current prints the name a run would select without checking that it exists; a run with an unknown environment exits with code 6 and lists the available ones, and one whose config.json cannot be read exits with code 4 naming the file. Like the desktop, fp run and fp env list keep an environment’s display name in step with its folder: if the name in config.json does not match the folder (for example after renaming the folder by hand), they rewrite that one field.

VariablePurpose
FETCHPIER_WORKSPACEWorkspace folder, when --workspace is not given.
FETCHPIER_ENVEnvironment folder name, when --env is not given.
FETCHPIER_WORKSPACE_KEYWorkspace key (FPK1-…) used to decrypt secret variables.
HTTPS_PROXY / HTTP_PROXYProxy for https / http targets.
NO_PROXYTargets that connect directly.
NO_COLORDisable colors (same as --no-color).
FORCE_COLORForce colors on when stdout is not a terminal (0 does not force).

Variables between requests. A value a script sets with fp.environment.set (or removes with fp.environment.remove) applies to every later request of the same run — a login’s token reaches the next call. A value set under a secret variable’s name stays masked as {secret:<name>} everywhere. Without --persist-env nothing is written to the workspace.

Missing variables. A {{var}} with no value in the chosen environment is sent literally, and a {{$env.NAME}} that neither .env nor the process environment defines is sent empty; each prints a warning (also in the JSON report, with a stable code) for the request that uses it. Neither fails the run — check the warnings, or assert on the response, to make a missing credential fail a pipeline. A name defined with an empty value is intended and produces no warning. Malformed percent-encoding also produces a warning and is sent literally.

Secrets in scripts. Only the secrets a request references (in its URL, headers, body or auth) are decrypted for it, so a script that reads a secret its own request does not use gets undefined. Reference it in the request, or pass it through a plain variable.

Secrets in CI. Provide the workspace key through FETCHPIER_WORKSPACE_KEY, typically injected from your CI provider’s secret store. Resolution is lazy: a run that touches no secret needs no key. A run that needs a secret it cannot resolve stops before sending, with a dedicated exit code (14–17).

--persist-env after a key rotation. If a teammate rotated the workspace key and you pulled it, your local values stay encrypted with the previous key until you open the workspace in the desktop app with the new key (it re-encrypts them). Until then, fp run --persist-env does not save script writes to secret variables, prints a warning naming the cause and the fix, keeps using the value in memory for the rest of the run, and saves writes to plain variables normally. A CI checkout has no local values, so this only happens on a developer machine.

Proxy. HTTPS_PROXY applies to https targets and HTTP_PROXY to http targets (lowercase forms too); NO_PROXY excludes hosts, domain suffixes, host:port entries, or everything (*). --proxy overrides both variables; --no-proxy ignores all three. The active proxy is shown in the header without credentials, followed by the NO_PROXY list when there is one. An unusable proxy URL stops the run before anything is sent (exit code 3); a proxy that does not answer fails the request in the report.

Default headers and compression. When a request does not set them, the CLI sends User-Agent: Fetchpier-CLI/<version> and Accept-Encoding: gzip, deflate, br. Compressed responses (gzip, deflate, br) are decoded; the JSON report carries compressedBytes, decompressedBytes and decodingStatus. A body that cannot be decoded is kept as received, with a warning, and fp.response.json() fails with the real cause.

Binary bodies. A request whose body is a file attached in the desktop (staged under requests/.files/) is sent with exactly that file’s bytes, and with the Content-Type of its file type unless the request sets one. A missing file, or an invalid reference, fails that request as “not run” instead of sending it empty.

The runner executes pre-request scripts, applies auth and variables, sends the request, captures cookies, executes post-response scripts, and evaluates tests. It continues after a failed request unless --bail is set. A request that received any response — including a 4xx or 5xx — passes unless one of its tests fails.

Network failures, TLS failures, timeouts, an interrupt while a request is in flight, script errors, and failed assertions are request outcomes, not internal crashes. The run continues to the next request (or stops, with --bail), the summary is produced, and every configured report is still written — the run that most needs inspecting is the one whose service was down. Conditions that prevent a run from starting at all — a missing workspace, environment, or path, an unreadable configuration, an unusable proxy, a missing workspace key — remain fatal and keep their own exit codes.

A request that got no response reads no response: <reason> (unreachable, TLS, timeout, proxy); one that was never attempted reads not run: <reason> (a file with no request, a pre-request script that failed, a binary file that is missing or whose reference is invalid). An expired timeout is reported as timed out after <limit>.

A single run can write JSON, JUnit XML, and standalone HTML simultaneously. JSON is versioned for consumers; JUnit works with common CI test dashboards; HTML works offline and includes theme and failure filters. Report targets must point to an existing parent directory.

  • Only JSON supports stdout. --reporter-json - writes to stdout; passing - to any other reporter is rejected with a clear message (exit code 2) rather than quietly creating a file literally named -.
  • The JSON schema version moves only on breaking changes. It changes only when a consumer would have to alter its code to keep parsing the report; new optional fields do not move it.
  • A request that never got a response is always a visible failure. Every format shows it as a failure carrying its reason — never as an entry that is merely missing, and never as an empty group that a dashboard would read as passing.

Redaction. Reports and terminal output redact Authorization / Proxy-Authorization, the API-key auth credential (query or header), and Cookie / Set-Cookie values. --show-secrets reveals them, but secret variables still print as {secret:<name>} — including in what a script logs, a test’s name, or a failed assertion’s message. Avoid publishing reports that contain production request or response data.

Masking limits. Secrets are masked by exact match, like CI log masking: a server that echoes a secret back escaped (inside JSON, URL-encoded) or a script that transforms it (hashes, encodes, slices it) is not recognized, and a very short secret can mask unrelated text that contains it. Prefer secrets of a realistic length.

The runner applies the workspace’s TLS configuration exactly like the desktop: the CA certificates and client certificates listed in the tls block of .fetchpier/settings.json, whose files live under .fetchpier/certs/ (both are normally managed from the desktop’s workspace settings and committed with the workspace). A client certificate is presented to the hosts it is configured for; a passphrase that references a secret variable is decrypted with FETCHPIER_WORKSPACE_KEY. Certificates configured only in the desktop’s own app settings are not visible to the runner. --insecure turns off certificate verification for a run.

History is off by default in automation. --save-history records each request that got a response in requests/.runs/ — a local debugging record, git-ignored by default. Like the desktop activity log it hides Authorization but keeps API keys and cookie values as sent: do not upload it as a CI artifact. The reports are the CI artifact, and they redact those values.

CodeMeaning
0Success.
1A request failed (failed test, script error, no response, or a file with no request), or a usage error (unknown command or option, bare fp). A run with failed requests never reports success, even when it declared no assertions.
2A reporter output target is invalid (missing directory, or - for a reporter other than JSON).
3Invalid flag value (--timeout, --delay), an unusable proxy URL (flag or variable), or --proxy together with --no-proxy.
4Workspace not found or invalid — including a .fetchpier/settings.json or a selected environment’s config.json that cannot be parsed; or fp init on a folder that already is a workspace.
5An input path does not exist or is not a .http file/folder; or the workspace has no requests/ folder.
6Environment not found.
7Malformed --env-var; expected KEY=VALUE.
8Reserved for fatal parse errors. A file that cannot be parsed into a request is reported as a failed request (exit 1).
9Unknown reporter (reachable only through the programmatic API).
10–13Reserved for multipart errors. Today a multipart problem is reported as a failed request with its reason (exit 1).
14A referenced secret needs decryption but FETCHPIER_WORKSPACE_KEY is not set.
15The workspace key is malformed or does not match this workspace.
16A secret value cannot be decrypted.
17A referenced secret has no value set (“needs value”).
18The Node.js runtime is older than the CLI’s minimum (22.19.0).
19Nothing to run: a target folder (or the workspace’s requests/) holds no .http file. Not masked by --suppress-exit-code.
130Interrupted by Ctrl-C or SIGTERM after partial reports are flushed.
255Internal error; report it as a bug.

Interrupting a run cancels the in-flight request and flushes any partial reports before exiting with 130 — no stack trace, because an interrupt is an intended outcome. Requests already completed are reported normally.

The package supports ESM and CommonJS consumers:

import { run, init, listEnvironments, currentEnvironment } from '@fetchpier/cli'
// No paths: the workspace's requests/ tree, from any working directory.
const summary = await run({ paths: [], workspace: '/path/to/ws', silent: true })
console.log(summary.summary.requests) // { total, passed, failed }
console.log(summary.summary.tests) // { total, passed, failed }

run returns the structured RunSummary (the JSON reporter’s schema) and never calls process.exit — your code decides what to do with the result. Explicit paths are relative to the process’s current directory. By default run also prints the terminal report to stdout; pass silent: true or your own reporters to avoid it. Fatal conditions (workspace, path, environment, secrets) reject the promise instead of returning a summary; a CliError carries its exit code in exitCode.

GitHub Actions — workspace in the api/ folder of the repository:

- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm install -g @fetchpier/cli
- run: mkdir -p reports && fp run --env ci --reporter-junit reports/fp.xml
env:
FETCHPIER_WORKSPACE: ${{ github.workspace }}/api
FETCHPIER_WORKSPACE_KEY: ${{ secrets.FETCHPIER_WORKSPACE_KEY }}
- if: always()
uses: actions/upload-artifact@v4
with: { name: fetchpier-report, path: reports/fp.xml }

GitLab CI — workspace at the repository root:

api-tests:
image: node:22
script:
- npm install -g @fetchpier/cli
- fp run --reporter-junit report.xml
artifacts:
when: always
reports:
junit: report.xml

To run a subset, pass a path relative to the job’s working directory, e.g. fp run api/requests/smoke.

Pre- and post-request scripts run in the same sandbox, with the same fp.* API, as in the desktop. The sandbox prevents accidental mistakes in scripts; it is not a defense against an adversary who has placed malicious code in your workspace. If you run untrusted collections in CI, run fp inside a container or another isolation boundary.

The current CLI does not switch or mutate desktop environment selection (fp env switch is not part of this release: the desktop keeps its active environment in its own user-data store, outside the workspace), rename or duplicate environments, import schemas, send ad-hoc URLs, install shell completions, run requests in parallel, or accept a per-run CA file. Configure shared TLS material in the workspace and use the desktop for schema import and environment maintenance.