ServicesOperate
Services

Operate

Logs, metrics, egress, disk, and a service's whole life.

Status

http
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 data

A service is running (it serves its code; the process sleeps when idle and starts on the next request), stopped or not_deployed.

Logs

http
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 entries

Each entry has a source:

Source
requestOne line per request: method, path, status, duration
consoleconsole.* output and uncaught exceptions
fetchOutbound requests
runtimeThe process: starts, stops and why, restarts, failed deployments, workerd's errors

Each service keeps 5,000 entries and 7 days.

Metrics

http
GET /api/services/<name>/metrics?period=24h

Requests, 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:

http
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:

http
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.

http
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:

http
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 store

Export and import

http
GET  /api/services/<name>/export   → all its data as .tar.gz
POST /api/services/<name>/import   (the .tar.gz) → replaces the stores it holds

An export is consistent while the service runs. An import restarts the service and replaces each store the archive holds; the others stay.