Admin API
The Admin API runs on a separate port (default 127.0.0.1:4001) and exposes endpoints for health checks, cache management, rendering, and metrics.
Authentication
Three endpoints are always unauthenticated — GET /health, GET /ready and
GET /startup — because a kubelet probing them holds no credential.
Everything else requires a bearer token.
The token is not optional in practice: leave admin.bearer_token unset and
PRISM generates one at startup and logs it, and it refuses to start at all
if the admin API is bound to a non-loopback address without either a token or
an explicit insecure_no_auth = true. There is no configuration in which the
mutating endpoints are quietly open.
[admin]
address = "127.0.0.1:4001"
bearer_token = "your-secret-token"
Include the token in the Authorization header:
Authorization: Bearer your-secret-token
Token comparison uses constant-time equality to prevent timing attacks.
Endpoints
GET /health
Returns the health status of the PRISM instance. No authentication required.
Returns 200 when Chrome is responsive, 503 when unhealthy.
curl http://127.0.0.1:4001/health
{"status": "ok"}
Unhealthy response (503):
{"status": "unhealthy", "reason": "chrome not responding"}
GET /status
Returns runtime statistics including uptime, cache state, pool size, and render counts.
curl -H "Authorization: Bearer your-secret-token" \
http://127.0.0.1:4001/status
{
"uptime_secs": 3600,
"cache": {
"entries": 1482,
"memory_bytes": 52428800
},
"pool": {
"size": 8,
"available": 5,
"crashes": 0
},
"renders": {
"total": 4521,
"active": 3,
"queue_depth": 0
}
}
GET /metrics
Returns all metrics in Prometheus text exposition format (text/plain; version=0.0.4).
curl -H "Authorization: Bearer your-secret-token" \
http://127.0.0.1:4001/metrics
# HELP prism_requests_total Total requests by cache status
# TYPE prism_requests_total counter
prism_requests_total{status="hit"} 3200
prism_requests_total{status="miss"} 1321
...
See the Metrics Reference for a full list of exported metrics.
POST /purge/url
Purge a specific URL from the render cache.
curl -X POST \
-H "Authorization: Bearer your-secret-token" \
-H "Content-Type: application/json" \
-d '{"url": "/products/widget-pro"}' \
http://127.0.0.1:4001/purge/url
{"purged": true}
The purged field is true if the URL was found and removed, false if it was not in the cache.
POST /purge/pattern
Purge all cached URLs matching a glob pattern.
A pattern beginning with / is resolved against the configured origin, because
entries are keyed by the rendered URL rather than by path. Give a pattern that
starts with a scheme, or with *, to match across origins.
curl -X POST \
-H "Authorization: Bearer your-secret-token" \
-H "Content-Type: application/json" \
-d '{"pattern": "/products/*"}' \
http://127.0.0.1:4001/purge/pattern
{"purged_count": 47}
POST /purge/all
Purge the entire render cache.
curl -X POST \
-H "Authorization: Bearer your-secret-token" \
http://127.0.0.1:4001/purge/all
{"purged_count": 1482}
POST /render
Force-render a URL through the Chrome pipeline and store the result in cache. Requires a valid license.
curl -X POST \
-H "Authorization: Bearer your-secret-token" \
-H "Content-Type: application/json" \
-d '{"url": "/products/widget-pro"}' \
http://127.0.0.1:4001/render
Success response:
{
"status": "ok",
"cache_status": "MISS",
"render_time_ms": 1250,
"html_bytes": 48230
}
Error responses:
- 403 — No valid license:
{"error": "rendering unavailable — license required"}
- 400 — Invalid URL or path traversal attempt
- 500 — Render failure (Chrome crash, timeout, etc.)
The url field accepts either a path (/page) or a full URL (https://example.com/page). Full URLs are parsed and only the path and query string are used.
POST /warmup
Start a cache warmup job by fetching and rendering all URLs from a sitemap. Requires a valid license.
curl -X POST \
-H "Authorization: Bearer your-secret-token" \
-H "Content-Type: application/json" \
-d '{"sitemap_url": "https://example.com/sitemap.xml"}' \
http://127.0.0.1:4001/warmup
{"status": "warmup started"}
Returns 202 Accepted on success, 409 Conflict if a warmup is already in progress.
GET /warmup/status
Check the progress of a running warmup job.
curl -H "Authorization: Bearer your-secret-token" \
http://127.0.0.1:4001/warmup/status
{
"status": "running",
"total": 500,
"processed": 237,
"errors": 3,
"elapsed_secs": 45.2,
"error_message": null
}
The status field can be idle, running, complete or failed. The enum serialises Complete as complete, not completed, and failed means the warmup ran and gave up — distinct from idle, which means it never started.
Error Handling
All error responses use JSON:
{"error": "description of what went wrong"}
Common status codes:
| Code | Meaning |
|---|---|
| 400 | Invalid JSON body or bad request |
| 401 | Missing or invalid bearer token |
| 403 | License required for this endpoint |
| 404 | Unknown endpoint |
| 409 | Conflict (e.g., warmup already running) |
| 500 | Internal server error |
Request bodies are limited to 1 MB.
GET /explain
Answers why did PRISM do that for one URL, without rendering, fetching or touching the cache.
curl -H "Authorization: Bearer $TOKEN" \
"http://localhost:4001/explain?url=/products/widget"
The question this exists for is the one that has cost days of back-and-forth on live deployments: why will my page not cache? Until 1.6.0 the honest answer was a header value and a shrug — the decision was an if-else chain producing string literals, so nothing could report on it. The render decision and the cache decision now carry typed reasons, and this reads them.
Three answers, asked separately
routing — would a request for this path be rendered, and if not, which
condition stopped it. Add user_agent=... to ask as a specific crawler:
without one the answer assumes a human, which is why not_bot is the usual
reply in the default bot-only mode. Add authenticated=1 to ask as a request
carrying credentials.
cache — what is stored right now: its age, its lifetime, whether it still
counts as fresh, whether grace may serve it after that, and every variant
stored under the same canonical URL. Read without promoting the entry or
counting a hit — a diagnostic that moves the numbers it reports would raise the
hit rate of anyone debugging a low one.
policy — a hypothesis. Give it status and optionally cache_control as
your origin would send them, and it answers whether the result would be stored,
for how long, what would be announced downstream, and if it would not be
stored, why not, in a sentence:
curl -H "Authorization: Bearer $TOKEN" \
"http://localhost:4001/explain?url=/products/widget&status=200&cache_control=public,%20max-age=0"
{
"policy": {
"would_store": false,
"reason": "origin_lifetime_is_zero",
"why": "the origin gave this a lifetime of zero, so it is stale on arrival and reusing it would need a revalidation PRISM cannot perform"
}
}
reason is a stable token safe to key a dashboard on; why is the sentence for
a person. The four are origin_forbids_shared_caching,
origin_lifetime_is_zero, status_needs_explicit_permission and
error_caching_disabled.
Omit status and the policy section is null — PRISM will not guess what
your origin would have said.