Documentation

How to get ReqForge running on your machine, how to build your own binary if you would rather not use somebody else's, and how to use the parts of it that are not obvious. The application also carries its own guide: press F1 inside it.

What ReqForge is

A desktop API client and test runner: a Tauri v2 shell with a SolidJS front end and a Rust back end. The interface is a web view; everything underneath it, including every byte that goes on the wire, is native Rust.

Runs onmacOS, Windows, Linux
Needs an accountA seat, assigned to your work email by your administrator
Needs a networkOnly to reach the APIs you are testing
Stores your work asOne JSON file per collection, in a folder you can back up, diff and commit
Sends telemetryNone. No analytics and no crash reporting. It checks your licence once a day and sends nothing else — see privacy
Costs£96 per seat per year — pricing
HTTP engineRust (reqwest over rustls)
ScriptingJavaScript, Postman-compatible pm API, in an embedded engine
Also ships asreqforge-cli, a headless binary that runs the same collections in CI

Installing it

ReqForge is a licensed product: you need a seat before it will send a request. Start a trial and you will be sent a build for each platform your team uses, along with the seat that runs it.

PlatformWhat you getNotes
macOS 12 or newer .dmg, Apple silicon and Intel Not yet notarised — see the warning below
Windows 10 or 11 .msi installer Not yet signed with an EV certificate — see below
Linux .AppImage and .deb Needs GTK 3 and WebKitGTK 4.1, which most desktops already have
CI reqforge-cli, one binary per platform Runs your collections on a build agent under one of your seats
Your operating system will warn you on first run. Code signing is still being sorted out: Windows SmartScreen will say the publisher is unknown, and macOS Gatekeeper will refuse the first launch. On Windows, choose More info then Run anyway. On macOS, open it once from the Finder's right-click menu and choose Open. If your organisation blocks unsigned software, tell us and we will let you know when signed builds are ready rather than sending you something you cannot install.

Activating your seat

The first time you open ReqForge it asks for the work email address your administrator gave the seat to. Type it, and a six-digit code arrives by email; type that, and the machine is activated.

  1. Open ReqForge. The activation screen is the first thing it shows.
  2. Enter your work email address and press Send code.
  3. Type the six digits from the email. It is good for ten minutes.
  4. That is it — the app opens, and stays open.

A seat runs on one machine at a time. Moving to a new laptop takes one click: Deactivate this machine in app settings on the old one, then activate on the new one. If the old machine is gone — lost, wiped, or in a skip — your administrator releases it from the console instead.

What the app saysWhat it meansWhat to do
You do not hold a seat The address is not assigned to a seat in any organisation Ask your administrator to assign you one
That seat is already active on another machine One seat, one machine Deactivate the other machine, or ask for it to be released
Offline — ReqForge locks in n days It has not reached the licence service for a while Connect to a network for a moment; it refreshes by itself
The trial has ended Fourteen days are up Buy seats, and the same install carries on

What activation sends us: your email address, a one-way hash identifying the machine, a label for it, and the app version. Never your requests, collections, URLs or responses — those stay on your disk. The privacy policy lists it exactly.

Running it offline

A licence is good for fourteen days away from the network, and the app tells you how many are left. Longer than that by design — an air-gapped lab, a customer site with no outbound access — is something we can set per organisation: ask.

The command line in CI

reqforge-cli reads a licence token from REQFORGE_LICENCE_TOKEN, so a build agent needs no interactive activation. Your administrator issues the token from the console; treat it as a secret, and put it in your CI secret store rather than in the repository.

Your first five minutes

