moxzi
Docs / Server / The HTTP API

The HTTP APIalpha

What endpoints exist, and what do they take and return?

moxzid speaks plain HTTP/1.1 with raw Candid request and response bodies — there is no agent envelope, no CBOR, and no /api/v2. The constraint that catches people first: its own endpoints are matched on the exact request target, so /metrics?x=1 is a 404 and /health?x=1 is not treated as open. Only gateway paths receive the query string, which they forward to the actor as part of url.

Every endpoint#

<actor> is a manifest name or a hex principal, everywhere it appears.

MethodPathPurposeAuth
GET/healthliveness: the process is upopen
GET/health/readyreadiness: manifest installed and journals replayedopen
GET/versionversion and git shaopen
GET/the inspector dashboard, baked into the binary (with --web this path is the site instead)open
GET/inspectorthe same dashboard, at a fixed pathopen
GET/metricsPrometheus exposition texttoken
GET/inspectone JSON snapshot of every actortoken
GET/actorsname/id list of everything installedtoken
POST/call/<actor>/<method>deliver an update message. At-most-once is the client's job: there is no invocation-ID deduplication yet, so a request retried after a timeout may execute the mutation twice — retry queries freely, retry updates only when the method is idempotenttoken. x-caller: <principal> sets msg.caller for this message (default: anonymous); malformed is a 400
POST/query/<actor>/<method>deliver a non-replicated querytoken
POST/install/<name>install a new actor; body is the wasm moduletoken
POST/upgrade/<actor>replace code, keep state; body is the module or empty. A changed stable signature is pre-flighted through moxzi stable-compatible: a proven-incompatible upgrade is refused with the reasons (M0169/M0170/M0216); ?force=true skips the pre-flight, and the RTS's own memory check still has the final wordtoken
GET/cycles/<actor>read the remaining compute allowancetoken
POST/cycles/<actor>grant more; body is a decimal amounttoken
GET/logs/<actor>the actor's debug output, taken and clearedtoken
GET/history/<actor>the recent-message ring, as JSONtoken
GET/events/<actor>the same debug output as a live SSE streamtoken
GET/mops/closure/<entry>a source file's full dependency closure (needs --project)token
any/site/<actor>/<path>the HTTP gateway into that actor's http_requestopen
anyany other path, with --web <actor>the same gateway, at the rootopen
OPTIONSany non-gateway pathCORS preflight, answered 204 before authopen

Every reply carries moxzid-version and Access-Control-Allow-Origin: *. Anything unmatched is 404 not found.

Calling an actor#

Body in and body out are raw Candid. The empty argument is the six bytes DIDL\x00\x00; an empty body is not the same thing.

printf 'DIDL\x00\x01\x7d\x05' > nat5
curl -s -H "Authorization: Bearer $T" --data-binary @nat5 \
  http://127.0.0.1:7000/call/counter/add | xxd -p
# 4449444c00017d05

The reply's Content-Type is application/octet-stream and the bytes are not decoded server-side: moxzid does not know the actor's interface and does not guess.

StatusMeaning
200the actor replied; the body is the Candid reply
400the actor trapped or rejected (body is the runtime's message), or the path lacked /<method>
402the actor's cycle allowance is dry — top it up, this is not a bad request
404no such actor
413the body exceeded the cap, refused before it was read
500the actor thread produced no reply
503still recovering (starting: <note>), or the actor thread is gone

/query/ is the only route that can reach a composite query. A query's writes are rolled back and it is never journalled.

Install, upgrade, cycles#

RequestBody inBody out
POST /install/<name>the wasm modulethe new actor's hex id and a newline
POST /upgrade/<actor>the wasm module, or empty to re-read the manifest's fileupgraded
GET /cycles/<actor>the remaining balance as decimal text
POST /cycles/<actor>a decimal amount as textthe new balance as decimal text

Install over an existing name is refused with 400 — installing would discard state, and upgrading is the verb that keeps it. A body-less upgrade of an actor created at runtime is also 400: there is no recorded path to re-read, so the module must come in the body.

JSON shapes#

GET /actors:

[{"name":"counter","id":"9c1329bdc670cfa16f66e0afb34959c00481d47b02f1a7143b62a29103"}]

GET /inspect{"version": …, "actors": [ … ]}, each actor carrying id, name (null for runtime-created actors), cycles (a decimal string), heap_bytes, messages, traps, methods ({"name","kind"}, kind being update or query, with the runtime's @/__motoko plumbing filtered out) and interface, the Candid service text read from the module's own candid:service section.

GET /history/<actor> — an array of ring entries, newest last:

{"seq":2,"time_ns":1787342315895685000,"kind":"call","method":"add","caller":"04",
 "arg_len":8,"arg_preview":"4449444c00017d05","ok":true,"error":null,"reply_len":8}

kind is install, call, query or upgrade; arg_preview is bounded, never a whole blob. Ring size is --history N (default 50, 0 disables).

GET /mops/closure/<entry>{"entry", "pkgSource", "unresolved", "files":[{"vfs","bytes"}]} with bytes base64. Entry paths are confined to the --project directory; one that escapes it is 403, and a missing --project is 404.

GET /metrics — Prometheus text (text/plain; version=0.0.4) with moxzid_uptime_seconds, moxzid_http_requests_total{endpoint=…} by endpoint class, moxzid_actor_messages_total / moxzid_actor_traps_total per actor as the caller addressed it, moxzid_actor_cycles / moxzid_actor_heap_bytes per actor by hex id, and moxzid_state_bytes when -s is set.

The gateway and the event stream#

A gateway request is encoded as the IC's standard HttpRequest record — {method; url; headers; body; certificate_version} — and delivered to the actor's http_request query first, exactly as a boundary node does. If the actor answers upgrade = ?true, the same request is re-delivered to http_request_update as an update. The actor's status code, headers and body pass through; moxzid overrides only Content-Length, Connection and Transfer-Encoding, and never forwards the Authorization header to the actor.

SituationResponse
no such actor404
the actor has no http_request502 Bad Gateway, with the runtime's message
the reply does not decode as an HttpResponse502 Bad Gateway
certificate_versionalways sent as null; there is nothing to certify off-chain
streaming_strategy presentaccepted, logged, not followed — only the first chunk is served

GET /events/<actor> holds the connection open and emits data: lines as the actor prints, with retry: 2000 and a : keepalive comment roughly every 15 seconds. It drains the same take-and-clear ring as /logs/<actor>, so the two compete for lines rather than each seeing a copy. It is ops surface, not website: without a token it is 401.

Next#

On this pageEvery endpointCalling an actorInstall, upgrade, cyclesJSON shapesThe gateway and the event streamNext