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>,v1=<hex> and Webhook-Id: <delivery_id> (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
- HTTP: Bearer Auth
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_* |