Pulp EngineDocument Rendering
Get started
Release v0.13.0

Pulp Engine v0.13.0 — Scoped API Credential Model

Summary

Replaces the single shared API_KEY with three named credential scopes. Production integrations can now hold a render-only key; template management uses a separate admin key; preview routes can be restricted to a dedicated credential. One key can no longer unlock everything.


What changed

New env vars

Variable Scope Route access
API_KEY_ADMIN admin Full access — templates, assets, render, preview
API_KEY_RENDER render POST /render, POST /render/html only
API_KEY_PREVIEW preview POST /render/preview/* only

All configured keys must be distinct strings. See the authorization matrix below.

Authorization matrix

Route group Paths Required scope
Public GET /health, GET /assets/:filename (no auth)
Render POST /render, POST /render/html render or admin
Preview POST /render/preview/html, POST /render/preview/pdf preview or admin
Template management All /templates/* routes admin
Asset management POST /assets/upload, GET /assets, DELETE /assets/:id admin
All other authenticated routes Any route not matched above admin

Error semantics

Response Meaning
401 Unauthorized Header missing, or key not recognised
403 Forbidden Key recognised but scope insufficient for this route

Previously, an unrecognised key returned 403. It now returns 401 — the server does not confirm whether a key exists in the credential map.

Startup validation

The server now rejects at startup (via buildServer() rejecting) if:

  • API_KEY is set alongside any new scoped key (ambiguous config)
  • Any two new scoped keys share the same value (duplicate)
  • NODE_ENV=production and no credentials of any kind are set

Migration from API_KEY

The legacy API_KEY is still accepted and treated as admin scope. A deprecation warning is logged at startup. It cannot coexist with the new keys.

Recommended migration:

# Before
API_KEY=my-old-secret

# After — minimal (single admin key)
API_KEY_ADMIN=my-new-admin-secret

# After — split (separate render integrations)
API_KEY_ADMIN=my-new-admin-secret
API_KEY_RENDER=my-new-render-secret

Steps:

  1. Generate new secrets.
  2. Set the new env vars and remove API_KEY.
  3. Update callers to use the appropriate key.
  4. Restart the API.

Breaking changes

None, if you migrate API_KEY to API_KEY_ADMIN.

  • Setting API_KEY alone still works (deprecated, warned at startup).
  • Setting API_KEY alongside any new key fails at startup — this is the only new hard error.
  • The 403 response for unrecognised keys is now 401 (minor semantics change).

Files changed

File Change
apps/api/src/config.ts Added API_KEY_ADMIN, API_KEY_RENDER, API_KEY_PREVIEW env fields
apps/api/src/plugins/auth.plugin.ts Full rewrite — credential map, requiredScopes() helper, startup validation
apps/api/src/__tests__/auth-scopes.test.ts New — comprehensive scope tests
apps/api/src/__tests__/render-preview.test.ts Updated env stubs from API_KEY to API_KEY_ADMIN
.env.example Deprecated API_KEY, added new vars with documentation
README.md Updated production security section
docs/api-guide.md Updated authentication section with scoped model, matrix, migration, editor note
docs/deployment-guide.md Updated env vars table, added § 8 Migration
docs/runbook.md Updated checklist and smoke test curl commands
docs/release-v0.13.0.md This file

Notes

  • admin scope covers all routes including render and preview — an admin key can be used anywhere.
  • The visual editor (VITE_API_KEY) still requires an admin-scoped key (current limitation — a narrower editor credential is planned).
  • Static asset serving (GET /assets/:filename) remains public; the PDF renderer fetches image URLs without sending an API key.