Served by http-server on 127.0.0.1:7878 (default). All routes under
/api, JSON bodies, camelCase keys — the wire shapes are exactly the serde
shapes in crates/http-core/src/models.rs unless a wrapper is shown
below. The built UI is served at / from ui_dir with an SPA fallback to
index.html (any non-/api GET without an extension → index.html).
Permissive CORS on /api (the Vite dev server proxies /api →
http://127.0.0.1:7878).
Implementation notes for the server:
- axum 0.8: path params use
{param}syntax (NOT:param). pub fn router(workspace_dir: PathBuf, ui_dir: Option<PathBuf>) -> Routerandpub async fn serve(workspace_dir, port, ui_dir) -> anyhow::Result<()>(signatures already stubbed inhttp-server/src/lib.rs).- Errors:
{ "error": "message" }with 404 for missing slugs/ids, 400 for invalid input, 500 otherwise. - State is re-read from disk per request (no in-memory cache) — files are the source of truth and may be edited externally (git checkout, editor).
GET /api/workspace→{ "name", "version", "activeEnvironment", "path" }PUT /api/workspacebody{ "name"?, "activeEnvironment"? }(explicitnullclears activeEnvironment) → updated object
GET /api/collections→[{ "slug", "name", "description", "requests": [{ "slug", "name", "method", "url" }], "tree": TreeNode[] }]—requestsis flat and sorted by slug;treeis the folder layout over those same requests, already reconciled against them.TreeNodeis{ "type": "folder", "id", "name", "children": TreeNode[] }or{ "type": "request", "slug" }.POST /api/collectionsbody{ "name", "description"? }→{ "slug", ... }(201)PUT /api/collections/{slug}body{ "name"?, "description"? }(rename keeps slug)DELETE /api/collections/{slug}GET /api/collections/{slug}/requests/{rslug}→ fullRequestDefPOST /api/collections/{slug}/requests[?folder={fid}]body =RequestDef→{ "slug" }(201) — withoutfolderthe request lands at the collection rootPUT /api/collections/{slug}/requests/{rslug}body =RequestDefDELETE /api/collections/{slug}/requests/{rslug}
Folders are presentational: they live in collection.json's tree and never
move a request file, so a request keeps its slug however it is grouped.
POST /api/collections/{slug}/foldersbody{ "name", "parent"? }→{ "id", "name" }(201) —parentabsent/null= collection rootPUT /api/collections/{slug}/folders/{fid}body{ "name" }→{ "id", "name" }(rename keeps the id)DELETE /api/collections/{slug}/folders/{fid}— deletes the folder and every request nested under it, at any depthPOST /api/collections/{slug}/tree/movebody{ "kind": "folder"|"request", "id", "parent"?, "index"? }→ 204 — makes the node child numberindexofparent(null= root).indexclamps to the destination's length; moving a folder into its own subtree is a 400.
POST /api/sendbody:{ "def": RequestDef, "collection"?, "request"?, "environment"? }—defis always present (the tab's current, possibly unsaved state);collection/requestslugs are passed when it corresponds to a saved request so history can link back.environmentoverrides the active one. →ExecutionResult(includeshistoryId). Transport errors are a 200 witherrorset — HTTP 4xx/5xx from the target are normal responses. Whendef.testScriptis set it runs after the response arrives (see SCRIPTING.md): its results are appended totestResults, anything it printed comes back inconsole([{ "level", "message" }], never stored in history), and a script that could not run reportsscriptError. A script'shtx.environment.set/unsetcalls are applied to the active environment file as a side effect of the send.
GET /api/environments→[{ "slug", "name", "variables" }]POST /api/environments{ "name", "variables"? }→{ "slug", ... }(201)PUT /api/environments/{slug}{ "name"?, "variables"? }DELETE /api/environments/{slug}(clears manifest activeEnvironment if it pointed here)
GET /api/secrets→{ "global": [{ "name", "masked" }], "environments": { "<envslug>": [{ "name", "masked" }] } }PUT /api/secretsbody{ "scope": "global" | "<envslug>", "name", "value" }DELETE /api/secrets/{scope}/{name}(scopeglobalor env slug)POST /api/secrets/revealbody{ "scope", "name" }→{ "value" }
GET /api/history?limit=50&offset=0→{ "total", "entries": [HistoryEntry] }newest firstGET /api/history/{id}→HistoryEntryDELETE /api/history→ clears all
POST /api/import/openapibody{ "content": "<json|yaml text>", "name"? }→{ "slug", "name", "requestCount", "baseUrl"? }GET /api/export/openapi/{collection}→ the OpenAPI 3.0 document (Content-Disposition: attachment; filename="<slug>.openapi.json")
GET /api/git/status→{ "initialized": bool, "branch"?, "changes": [{ "path", "status" }] }(status: short porcelain code likeM,A,??)POST /api/git/init→ same as statusPOST /api/git/commitbody{ "message" }(stages all) →{ "hash", "message" }GET /api/git/log?limit=20→[{ "hash", "shortHash", "author", "date", "message" }]GET /api/git/diff?path=<optional>→{ "diff": "<unified text>" }