Authentication
Tokens, the Host and Origin checks, and calling the API from a web page.
The API accepts requests only from this Mac, addressed to it by name, carrying its token. This page explains each check and the errors you get when one fails.
Bearer token
Send the token in the Authorization header of every request to a /v1 path:
Authorization: Bearer <token>GET /openapi.json is the only path that works without it.
Token lifecycle
- Created at start. Without
SUPERPDF_API_TOKEN,superpdf servemakes a random token (32 bytes, base64url) and prints it. - Your own value. Set
SUPERPDF_API_TOKEN(at least 32 characters) before starting, and the API uses it instead. Your scripts read the same variable.openssl rand -hex 32makes a good one. - Valid until the API stops. There’s no expiry while it runs. A token made by the API is gone when it stops; the next start makes a new one.
- Rotate by restarting. Stop the API and start it with a new value. Requests with the old token then get
401. - Compared in constant time, so response timing doesn’t reveal how much of a guess was right.
Keep the token out of source code and version control. Any program on your computer that has it can use the allowed folders.
What is checked, in order
| # | Check | If it fails |
|---|---|---|
| 1 | The Host header is 127.0.0.1:<port> or localhost:<port>. | 403 forbidden |
| 2 | An Origin header, if present, is one you allowed with --allow-origin. | 403 forbidden |
| 3 | The path is /openapi.json or starts with /v1/. | 404 not_found |
| 4 | The bearer token matches. | 401 unauthorized |
| 5 | The path and method exist, and the body is JSON under 1 MB with valid fields. | 404, 405, 413, 415, 400 or 422 |
401: no token, or the wrong one
The answer says which, and carries a WWW-Authenticate header as HTTP requires:
curl -i http://127.0.0.1:7341/v1/toolsHTTP/1.1 401 Unauthorized
Content-Type: application/problem+json
WWW-Authenticate: Bearer realm="superPDF"
X-Request-Id: dbd91301-46c7-460d-95e3-75b8a4da4acd
{
"type": "urn:superpdf:problem:unauthorized",
"title": "Authentication required",
"status": 401,
"detail": "Send the token printed when superpdf serve started, as Authorization: …",
"instance": "urn:superpdf:request:dbd91301-46c7-460d-95e3-75b8a4da4acd",
"code": "unauthorized",
"request_id": "dbd91301-46c7-460d-95e3-75b8a4da4acd"
}403: the wrong host name or website
A request addressed to any other host name is refused before the token is checked. This is what stops a website from reaching the API by pointing its own domain name at 127.0.0.1:
curl -i http://127.0.0.1:7341/v1/tools \
-H "Authorization: Bearer $SUPERPDF_API_TOKEN" \
-H "Host: superpdf.example.com"HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
{
"type": "urn:superpdf:problem:forbidden",
"title": "Request not allowed",
"status": 403,
"detail": "Host superpdf.example.com is not allowed. Use http://127.0.0.1:7341.",
"instance": "urn:superpdf:request:943d5e1b-b561-4ad2-abee-a8d46e27f1ac",
"code": "forbidden",
"request_id": "943d5e1b-b561-4ad2-abee-a8d46e27f1ac"
}Calling from a web page
Browsers send an Origin header, and the API refuses every origin you haven’t allowed. To call it from your own web app, for example one running on http://localhost:5173, start the API with that origin:
superpdf serve --allow-dir ~/Documents/superPDF --allow-origin http://localhost:5173Requests from that origin then get CORS headers, and preflight requests are answered for GET and POST with the Authorization and Content-Type headers:
curl -i http://127.0.0.1:7341/v1/tools \
-H "Authorization: Bearer $SUPERPDF_API_TOKEN" \
-H "Origin: http://localhost:5173"HTTP/1.1 200 OK
Content-Type: application/json
Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Expose-Headers: Location, X-Request-Id
Vary: OriginThe page still has to send the token. Use this for tools that run on your own Mac, never for a site other people visit.
Webhook secret
Webhooks are signed with a second value, SUPERPDF_WEBHOOK_SECRET: whsec_ followed by 32 random bytes in base64. Like the token, it’s new on every start unless you set it. See Verify events.