A fresh ReqForge window with no request open, listing the top-level keyboard shortcuts.
  1. Press Create my first request in the middle of the window. On a fresh install that makes a collection and a request together. If you already have collections, press + in the Collections header instead.
  2. Type a URL and pick a method beside it. Try https://httpbin.org/get.
  3. Press Send (Ctrl/⌘+Enter). The response appears with its status, timing and size.
  4. Make it reusable. Open Manage in the top bar, create an environment called Local, add baseUrl = https://httpbin.org, select it in the top-bar dropdown, and change the URL to {{baseUrl}}/get. Hover the URL to see what it resolves to and where the value came from.
  5. Assert something. In the Tests tab:
    pm.test("status is 200", () => {
      pm.response.to.have.status(200);
    });
    Send again and read the Tests tab in the response panel.
  6. Run the lot. Press on the collection row in the sidebar to run every request in it.
  7. Press F1 for the full guide.

Building a request

GET, POST, PUT, PATCH, DELETE, HEAD and OPTIONS. The URL bar and the Params table stay in sync in both directions: edit the query string and the rows follow, edit the rows and the URL follows.

Variables and scopes

Write {{name}} in a URL, param, header, body or auth field and it is substituted when the request is sent.

{{baseUrl}}/users/{{userId}}?token={{apiKey}}

Names resolve from the innermost scope outwards. Where two scopes define the same name, the innermost wins:

Environment      (wins)
  Folder
    Collection
      App globals  (fallback)

