A local-first, git-friendly API client — a Postman/Bruno alternative with a Rust core, a full CLI, and a web UI.
A workspace is a plain directory of pretty-printed JSON files: one file per request, one per environment, one manifest. You version it with git like any other source tree. There is no cloud, no account, and no telemetry.
- Local-only, git-friendly storage — everything lives in your workspace directory as camelCase JSON, one file per request, so diffs and merges stay small and reviewable.
- Full CLI —
htxcovers the whole product: init, import, run, environments, secrets, history, export, git, and serving the UI. Exit codes are script-friendly (0ok,1test failures,2error) and--jsongives machine-readable output. - JavaScript tests, Postman-style — write
htx.test("...", () => htx.response.to.have.status(200))in a Monaco editor withhtxautocompletion; they run in the Rust core (no Node), so the UI andhtx runexecute the same script.htx.environment.setwrites through to the environment file, which is how you chain a login token into later requests. See docs/SCRIPTING.md. - Environments — named variable sets with
{{variable}}interpolation in URLs, params, headers, bodies, and auth; one environment is active at a time, or override per run. - Request history — every execution is appended to
.http/history.jsonlwith the resolved request, response, timing, and test results; browse it from the UI orhtx history. - OpenAPI v3 import/export — import a JSON or YAML document into a collection (one request per operation, parameters and body skeletons included) and export any collection back to OpenAPI 3.0; the round-trip is stable.
- Tabbed UI — a VS-Code-like single page (Vite + React + Joy UI) with request tabs, dirty indicators, keyboard shortcuts, response viewer, environments, history, secrets, and a git panel.
- Secret management, never committed — secrets live only in
.http/secrets.json(gitignored byhtx init,0600on unix), are referenced as{{secret.NAME}}, are masked in every listing, and are redacted from history before anything touches disk.
htx ships as two independent downloads. Neither needs the other; take whichever fits, or both.
| What it is | UI | |
|---|---|---|
htx-desktop |
The desktop app — the UI in its own window. | Compiled into the binary; the API runs in the same process, so nothing listens on a port. |
htx |
The command line tool, for scripting and CI. | htx serve hosts the UI in a browser over 127.0.0.1. |
Both are built for macOS (universal) and Windows (x64). See scripts/README.md for how the artifacts are assembled.
Build once (Rust 1.94+, Node 22+):
# build the web UI — both binaries need it
cd ui && npm install && npm run build && cd ..
# the desktop app: opens a window, scaffolds ~/htx on first run
cargo run -p htx-desktop
# or the CLI, serving the UI to a browser on http://127.0.0.1:7878
cargo run -p htx -- init my-api
cargo run -p htx -- -C my-api servehtx-desktop takes no arguments; set HTX_WORKSPACE to choose a workspace
directory other than ~/htx, and HTX_DEVTOOLS to enable the webview
inspector.
Or stay entirely in the terminal:
cargo install --path crates/htx # or use target/debug/htx
htx init my-api && cd my-api # scaffold workspace + git repo
htx import openapi ../examples/petstore-expanded.json
htx list # collections & requests tree
htx env set dev baseUrl https://api.example.com
htx secret set API_TOKEN # prompts; value stays out of shell history
htx run swagger-petstore/findpets -e dev # send + run assertionsGlobal flags: -C/--dir <path> selects the workspace directory (default .); --json switches run and history to machine-readable output.
| Command | Description |
|---|---|
htx init [NAME] |
Scaffold a workspace (into a NAME directory when given) plus git init and a .gitignore. |
htx serve [-p 7878] [--ui-dir DIR] |
Start the local API server on 127.0.0.1, serving the built UI when available. |
htx list |
Print the collections & requests tree. |
htx run <collection>[/<request>] [-e ENV] [--json] |
Run one request or a whole collection with its tests. |
htx run --all [-e ENV] [--json] |
Run every request of every collection. |
htx env list | show <slug> | set <slug> KEY VALUE |
Inspect and edit environments (set creates the environment when missing). |
htx secret list | set NAME [--value V] [--env SLUG] | rm NAME [--env SLUG] |
Manage secrets; set prompts without echo when --value is omitted. |
htx history [--limit N] [--json] / htx history clear |
Show or clear the execution history. |
htx import openapi <file> [--name NAME] |
Import an OpenAPI v3 document (JSON or YAML) into a new collection. |
htx export openapi <collection> [-o out.json] |
Export a collection as an OpenAPI 3.0 document. |
htx git status | commit -m MSG | log [--limit N] |
Git shortcuts for the workspace repository. |
Tests are JavaScript, with Postman's API, written in the Tests tab (Monaco,
with htx autocompletion) or straight into the request's JSON file:
htx.test("Status code is 200", function () {
htx.response.to.have.status(200);
});
htx.test("Response is a list of pets", function () {
const body = htx.response.json();
htx.expect(body.pets).to.be.an("array").that.is.not.empty;
htx.expect(body.pets[0]).to.have.property("name");
});
// Chain requests: saved to the active environment, used as {{token}} later.
htx.environment.set("token", htx.response.json().token);They run in the Rust core on Boa — no Node, no browser
— so htx run in CI executes exactly what the UI does, and exits 1 when a
test fails. Secrets are never visible to a script and are redacted from its
output. docs/SCRIPTING.md is the full reference,
including what is deliberately not supported.
my-api/
http.json # workspace manifest {name, version, activeEnvironment}
collections/
petstore/
collection.json # {name, description}
requests/
list-pets.json # one file per request: method, url, params,
# headers, body, auth, testScript, docs
environments/
dev.json # {name, variables: [{name, value, enabled}]}
.http/ # gitignored — never committed
secrets.json # secret values (0600 on unix)
history.jsonl # one execution record per line, append-only
.gitignore # created by `htx init`, contains `.http/`
crates/http-core— models, storage, interpolation, secrets, history, HTTP engine + assertions, the JavaScript test sandbox (docs/SCRIPTING.md), OpenAPI import/export, git (docs/DESIGN.md).crates/http-server— axum REST API and static hosting for the built UI (docs/API.md).crates/htx— thehtxbinary: CLI plushtx serve.crates/htx-desktop— thehtx-desktopbinary: the desktop app. A native window hosting the system webview, withui/distcompiled in and thehttp-serverrouter dispatched in-process — no local server, no port, no browser.ui/— Vite + React + TypeScript + Joy UI single-page app (docs/UI.md), shared by both binaries.
cargo test --workspace # unit + integration tests (core, server, CLI, desktop)
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all --check
cd ui
npm install
npm test -- --run # vitest
npm run build # tsc (strict) + vite build → ui/dist
npm run dev # dev server, proxies /api → 127.0.0.1:7878The desktop app embeds ui/dist at compile time, so re-run npm run build
before cargo build -p htx-desktop to pick up UI changes (its build.rs makes
cargo notice). On Linux, building it needs the GTK webview stack:
libgtk-3-dev libsoup-3.0-dev libwebkit2gtk-4.1-dev libjavascriptcoregtk-4.1-dev.
Tests spin up in-process HTTP servers on ephemeral localhost ports; nothing depends on external network access.
MIT (declared in the workspace Cargo.toml).
