Authentication
The server authorizes a request by a credential header or by a signed URL.
| Method | Flag | Client sends |
|---|---|---|
| Basic | --server-basic-auth | Authorization: Basic <base64 of user:password> |
| Token | --server-token-auth | Authorization: Bearer <token> |
| Signed URL | --server-signed-url | expires and signature query parameters |
A missing or wrong credential answers 401. With both Basic and token set, only the token authorizes.
Precedence
A valid credential header authorizes outright, and a wrong one is rejected without checking a signature. A request with no credential header is checked for a signature when signed URLs are on.
Signatures apply to GET /, DELETE / and GET /assets. POST / and /mcp authorize by credential header only, so a server with signed URLs and no Basic or token auth accepts any POST and any MCP request.
Signed URLs
Sign the request’s query parameters, expires included:
- Set
expiresto a Unix time in seconds. - Sort the parameters by key, then by value, and form-encode them.
- Compute HMAC-SHA256 of that string with the secret, as lowercase hex.
- Append it as
signature.
import hashlib, hmac, time, urllib.parse
params = {
"url": "https://zshot-cli.com/example_assets/zrm/",
"output_type": "png",
"expires": str(int(time.time()) + 300),
}
query = urllib.parse.urlencode(sorted(params.items()))
signature = hmac.new(b"SECRET", query.encode(), hashlib.sha256).hexdigest()
print("http://127.0.0.1:3000/?" + query + "&signature=" + signature)| Response | Reason |
|---|---|
401 | no signature |
403 | the signature does not match, expires is missing or past, or expires is beyond --server-signed-url-max-age |
The server signs the asset links in a render response with the same secret; see Additional Assets.
Metrics Endpoint
--server-prometheus-auth sets a Basic credential that is the only one /metrics accepts. Without it, /metrics takes the main server’s credentials, and is open when the server has none.