Pulp Engine Document 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

VariableScopeRoute access
API_KEY_ADMINadminFull access — templates, assets, render, preview
API_KEY_RENDERrenderPOST /render, POST /render/html only
API_KEY_PREVIEWpreviewPOST /render/preview/* only

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

Authorization matrix

Route groupPathsRequired scope
PublicGET /health, GET /assets/:filename(no auth)
RenderPOST /render, POST /render/htmlrender or admin
PreviewPOST /render/preview/html, POST /render/preview/pdfpreview or admin
Template managementAll /templates/* routesadmin
Asset managementPOST /assets/upload, GET /assets, DELETE /assets/:idadmin
All other authenticated routesAny route not matched aboveadmin

Error semantics

ResponseMeaning
401 UnauthorizedHeader missing, or key not recognised
403 ForbiddenKey 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

FileChange
apps/api/src/config.tsAdded API_KEY_ADMIN, API_KEY_RENDER, API_KEY_PREVIEW env fields
apps/api/src/plugins/auth.plugin.tsFull rewrite — credential map, requiredScopes() helper, startup validation
apps/api/src/__tests__/auth-scopes.test.tsNew — comprehensive scope tests
apps/api/src/__tests__/render-preview.test.tsUpdated env stubs from API_KEY to API_KEY_ADMIN
.env.exampleDeprecated API_KEY, added new vars with documentation
README.mdUpdated production security section
docs/api-guide.mdUpdated authentication section with scoped model, matrix, migration, editor note
docs/deployment-guide.mdUpdated env vars table, added § 8 Migration
docs/runbook.mdUpdated checklist and smoke test curl commands
docs/release-v0.13.0.mdThis 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.