Local dev HTTP API
Everything `hs-x dev invoke` and the MCP tools can do is also three plain HTTP endpoints on the dev server — same engine, same production-router dispatch, same envelopes. This is the door for CI smoke jobs, REST clients, and anything that speaks HTTP and nothing else.
TL;DR — GET /_hsx/health, GET /_hsx/manifest, POST /_hsx/invoke/<capability-id> with optional {input, enrolledObject, install} JSON. The invoke response carries the runtime envelope plus logs: [{level, message, fields}] from the handler. When the dev server runs under Bun, edits to worker files apply on the next request — the server hot-reloads modules per invocation; under Node, restart to pick them up.
GET /_hsx/health
Liveness plus identity — useful as a CI readiness gate.
curl -s http://127.0.0.1:8787/_hsx/health{ "ok": true, "cliVersion": "0.2.5" }cliVersion is the identity half: it names the process that answered, and it's how hs-x dev status tells an hs-x dev server apart from anything else that happens to be holding the port.
GET /_hsx/manifest
The project's full capability inventory: every worker, every capability with kind, label, object type, and typed input/output fields. This is the discovery step — the HTTP counterpart of hsx.dev.capabilities over MCP, and the richer of the two: the MCP tool reports capability ids, kinds, and object types, while the manifest adds labels and the typed field maps.
curl -s http://127.0.0.1:8787/_hsx/manifest{
"workers": [
{
"name": "deals",
"capabilities": [
{ "kind": "tool", "id": "tag-high-value-deals", "label": "Tag high value deals", "objectType": "deal" }
]
}
]
}One nuance: the manifest is computed once at server start. A capability you add or rename afterwards is already invokable — /_hsx/invoke reloads worker modules per request — but it won't show up here until you restart hs-x dev.
POST /_hsx/invoke/:capabilityId
Dispatch one capability through the production runtime router — identical code path to a deployed Worker: payload validation, context construction, handler, result envelope. All three body keys are optional; fixture defaults fill what you omit.
curl -s -X POST http://127.0.0.1:8787/_hsx/invoke/tag-high-value-deals \
-H 'content-type: application/json' \
-d '{
"input": { "threshold": 25000 },
"enrolledObject": { "id": "d1", "objectType": "deals", "properties": { "amount": "50000" } }
}'{
"ok": true,
"capabilityId": "tag-high-value-deals",
"worker": "deals",
"kind": "tool",
"response": { "ok": true, "capabilityId": "tag-high-value-deals", "result": { "status": "ok", "output": { "tagged": true } } },
"logs": [
{ "level": "info", "message": "tagging deal", "fields": { "amount": 50000 } }
]
}| Body key | Default | What it is |
|---|---|---|
input | {} | The handler's typed input |
enrolledObject | fixture (id: fixture-001, capability's object type — contacts when it has none) | {id, objectType, properties} |
install | local fixture install | Install identity {id, portalId, state, config} |
Three behaviors worth knowing:
- Hot reload — under Bun. Running under Bun, the server re-reads edited worker modules (and their helper imports) on every invocation — edit, save, re-POST; no restart. Under Node (how the published
hs-xbinary runs by default), modules stay cached, so restart the server to pick up edits. - Handler env from
.dev.vars. Handlers getctx.envfrom.dev.varsat the project root — the Wrangler convention — re-read on every invocation, so secret edits apply without a restart on either runtime. - Honest exit semantics.
okis false when the dispatch failed or the runtime envelope reports failure, so a CI job can gate on the HTTP response alone.
Failures stay JSON too — always { "ok": false, "error": ... }:
| Status | error | When |
|---|---|---|
| 400 | invalid_json | The request body isn't valid JSON |
| 404 | HSX_E_DEV_UNKNOWN_CAPABILITY | No capability with that id — the hint lists what the project does declare |
| 500 | HSX_E_INTERNAL | The dispatch itself threw — a worker module failed to load, for instance; message carries the full cause chain, not just the top-level error |
Other dev-loader failures — no worker modules under src/workers, for instance — also return 500, with their own HSX_E_DEV_* code and a hint. Any other path gets a 404 whose error names the three endpoints.
The same dispatch against a deployed Worker
A deployed HS-X Worker serves the same dispatch at POST /capabilities/<id>/invoke on its own origin — that's where install tokens live, so handlers using ctx.hubspot run against the real portal. hs-x dev invoke <id> --remote resolves the origin for you; from raw HTTP, hit the Worker URL directly:
curl -s -X POST https://<your-worker>.workers.dev/capabilities/tag-high-value-deals/invoke \
-H 'content-type: application/json' \
-d '{ "input": { "threshold": 25000 }, "install": { "id": "hubspot-app:<appId>:portal:<portalId>", "portalId": "<portalId>", "state": "active", "config": {} } }'