Skip to main content
    Skip to content
    NorruvaDeveloper Docs
    Sandbox · verified 2026-07-29
    This page
    Whole docs
    Machine formats

    PlaygroundGet API keys
    IntroductionQuickstartAuthenticationErrors & conventionsSandbox & environments
    Entity modelProducts & categoriesPassports & versionsDigital Link & resolutionCompliance & regulationsWebhooks & eventsAutoID print loopImport jobsObservability & audit
    OverviewAuth & API keysProductsCompliance & regulationsPassportsResolver & publicWebhooksPrint jobs & devicesImport / bulkObservabilityBeyond happy path
    Integration playbookWebhook receiver guideRun a print deviceDeviations & gotchas
    EN 18222 API methodsAnnex ZA — ESPR correspondence
    Docs/Get started/Errors & conventions

    Errors & conventions

    Assert these in every integration. The platform's promise is that refusals are self-diagnosing — the error body alone should tell a misconfigured client what to fix.

    Universal request conventions

    ConventionContract
    AuthenticationAuthorization: Bearer <api-key-or-oauth-token> on every call. x-api-key-shaped headers also pass the edge.
    IdempotencyIdempotency-Key: <uuid> on mutating POSTs. Required on publish-dpp and oauth/token; accepted & recommended elsewhere.
    Content typeapplication/json unless the endpoint says multipart.
    ErrorsTyped envelope { error: { code, message, details? } }.
    CorrelationOptional X-Correlation-Id flows route → use case → audit log.

    The error envelope

    JSON
    {
      "error": {
        "code": "API_SCOPE_DENIED",
        "message": "Missing required scope for this operation.",
        "details": { "required": "products:create", "available": ["products:view"] }
      }
    }

    Error catalogue

    The codes an integrator must branch on. Two are documented deviations — asserted as-is until the underlying contract changes.

    StatusCodeTrigger
    401—Bad/garbage or missing credential; also role-level permission denials.
    403API_SCOPE_DENIEDGood credential, missing scope. Carries { required, available }.
    403FORBIDDENOut-of-scope on publish-dpp — different auth wrapper. Deviation.
    403SANDBOX_EXPIREDSandbox tenant past expiry (both conditions required).
    403GUARD_REJECTEDIllegal passport lifecycle transition (e.g. EDIT a published passport).
    400VALIDATION_ERRORField validation — names the field/bound (e.g. 21-char serial, description < 10).
    422EXTENSIONS_REQUIREDMissing extensions on product create.
    422SCHEMA_NOT_FOUNDCategory has no effective schema (see the category-hint gotcha).
    400INVALID_GTIN / INVALID_SERIALBad check digit / over-long serial at the resolver.
    404PRODUCT_NOT_FOUNDUnknown-but-valid GTIN at the resolver — names gtin/serial/host.
    404—Draft/nonexistent public passport; cross-tenant fetch. Authz runs before existence — no id enumeration.
    410SERIAL_VOIDEDVoided serialized item — returns a tombstone, zero passport content.
    422UNKNOWN_EVENT_TYPEWebhook subscription to a non-catalogue event name.
    422PASSPORT_NOT_PUBLISHABLERegistry broadcast attempted on a passport whose lifecycle state is not publishable.
    409DUPLICATE_GTINA second model-granularity product with the same GTIN in this tenant — (company_id, gtin) is unique.
    409ALREADY_REVOKEDRevoking a key that is already revoked. Note that rotate revokes the old id for you and returns a NEW data.id — revoke that one.
    409—Conflict: re-void, same-key different-body idempotency, scheme-lock change, illegal passport command transition.
    503COMPLIANCE_EVALUATION_UNAVAILABLEBattery only, and deliberate: the governed battery obligation pack is quarantined (ATLAS-F017), so no battery compliance verdict may be issued. This is NOT_EVALUATED — a fail-closed refusal to judge, never a compliance failure. Every other category evaluates normally; start on electronics.
    503REGISTRY_UNAVAILABLENo external registry (CATENA-X / GS1 / EBSI) is configured — the normal sandbox answer to POST /passports/{id}/publish.
    503ANCHORING_UNAVAILABLEBlockchain not configured in this environment (ENABLE_BLOCKCHAIN=false) — the normal sandbox answer.
    500ANCHORING_FAILEDA real anchoring fault on a configured chain. Distinct from ANCHORING_UNAVAILABLE (503): only this one means something is wrong.
    405—Wrong verb on a route that does not support it. (Product update accepts PATCH — canonical, PRD F1 — and PUT as an alias.)
    302—Resolver redirect to /p/{uid} (the public uid = the product id).
    202—Async accepted (generation, imports).
    ◆
    Rate limiting is enforced (PRD F10): per-tier budgets on a Redis-backed limiter. Over budget → 429 RATE_LIMITED with X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset and Retry-After. Back off on Retry-After.

    Idempotency semantics

    • Same key + same body → the first result is replayed, with no duplicate side effects.
    • Same key + different body → a typed conflict.
    • Enforcement is per-route: publish-dpp and oauth/token require a key; POST /products without one still returns 201.
    Was this page helpful?
    Thanks — noted.Feedback goes to the docs team by email.
    ← PreviousAuthenticationNext →Sandbox & environments
    On this page
    Universal request conventionsThe error envelopeError catalogueIdempotency semantics
    Norruva DPP API · sandbox developer docsGenerated 2026-07-29 · PRD-aligned (TSC roadmap rev 2) · statuses reflect E2E-verified sandbox behaviour — not marketing