Skip to main content
Version: 1.0.0-phase1

Traceable Headless DPP Integration API

Author Digital Product Passports (DPPs) programmatically — an external system (ERP / PLM, e.g. Odoo) creates a draft, fills it, attaches evidence, checks the publish gate, and submits it for review, WITHOUT the Traceable operator UI.

Regulatory boundary (read first)

This API is a DATA-INGESTION surface. Every state-changing operation terminates at Submitted (for review). The API can NEVER publish a DPP. Publication is a deliberate human act performed by an operator in the Traceable UI, and the published record carries that natural person's attribution (ESPR / EU 2023/1542 Art. 77). There is no dpp:publish scope in Phase 1 and no endpoint that can set a product to Approved/Published. A submitted DPP enters the verifier/operator review queue.

Availability

Disabled by default. The endpoints return 503 FEATURE_DISABLED unless API_V1_PRODUCTS_ENABLED=true is set on the target environment.

What is NOT covered here

POST /api/v1/products/{id}/bms-update (BMS telemetry ingestion) is a separate surface documented in bms-v1.yaml.

Testing

The authentication chain (signature tamper, replay, timestamp-skew, cross-tenant isolation, and fail-closed rate limiting) is covered by an automated test suite. Detailed assurance status is available to onboarding partners under NDA.

Response headers (every response)

Every response carries x-traceable-api-version (this contract's version) and x-traceable-request-id (a correlation id, also on the audit trail). Every authenticated response additionally carries RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset (seconds), and a 429 adds Retry-After (seconds). Responses served to an API key whose last rotation (or mint) is older than 90 days carry Warning: 299 advising rotation (soft at 90 days, urgent wording at 365 days).

Sandbox mode

Keys are live (pk_live_*) or sandbox (pk_test_*). Sandbox-authored data is invisible to live keys (and vice versa), never appears on a public passport, and has its own rate-limit quota. A sandbox request MUST send X-Traceable-Mode: test; every authenticated response echoes the mode in the same header. The idempotency replay window for key mint/rotate is 1 hour (a retried mint returns the SAME key instead of creating a second).

Outbound webhooks — verifying deliveries

Deliveries POST a canonical JSON body with headers Webhook-Signature: t=<unix_seconds&gt;,v1=&lt;hex&gt; and Webhook-Id: &lt;delivery_id&gt; (your retry idempotency handle). Verify EXACTLY like this (never compare with ===):

const expected = crypto.createHmac("sha256", signingSecret) .update(${t}.${rawBody}).digest("hex"); const ok = crypto.timingSafeEqual( Buffer.from(v1, "hex"), Buffer.from(expected, "hex"));

Reject when |now - t| > 300 seconds (replay window). The signing secret is shown ONCE at endpoint creation/rotation. Failed deliveries retry on a 30s/2m/10m/1h/6h/24h ladder, then dead-letter (re-queue them via the redeliver endpoint); 20 consecutive 5xx/timeouts auto-disable the endpoint (re-enable via PATCH status=active). NOTE: the bulk-revoke kill-switch can revoke the calling admin key itself — revoke it LAST in incident runbooks.

Authentication

Authorization: Bearer pk_live_…. The key determines the tenant (companyId) and the granted scopes. Writes ALSO require the HMAC signature header and a per-key signing secret (provisioned at key mint). Scopes (exhaustive): dpp:read, dpp:gate:read, dpp:draft:write, dpp:submit, document:write, category:read, schema:read, supplier:read, dpp:withdraw, mapping:read, mapping:write, webhook:read, webhook:write. There is NO ai.* scope and NO dpp:publish scope. Two PRIVILEGED scopes exist OUTSIDE this grantable set and are minted only via the Company Portal admin-key dialog, each always in isolation: keys:admin (/api/v1/keys) and gdpr:admin (/api/v1/gdpr).

Security Scheme Type:

http

HTTP Authorization Scheme:

bearer

Bearer format:

pk_live_*