Skip to content

Latest commit

 

History

History
104 lines (92 loc) · 5.51 KB

File metadata and controls

104 lines (92 loc) · 5.51 KB

http HTTP API (contract)

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>) -> Router and pub async fn serve(workspace_dir, port, ui_dir) -> anyhow::Result<()> (signatures already stubbed in http-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).

Workspace

  • GET /api/workspace → { "name", "version", "activeEnvironment", "path" }
  • PUT /api/workspace body { "name"?, "activeEnvironment"? } (explicit null clears activeEnvironment) → updated object

Collections & requests

  • GET /api/collections → [{ "slug", "name", "description", "requests": [{ "slug", "name", "method", "url" }], "tree": TreeNode[] }] — requests is flat and sorted by slug; tree is the folder layout over those same requests, already reconciled against them. TreeNode is { "type": "folder", "id", "name", "children": TreeNode[] } or { "type": "request", "slug" }.
  • POST /api/collections body { "name", "description"? } → { "slug", ... } (201)
  • PUT /api/collections/{slug} body { "name"?, "description"? } (rename keeps slug)
  • DELETE /api/collections/{slug}
  • GET /api/collections/{slug}/requests/{rslug} → full RequestDef
  • POST /api/collections/{slug}/requests[?folder={fid}] body = RequestDef → { "slug" } (201) — without folder the request lands at the collection root
  • PUT /api/collections/{slug}/requests/{rslug} body = RequestDef
  • DELETE /api/collections/{slug}/requests/{rslug}

Folders & layout

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}/folders body { "name", "parent"? } → { "id", "name" } (201) — parent absent/null = collection root
  • PUT /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 depth
  • POST /api/collections/{slug}/tree/move body { "kind": "folder"|"request", "id", "parent"?, "index"? } → 204 — makes the node child number index of parent (null = root). index clamps to the destination's length; moving a folder into its own subtree is a 400.

Send

  • POST /api/send body: { "def": RequestDef, "collection"?, "request"?, "environment"? } — def is always present (the tab's current, possibly unsaved state); collection/request slugs are passed when it corresponds to a saved request so history can link back. environment overrides the active one. → ExecutionResult (includes historyId). Transport errors are a 200 with error set — HTTP 4xx/5xx from the target are normal responses. When def.testScript is set it runs after the response arrives (see SCRIPTING.md): its results are appended to testResults, anything it printed comes back in console ([{ "level", "message" }], never stored in history), and a script that could not run reports scriptError. A script's htx.environment.set/unset calls are applied to the active environment file as a side effect of the send.

Environments

  • 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)

Secrets (masked by default)

  • GET /api/secrets → { "global": [{ "name", "masked" }], "environments": { "<envslug>": [{ "name", "masked" }] } }
  • PUT /api/secrets body { "scope": "global" | "<envslug>", "name", "value" }
  • DELETE /api/secrets/{scope}/{name} (scope global or env slug)
  • POST /api/secrets/reveal body { "scope", "name" } → { "value" }

History

  • GET /api/history?limit=50&offset=0 → { "total", "entries": [HistoryEntry] } newest first
  • GET /api/history/{id} → HistoryEntry
  • DELETE /api/history → clears all

OpenAPI

  • POST /api/import/openapi body { "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")

Git

  • GET /api/git/status → { "initialized": bool, "branch"?, "changes": [{ "path", "status" }] } (status: short porcelain code like M, A, ??)
  • POST /api/git/init → same as status
  • POST /api/git/commit body { "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>" }