Documentation

How to get ReqForge running on your machine, how to activate the seat that runs it, and how to use the parts 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 address. No password: activation is a six-digit code sent to that address
Needs a networkThe APIs you are testing, and our licence service once a day. Three weeks of missed checks in a row is what it takes to stop it. See running it offline
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£8 per seat per month, or £50 per seat per year. See 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. A seat is what lets it put traffic on the wire: sending a request, running a collection, opening a WebSocket or SSE stream, starting the mock server, running a load test and running a security scan. Everything else (opening, editing, importing, exporting, and the git commands) works whatever the licence says. 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
Command line reqforge-cli, one binary per platform The same runs, headless. Which commands need a licence
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.
No seat yet? Only your organisation's administrator can give you one; the app cannot assign it. Administrators: seats, invitations and released machines are all in the console. Sign in at the top of this page. If nobody there knows who your administrator is, write to us and we will tell you.

A seat runs on one machine at a time. Moving to a new laptop takes one press: Deactivate this machine in Defaults → Licence on the old one, then activate on the new one. Do it while the old machine is still online, handing the seat back needs the licence service, and a seat released only locally is a seat nobody else can use. If the machine has already gone (lost, wiped, or in a skip), write to us and we release the seat for you.

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
ReqForge is not activated on this machine There is no licence stored here, or the stored one cannot be read Defaults → Licence, and activate with your work email
Your ReqForge seat was removed The seat was released or reassigned while this machine was using it Ask for a seat, then activate again from Defaults → Licence
This machine was deactivated by your administrator The seat is still yours; this particular machine was released Activate it again from Defaults → Licence
The trial has ended Fourteen days are up, and the app is read-only Buy seats in the console, or ask us; the same install carries on where it left off

What activation sends us: your email address, a one-way hash identifying the machine, a label for it (the hostname and the operating system), and the app version. The daily check after that sends the signed licence the app already holds, 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

ReqForge checks its licence once a day, in the background, and never blocks the interface to do it. The licence it holds is good for seven days; when that runs out a fourteen-day grace period starts, and a banner counts down the days left. So it is three weeks from the last successful check before the app goes read-only, and a moment of network anywhere in that window resets the whole of it. There is nothing to press.

The fourteen days are set per organisation. Longer by design (an air-gapped lab, a customer site with no outbound access) is something we can raise for you: ask. Winding the machine's clock back does not buy any of it: ReqForge remembers the latest moment it has seen and will not believe an earlier one.

Past three weeks, reconnecting is not enough on its own: the licence has gone too stale to refresh, and the app asks you to activate again, the same email address and six-digit code as the first time. The seat is still yours, and nothing you have made is touched.

What a lapsed licence leaves working

Read-only, not shut. Collections, folders, requests, environments, history and saved examples stay open, editable, importable and exportable, and the git commands keep working. Six things stop: sending a request, running a collection, opening a WebSocket or SSE stream, starting the mock server, running a load test, and running a security scan. Nothing of yours is deleted, locked or held back.

On a build agent

reqforge-cli reads a licence token from REQFORGE_LICENCE_TOKEN, since there is nobody at a keyboard on a build agent to type a code into an activation screen. The token is bound to the machine it was issued for, and nothing issues one for a build agent yet, so an unattended pipeline cannot hold a seat today. The command line has the detail, and what runs without a licence in the meantime.

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. Needs a licence
diagnose <url>Check the connection to a URL: DNS, TCP, TLS and one HTTP round trip, separately. Needs a licence
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

run and diagnose put traffic on the wire, so they need a seat: the app's activation on the same machine, or a licence token in REQFORGE_LICENCE_TOKEN. list, logs, doctor, help and version do not: an install can always be diagnosed, which is exactly when you need it.

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.

Diagnose options

FlagMeaning
-k, --insecureSkip TLS certificate verification
--proxy <url>Go through this proxy instead of the default
--timeout <ms>Cap the HTTP round trip (default 30000)
--jsonPrint the whole diagnosis as JSON

