Skip to content

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:

Header
Authorization: Bearer <token>

GET /openapi.json is the only path that works without it.

Token lifecycle

  • Created at start. Without SUPERPDF_API_TOKEN, superpdf serve makes 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 32 makes 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

#CheckIf it fails
1The Host header is 127.0.0.1:<port> or localhost:<port>.403 forbidden
2An Origin header, if present, is one you allowed with --allow-origin.403 forbidden
3The path is /openapi.json or starts with /v1/.404 not_found
4The bearer token matches.401 unauthorized
5The 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:

Terminal
curl -i http://127.0.0.1:7341/v1/tools
Response
HTTP/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:

Terminal
curl -i http://127.0.0.1:7341/v1/tools \
  -H "Authorization: Bearer $SUPERPDF_API_TOKEN" \
  -H "Host: superpdf.example.com"
Response
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:

Terminal
superpdf serve --allow-dir ~/Documents/superPDF --allow-origin http://localhost:5173

Requests from that origin then get CORS headers, and preflight requests are answered for GET and POST with the Authorization and Content-Type headers:

Terminal
curl -i http://127.0.0.1:7341/v1/tools \
  -H "Authorization: Bearer $SUPERPDF_API_TOKEN" \
  -H "Origin: http://localhost:5173"
Response
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: Origin

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