Two things make this usable rather than a guessing game. Typing {{ offers the names in scope along with what each currently resolves to and which scope supplies it. And hovering — or tabbing into — any field shows the fully resolved text, the value behind each token, and which scopes it overrode.

A variable's value may itself contain {{tokens}}, which are resolved in turn. That is how one collection covers every environment: the collection owns the shape of the URL, each environment owns only the piece that changes.

Collection   baseUrl = https://{{env}}.api.example.com/v{{version}}
             version = 2
Environment  env     = staging

Request      {{baseUrl}}/users  ->  https://staging.api.example.com/v2/users
This is Postman's algorithm, step for step: the text is substituted, then substituted again over the result, up to nineteen times — so a collection that relied on nested variables there resolves to the same text here. Anything still in braces at the end keeps them, wherever in the chain it sits, and a banner lists it with a one-click fix.

Environments

The environments dialog with Local, Staging and Production, and the Local variables listed.

An environment is a named set of variables — one per place you run against. Pick the active one in the top bar; No Environment is a valid choice and means only collection, folder and global variables resolve.

Mark a value secret and it is masked in the interface, kept out of exports unless you explicitly ask for it, and never written to the log. A secret still lives in a plain file on your disk: secrecy here is about not leaking it into an export or a bug report, not encryption at rest.

Inheritance

App defaults, then the collection, then each folder, then the request. Headers, auth, scripts, variables and transport settings all cascade field by field, so a folder can raise a timeout without restating everything else. Each field shows what it inherits and from which level.

The practical effect: set auth once on a collection and every request inside it is authenticated; add a header on a folder and only that folder's requests send it.

Authentication

TypeNotes
BasicUsername and password
BearerA token, usually from a variable
API keySent as a header or a query parameter
OAuth 2.0Client credentials, with the token cached until it expires
AWS Signature V4Access key, secret, session token, region and service

Set it on the collection and leave every request on Inherit, which is the default.

Scripts and tests

The Tests tab of a request, showing a JavaScript test script with pm.test assertions and syntax highlighting.

Two scripts per request, both JavaScript with a Postman-compatible pm API: Pre-request runs before the send, Tests runs after the response arrives. Both cascade, so a collection-level script runs for every request inside it.

pm.test("status is 200", () => {
  pm.response.to.have.status(200);
});

const body = pm.response.json();

pm.test("returns a page of invoices", () => {
  pm.expect(body.data).to.be.an("array");
  pm.expect(body.data.length).to.be.above(0);
});

// Hand the first invoice to the requests that come after this one.
pm.environment.set("invoiceId", body.data[0].id);
console.log("first invoice", body.data[0].id);

pm.sendRequest is available when a call needs a token first, and pm.iterationData.get() reads the current row when the CLI is running with a data file. When the response is a page rather than JSON — a sign-in policy, a SAML post — cheerio.load(pm.response.text()) reads it with the same CSS selectors a Postman collection uses, so the code on a redirect link or a hidden form field is one .attr() away. require serves the sandbox packages a Postman script is written against — lodash, moment, crypto-js, uuid, chai, ajv, tv4, xml2js, csv-parse and the Node modules beside them — so an imported collection's scripts run unedited. Scripts run in an embedded engine with time limits, so a runaway loop cannot hang the app.

The response panel's Tests tab, listing three passing assertions.
Assertion results appear in the response panel's Tests tab.

Reading responses

Running a collection

The collection runner: nine requests run, one failed, with the failing assertion expanded.

Press on a collection or folder row. Requests run in tree order, results stream in as they finish, and each row expands to the URL that was reached, the assertions and any error. Stop at the first failure ends the run on the first failing request. There is an inter-request delay for APIs that rate-limit.

The runner, the Send button and reqforge-cli all call the same execution core, so a run in CI cannot behave differently from a run at your desk.

The command line

reqforge-cli runs collections headlessly, through the same engine: same variables, same scripts, same settings, same cascade. Its option surface mirrors Newman's and it reads the same files, so an existing pipeline usually only needs the command name changed.

reqforge-cli
$ reqforge-cli run ./billing.postman_collection.json

ReqForge · Billing API · 3 request(s)

Billing API / Invoices / List invoices GET http://127.0.0.1:8099/v1/invoices
  200 OK · 3 ms · 123 B
  ✓ status is 200
  ✓ returns a page of invoices

Billing API / Invoices / Create invoice POST http://127.0.0.1:8099/v1/invoices
  201 Created · 1 ms · 30 B
  ✓ status is 201

Billing API / Health check GET http://127.0.0.1:8099/healthz
  200 OK · 0 ms · 55 B
  ✓ status is 200
  ✓ every dependency is up

3 request(s) · 0 failed · 5 assertion(s) · 0 failed · 46 ms

Commands

CommandWhat it does
run <collection>Run a collection, a folder, or a single request
list [what]collections, environments, or requests <collection>
logsPrint the log file path, or its tail with --tail N
doctorPaths, versions, counts and tool availability in one block
help / versionWhat they say

What the arguments accept

Format is decided by the content, not the file name, so the same command works whichever tool produced the file. A file the Postman API wrapped in a collection or environment key is unwrapped on the way in.

ArgumentAccepts
<collection>A ReqForge or Postman v2.x collection file, or the name or id of a collection in this workspace
-e, --environmentA ReqForge or Postman environment file, or the name of an environment in this workspace
-g, --globalsA ReqForge environment or Postman globals file
-d, --iteration-dataA CSV whose first row names the columns, or a JSON array of objects

Run options

FlagMeaning
-e, --environment <name|file>Activate an environment
-g, --globals <file>Global variables — the outermost layer
-d, --iteration-data <file>Run once per row, binding that row's columns as variables
-n, --iteration-count <n>Run the selection this many times
--folder <name>Only run this folder (Name or Parent/Child). Repeatable
--request <name>Only run requests matching this. Repeatable
--env-var <key=value>Set a variable above the environment. Repeatable
--global-var <key=value>Set a variable at the globals layer. Repeatable
-r, --reporters <list>cli (default), json, junit — comma separated
--reporter-json-export <file>Where the json reporter writes
--reporter-junit-export <file>Where the junit reporter writes
-o, --output <file>Where to write, when exactly one reporter produces a file
--timeout-request <ms>Override the request timeout for this run
--delay-request <ms>Wait between requests
-k, --insecureSkip TLS certificate verification
--ignore-redirectsDo not follow redirects
--bailStop at the first failing request
-x, --suppress-exit-codeExit 0 even when something failed
--color <on|off|auto>Colour the cli reporter
--silentPrint nothing; file reporters still write
--verbosePrint the request as sent, redirect hops, bodies, script logs
--data-dir <path>Where this workspace's collections and environments live

The pre-Newman spellings — --env, --var, --timeout, --delay, --reporter and --no-color — still work.

Exit codes

CodeMeaning
0Everything passed
1A request failed, or an assertion failed
2Bad usage, or a file or collection that could not be read

Examples

reqforge-cli doctor
reqforge-cli list collections
reqforge-cli run ./api.postman_collection.json -e ./staging.postman_environment.json
reqforge-cli run "My API" --folder Users --bail
reqforge-cli run "My API" -d users.csv --delay-request 200
reqforge-cli run "My API" -r cli,junit --reporter-junit-export results.xml
reqforge-cli logs --tail 100

In CI

A build agent cannot type a code into an activation screen, so it reads a licence token from REQFORGE_LICENCE_TOKEN instead. Your administrator issues one from the console; it is a secret, so it belongs in your CI secret store and nowhere else. The agent uses one of your organisation's seats.

Point REQFORGE_DATA_DIR at a throwaway directory, or hand the CLI an exported collection file and skip the data directory altogether.

# .github/workflows/api-tests.yml
name: API tests
on: [push]

env:
  REQFORGE_LICENCE_TOKEN: ${{ secrets.REQFORGE_LICENCE_TOKEN }}

jobs:
  api:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run the collection
        run: |
          reqforge-cli \
            run ./tests/api.postman_collection.json \
            -e ./tests/staging.postman_environment.json \
            -r cli,junit --reporter-junit-export results.xml

      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: api-test-results
          path: results.xml

The job fails on exit code 1, which covers both a request that errored and an assertion that did not hold. Add --verbose when a CI-only failure needs diagnosing, or read reqforge-cli logs --tail 200.

The editor on any collection or folder has a Copy CLI command button that writes the exact invocation for that scope to your clipboard.

Load testing

The Load Test tab after a run: a results table with latency percentiles and throughput, above k6's streamed console output.

The Load Test tab turns the request you have open into a k6 script and runs it locally. Choose the number of virtual users, then either a duration (30s, 2m) or a fixed iteration count. k6's output streams while the test runs; at the end you get average, p90, p95, p99 and max latency, total requests, request rate, failure rate and peak VUs.

k6 is a separate tool and must be installed. Restart ReqForge after installing it; the tab says so if it cannot find k6.

brew install k6          # macOS
winget install k6        # Windows
snap install k6          # Linux

Security scanning

The OWASP ZAP dialog: connection settings, target selection, and a findings list with a high-risk alert expanded.

Security in the top bar drives OWASP ZAP against your APIs. ReqForge can start and stop a bundled ZAP for you, talk to a daemon you already run, or use an executable path you give it.

Scan the active request, every request in the current collection, or every request in every collection. Targets are resolved URLs, so {{variables}} are substituted first; anything still unresolved or not http(s) is skipped and counted so you know it was left out. Spidering always runs; the active scan is opt-in, because it sends attack traffic. Findings are grouped by risk with description, evidence and a suggested fix, and export as JSON.

Only scan systems you are authorised to test. An active scan sends real attack payloads and can create, modify or delete data.

Workspaces and git

The workspace dialog: the current folder with its git branch and remote, the API files found in it, and recent folders.

A workspace is a folder holding collections/, environments/ and settings.json. ReqForge works out of one at a time; until you open another, that is the default data directory.

Because a collection is one JSON file, git tracks it with no translation. Press Git in the header to clone a repository as a workspace, then commit, branch, pull and push from inside the app. libgit2 is bundled, so this works whether or not git is installed.

Committing a collection commits whatever is in it, including credentials that are not marked secret. Marking something secret keeps it out of an export, but a commit records the file as it is. Keep real credentials in an environment you do not commit.

Import and export

Import in the top bar takes four formats, pasted as text or picked as a file.

FormatBecomes
Postman Collection v2.1A new collection, folders and all
Postman EnvironmentA new environment
cURL commandA single request in the collection you choose
OpenAPI 3.x (JSON) Paths become requests in document order, grouped by tag. The first server becomes a {{baseUrl}} variable; query and header parameters and JSON body examples come across; each {path} parameter is seeded as an empty collection variable

Export with on a collection row: Postman v2.1 JSON that other tools, and ReqForge's own CLI, can read. If the collection holds credentials, ReqForge asks before writing them out. Choosing to blank them keeps every key, header and auth type with the credential half empty and marks the filename .redacted.

Where your data lives

PlatformPath
Windows%APPDATA%\com.adrian.reqforge
macOS~/Library/Application Support/com.adrian.reqforge
Linux~/.local/share/com.adrian.reqforge
<data-dir>/
  collections/<id>.json      one file per collection
  environments/<id>.json     one file per environment
  settings.json              app-wide defaults, globals and headers
  history/                   per-request run history, pruned automatically
  logs/reqforge.log          rotating at 5 MB, keeping three previous files

Back it up by copying the folder. Open ReqForge on another folder and all of that comes from there instead.

VariableEffect
REQFORGE_DATA_DIRUse this directory instead of the platform default
REQFORGE_LOGLog level: error, warn, info (default), debug, trace
RUST_BACKTRACE=1Add backtraces to ordinary errors

Cookies are handled for you — a Set-Cookie is stored and sent back to the same site — and live in memory only, so quitting forgets them. Defaults → Cookies lists what is stored and lets you forget one or all of them.

The log records the URLs and headers you send. Review it before attaching it to a public bug report.

Keyboard shortcuts

KeysAction
Ctrl/⌘+EnterSend the active request
Ctrl/⌘+SSave the active request
Ctrl/⌘+TNew request
Ctrl/⌘+WClose the active tab
Ctrl/⌘+KJump to the sidebar filter
Ctrl/⌘++NOpen another window on the same folder
Ctrl/⌘++OOpen the workspace panel
F1 or Ctrl/⌘+/Open the user guide
EscClose a dialog, or clear the sidebar filter
Sidebar: move between rows
Sidebar: open a folder, or close it and step back out
Tab strip: previous or next tab
EnterParam and header tables: key → value → next row

The collection tree and the tab strip are each a single tab stop, implementing the WAI-ARIA tree and tablist patterns, so Tab carries on rather than walking you through every row.

The in-app user guide open at Getting started, with a searchable list of sections.
The in-app guide goes deeper than this page, and is searchable.

Troubleshooting

SymptomCause and fix
npm error enoent ... /package.json Wrong directory. Run commands from reqforge/, not the repository root
cargo metadata ... No such file or directory Rust is not installed or not on PATH. See rustup.rs
./start-mac not found Use the full filename, ./start-mac.command
tauri dev fails on Linux with a missing .pc file A missing system package — see the Linux prerequisites above
A variable is not substituting Hover the field. The preview says whether it is defined and which scope would supply it
A request fails with a TLS error Check Verify TLS in Settings, remembering it may be inherited from a folder or collection
The Load Test tab says k6 is missing Install k6 and restart ReqForge
A request is authenticated as the wrong user Defaults → Cookies. Forget the stale one
The build warns about an unknown publisher Code signing is not configured yet
Anything else Defaults → Diagnostics, or reqforge-cli logs --tail 100. Raise the detail with REQFORGE_LOG=debug

Getting the builds

Builds go to the organisations that hold seats, by email, along with the seat itself. There is no public download page while the installers are unsigned: an unsigned installer means a SmartScreen warning on Windows and a Gatekeeper refusal on macOS, and a first run that looks like malware is not something to hand a stranger. This page gets a download table the day the certificates are in place.

Trialling ReqForge takes one email, and the builds come back with the trial.