Skip to main content

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:

CodeMeaning
400Invalid JSON body or bad request
401Missing or invalid bearer token
403License required for this endpoint
404Unknown endpoint
409Conflict (e.g., warmup already running)
500Internal 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.