diagnose sends none of your request: no headers, no auth, no body. It exits 0 when every check passes and 1 when one fails, so "it does not work" becomes "the name does not resolve on this machine" or "the port is closed".

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 diagnose https://api.example.com/v1/users
reqforge-cli logs --tail 100

In CI

Not available yet. run and diagnose verify a licence token from REQFORGE_LICENCE_TOKEN, and that token is bound to one machine: the CLI checks it against the agent it is running on and refuses a token issued for anywhere else. Nothing issues a token for a build agent today, so a pipeline cannot hold a seat yet. Issuing them is being built; tell us if it is what would decide it for you, because that changes what gets done first. The rest of this section is what a pipeline will look like, and is here so you can see whether it fits yours.

Point REQFORGE_DATA_DIR at a throwaway directory, or hand the CLI an exported collection file and skip the data directory altogether. Keep the token in your CI secret store and nowhere else.

# .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 the load tool's streamed console output.

The Load Test tab integrates with k6, an open-source load testing tool. It turns the request you have open into a script and runs it on your own machine. Choose the number of virtual users, then either a duration (30s, 2m) or a fixed iteration count. The tool'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, third-party tool and must be installed. Restart ReqForge after installing it; the tab says so if it cannot find it.

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

Security scanning

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

Security in the top bar integrates with ZAP, an open-source web application security scanner, and points it at your APIs. Like k6, ZAP is a separate install: ReqForge finds the one on your machine, starts it as a daemon for you and stops it again, or talks to a daemon you already run. Set an executable path only to use a different install from the one it found.

ZAP must be installed. The dialog says so, with these commands, when it cannot find one; press Refresh status after installing. ZAP needs a Java runtime, which its macOS and Windows installers include.

brew install --cask zap          # macOS
winget install ZAP.ZAP           # Windows
snap install zaproxy --classic   # Linux

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
REQFORGE_LICENCE_TOKENThe licence reqforge-cli run and diagnose use. Unset, they use the app's activation on this machine. The app ignores it and reads its own

The licence is not in that folder. It lives in the operating system's keychain (Keychain Access on macOS, Credential Manager on Windows, your desktop's secret store on Linux), and it is tied to the machine, so copying the data directory somewhere else carries your work but not your seat. Activate on the new machine instead.

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
ReqForge is not activated on this machine No licence is stored here yet. Defaults → Licence, then activate with the work email your seat was given to
Sending comes back with a licence message instead of a response The licence has lapsed, the seat was removed, or it has been offline too long. The banner across the top says which, and Check again on it refreshes. Reading, editing and exporting carry on regardless
Offline: ReqForge locks in n days The daily check has not got through. Any moment of network clears it; see running it offline
reqforge-cli: no licence run and diagnose need the app activated on this machine, or REQFORGE_LICENCE_TOKEN. list, logs and doctor work without either
That seat is already active on another machine One seat, one machine. Deactivate the other one from Defaults → Licence, or ask us to release it
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
The Security dialog says ZAP is not found Install ZAP and press Refresh status, or set the executable path to where it is
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

Every platform, every release, with the headless CLI beside the installers. Download the one for your machine, install it, open it, and activate with the work email your seat is on. The app checks that page once a day and offers each new version from inside Defaults → Diagnostics; there is nothing to reinstall by hand after the first time.

PlatformDownloadAlso on the page
macOS.dmg, one build for Apple silicon and Intelreqforge-cli
Windows.exe installerreqforge-cli.exe
Linux.deb or .rpmreqforge-cli

The Windows and macOS builds are not code-signed yet, so SmartScreen and Gatekeeper warn the first time you run one. Nothing is wrong, and install shows exactly what to click. If your organisation blocks unsigned software, tell us and we will say when signed builds are ready.

A trial is opened in the console, and the builds are on the releases page above; the seat is what makes them run.