Deviations & gotchas
These are documented as the current contract, not bugs to wait on. Code around them; they will only change with a versioned update.
Auth & resolution
- Resolver 401 to anonymous scanners.
GET /api/v2/01/{gtin}/21/{serial}currently returns401when noAuthorizationheader is present. SendAuthorization: Bearer edge-gate-placeholderuntil fixed. publish-dppreturns403 FORBIDDEN, notAPI_SCOPE_DENIED, on a missing scope — a different auth wrapper. Branch on both.- Public passport is edge-gated though treated as public; draft/unpublished uids return
404 "not found or not published". - No refresh tokens. OAuth issues access tokens only; request a fresh one on expiry. Cross-tenant token binding fails closed.
POST /compliance/export/evidence-packis not callable with an API key. It passes its owncompliance:viewgate, then delegates to a server action wrapped inwithAuthentication(), which requires a Supabase user session. An API key has none, so the action throws and the route answers500 "Authentication required"— not a typed 401/403. Use the dashboard export until the route calls its use case directly. Pinned by the journey probe (Step 14) so the day it starts returning 2xx, the probe fails and this note gets deleted.- The credentials surface spans two scope families that are not aliases.
POST /credentials/issue,GET /credentialsandPOST /credentials/commandsare gated oncompliance:manage/compliance:view;POST /credentials/verifyandPOST /credentials/{id}/revokeare gated oncredential:verify/credential:manage. The alias table bridges plural and synonym forms within a family (credentials:read→credential:read,credential:manage→credential:write) but never betweencredential:*andcompliance:*. A key minted only from thecredential:*family gets403 API_SCOPE_DENIEDon the first three. Mint keys covering both families.
Data & lifecycle
- Category hints can advertise unresolvable categories (e.g.
dpp,vehicle) that then422 SCHEMA_NOT_FOUND. Confirm with a real create. - Compliance is fail-closed: absent data →
Indeterminate(score 0), neverFullyCompliant. Findings cite obligations from the requirements SSOT (ESPR_ART<N>_<TOPIC>); the bare…/compliance/esprpath is a deliberate410tombstone — usePOST /compliance/validate. passports/{id}/validationsreturns the anchored validation chain plus the derivedreadyToPrintflag (= validated ∧ published). An empty chain just means nothing is anchored yet —readyToPrintstaysfalseuntil a validation is anchored AND the passport is published.as-oftakes an ISO 8601datequery param and returns the version valid at that instant; a date before the first version is a typed404 NOT_FOUND, not an empty object.- Product ≠ Passport and publish ≠ register. Registering the resolvable Digital Link is the separate
publish-dppstep (dpp_identifiers0 → 1). - Serial split: alphanumeric serials may yield a valid Digital Link/QR while EPC carrier generation refuses; numeric serials must fit ≤ 238.
Scope of the current build
Carrier batch allocation, EPCIS capture, and the print-confirmation loop (carriers/print-status + the carrier.generated / print.confirmed / print.failed events) are live HTTP surfaces. Print-job orchestration and label verification (verify-label) are still missing at the HTTP layer — print execution belongs to the printer partner. Rate limiting is enforced (per-tier budgets, 429 RATE_LIMITED). Check the Beyond happy path reference before depending on anything outside the core journey.