PackAuth · developers OpenAPI 3.1

API reference and playground

Every operation PackAuth exposes, the scope it needs, and a request you can actually send. This page is generated from the same registry the API is routed from, the SDKs are generated from and the OpenAPI document is built from — so it cannot describe an endpoint that is not there, and it cannot fall behind one that is.

Your key

Held for this browser tab only, never written to disk, and sent to this host alone — the playground calls dev.packauth.com/v1/…, which is the same Worker that serves api.packauth.com. Nothing here leaves your browser except the request you send.

Scopes

A key carries scopes and an operation checks its scope before it does anything else. A call never widens the key that made it: ask for an approval you do not hold and it is refused rather than escalated.

approvals:read
Read approvals and their recorded basis — scope, jurisdictions, artwork hash, evidence snapshot, decision. Separate from approvals:write because seeing who approved what is a different authority from approving.
approvals:write
Grant or withhold an approval within the scope the principal actually holds. Never widens that scope.
billing:read
Read plan, usage and meter state.
billing:write
Change plan or manage billing configuration.
counterparties:read
Read counterparties and the evidence they have supplied.
counterparties:write
Invite counterparties and manage their obligations.
evidence:read
Read evidence on file and the outstanding requests against counterparties. Separate from evidence:write because reading what a supplier owes is routine and uploading on their behalf is not.
evidence:write
Submit evidence against a counterparty obligation.
findings:read
Read findings raised by a run.
findings:write
Waive or annotate a finding. A blocking finding can only be waived by a principal holding the approval authority for that market.
manifests:read
Read packaging manifests and their per-market matrices.
manifests:write
Create and amend manifests. A manifest in an approved state cannot be amended without superseding it.
packs:read
Read rule packs and the rules inside them. Read-only: packs are authored in the repo, never through the API.
platform:read
Read across tenants as a PackAuth operator. The only scope in the system that is not bounded by a tenant, held only by operator keys, and every read it permits writes an audit row into the read tenant's own stream before the data is returned.
products:read
Read this tenant's products and their classification.
products:write
Create and amend products. Amending a classified product re-opens the checks that depended on the classification.
releases:read
Read print releases and verify one from its public reference.
releases:write
Cut a print release. Refused unless every blocking finding for the target markets is resolved or waived.
runs:read
Read compliance runs and their results.
runs:write
Start a compliance run against a manifest.

Things that are true of every operation

These were documented per-operation or nowhere, so an integrator learned each one by hitting it. Every statement here is checked against the code by dev-crosscutting — a promise about the API that the API stops keeping fails the build.

Authentication

Authorization: Bearer <key> on every operation except the 7 public ones. A key carries a fixed set of scopes; an operation refuses with 403 naming the scope it wanted. Keys are stored only as a hash — a lost key is revoked and replaced, never recovered.

Errors

Every failure is {"error": {"code", "message", "detail"}, "request_id"}. The code is stable and safe to branch on; the message is written for a person and may change. Quote the request_id when asking about one.

409 rail_blocked is the one worth handling deliberately: a policy rail refused, and failed_check names which. Its reason_code distinguishes a decision from an outage — engine_unavailable means no verdict was reached and the action was refused rather than allowed. PackAuth never fails open.

Idempotency

Send Idempotency-Key: <your id> on any write. A repeat with the same key and the same body returns the first response instead of acting twice. A repeat with the same key and a different body is refused — that is a bug in the caller, not a retry. Without the header a retried write is a second write, and for a print release that means a second certificate for one decision.

Pagination

Every list takes limit (default 50, max 500) and offset, and answers {"data": [...], "has_more": bool}. has_more is computed by fetching one row beyond the page, so it is exact rather than a guess from a full page.

Rate limits

There is no per-tenant rate limit today. Stating that plainly is more use than a number nobody enforces: an integrator who plans around an invented limit has planned around nothing. The one 429 in the API is the sandbox refusing to mint another anonymous tenant once it is full. When a limit exists it will be documented here with its headers, and this paragraph will say so.

What this page will not do

It fires reads. It will not fire a write while it points at production: a Send button next to a description of an operation that revokes an approval is one mis-click from changing a real tenant, and the copyable request above each write is the same call without that hazard. Writes become sendable when the sandbox surface exists and a mistake costs nothing.

85 operations, 7 of them public, across 34 groups. Generated from spec/registries/api.json.