Operate
Logs, metrics, egress, disk, and a service's whole life.
Status
GET /api/services → [{name, url, status, ...}]
GET /api/services/<name> → status, active deployment, bindings, usage, quota, process
POST /api/services/<name>/stop → a stopped service answers 503 and runs no jobs
POST /api/services/<name>/start
DELETE /api/services/<name> → deletes the service and all its dataA service is running (it serves its code; the process sleeps when idle and
starts on the next request), stopped or not_deployed.
Logs
GET /api/services/<name>/logs?limit=200 → [{id, ts, source, level, message, deployment}]
GET /api/services/<name>/logs?after=<id>&wait=30 → waits up to 30 s for newer entriesEach entry has a source:
| Source | |
|---|---|
request | One line per request: method, path, status, duration |
console | console.* output and uncaught exceptions |
fetch | Outbound requests |
runtime | The process: starts, stops and why, restarts, failed deployments, workerd's errors |
Each service keeps 5,000 entries and 7 days.
Metrics
GET /api/services/<name>/metrics?period=24hRequests, 5xx answers, exceptions, p50 and p95 latency and outbound
requests, per minute, for 1h, 24h, 7d or 30d. The list of services
takes ?period= too, and adds each one's traffic.
Renaming
A service's name is its address. Renaming it keeps its data, versions, logs and settings; it answers at the new address at once, and no longer at the old one:
PATCH /api/services/<name> {"name": "new-name"}Services that call it through a service:<name> binding need deploying
with the new name.
Egress
Outbound fetch and connect() reach public internet addresses only:
never loopback or private networks, checked after DNS. Each service may reach
any host (the default), a list of hosts, or none:
PATCH /api/services/<name> {"egress": ["api.example.com", "*.github.com"]}A refused request answers 403 and a failed one (DNS, connection, timeout)
502, both with an X-Zeroko-Egress header (blocked or error) and the
reason in the body. Each one is in the logs.
Outbound WebSockets (fetch with Upgrade: websocket) work as any
request. Sockets from connect() are plain TCP: TLS on them
(secureTransport) isn't available yet, so use fetch for HTTPS.
Rate limits
A service can hold each client, by its address, to a number of requests a
second, with bursts of twice as many; past it, 429 with Retry-After.
Calls from other services aren't counted. A client's address is the last
of X-Forwarded-For when the request comes from a private or loopback
address (a proxy of one's own in front), and the connection's otherwise.
PATCH /api/services/<name> {"rate_limit": 20}0 uses the runner's rate_limit (none unless set), and -1 none for
this service.
Disk
A service's databases and files share one quota:
GET /api/services/<name>/storage → [{type, binding, bytes, count, bound}]
PATCH /api/services/<name> {"disk_mb": 2048}
DELETE /api/services/<name>/storage/<type>/<BINDING> → empties one storeExport and import
GET /api/services/<name>/export → all its data as .tar.gz
POST /api/services/<name>/import (the .tar.gz) → replaces the stores it holdsAn export is consistent while the service runs. An import restarts the service and replaces each store the archive holds; the others stay.