Skip to main content

The headless wizard flow

Availability

Every endpoint in this flow — discovery, create, form-data ingest, readiness, traceability, components, GS1, document upload, the publish gate, submit, and withdrawal — is GA on production (app.traceable.digital) as of release v0.99.6.1. See Availability for the per-endpoint matrix.

This API exists so an integration can reproduce the entire operator wizard over HTTP. An ERP or PLM discovers what a battery category requires, maps its own columns to those fields, writes the data, closes the publish gates, checks the same readiness numbers the wizard shows, and submits. The only step it cannot perform is publication, which stays a human action.

Step 1 — Discover the industries you can author

GET /api/v1/industries (category:read) returns the industries your key is authorized for. In Phase 1.5 a key is scoped to a single industry.

Step 2 — List the categories

GET /api/v1/categories (category:read) returns the categories under your industry. Each carries the current schema version and a content hash, so you can pin to a known schema.

Step 3 — Fetch the category field schema

GET /api/v1/categories/{code}/schema (schema:read) returns every field a category defines: field_id, label, type, required, validation, options, access_tier, unit, and help_text. This is the machine-readable surface your ERP maps its columns against. Request ?format=json-schema, csv, or html for alternate representations (see Category schema formats). An out-of-scope or unknown code returns 404 (no existence oracle).

Step 4 — Create a product and ingest field data

Create the draft with POST /api/v1/products (dpp:draft:write), then write fields with PUT /api/v1/products/{id}/form-data (dpp:draft:write). Form-data ingest is a batch of field_id to value pairs and is fail-closed: the whole batch is validated, and on any failure the response is 422 FIELD_VALIDATION_ERROR with details.fieldErrors[], each entry {field_id, label, code, message}. Unknown fields are rejected, never silently dropped. The error references the field, never the submitted value (a privacy guarantee, see Errors and conventions).

PATCH /api/v1/products/{id} (dpp:draft:write) updates a draft under an optimistic lock; a stale lockVersion returns 409 LOCK_CONFLICT.

Step 5 — Close the publish gates

Some publish gates need structured writes beyond plain fields:

  • TraceabilityPOST /api/v1/products/{id}/traceability (dpp:draft:write) writes the supply-chain and bill-of-materials records that close the traceability gate. GET the same path to read them.
  • ComponentsPOST /api/v1/products/{id}/components (dpp:draft:write) adds sub-components. Self-references and cycles are rejected (422 SELF_REFERENCE / 422 CIRCULAR_REFERENCE).
  • GS1 prefixesPOST /api/v1/companies/me/gs1-prefixes (dpp:draft:write) registers the GS1 company prefixes that close the GS1 ownership gate. GET to list them.
  • SuppliersGET /api/v1/suppliers (supplier:read) returns the suppliers your company owns or is linked to, GDPR-minimised, so you can reference them in traceability writes.
  • EvidencePOST /api/v1/products/{id}/documents (document:write) attaches a document (multipart; the HMAC signs the entire raw multipart body, not the file bytes alone). GET /api/v1/products/{id}/documents (dpp:read) lists them with fileSha256, virusScanStatus, and verificationStatus; GET /api/v1/documents/{id}/download (dpp:read) returns a 2-hour signed URL.

Step 6 — Check readiness, then submit

GET /api/v1/products/{id}/readiness (dpp:gate:read) returns the same numbers the operator wizard shows: completenessScore, the persisted complianceScore and complianceStatus, the per-section breakdown, missingFieldEntries[] ({fieldId, label, kind}), the partner-stable gaps[] (each with a resolver), and ok (ready to submit). It is the richer sibling of GET /api/v1/products/{id}/gate (dpp:gate:read), which remains as a lighter gap report.

When ok is true, POST /api/v1/products/{id}/submit (dpp:submit, bodyless) moves the product to Submitted. If gaps remain, submit returns 422 SUBMIT_BLOCKED listing the missing mandated fields. A submitted DPP enters the human review queue.

Withdrawal (after publication)

POST /api/v1/products/{id}/withdraw (dpp:withdraw) withdraws a published DPP under EU Battery Regulation Article 76. The public DPP then returns HTTP 410 Gone. Withdrawal uses its own least-privilege scope, distinct from dpp:submit. Withdrawing a product that is not in a withdrawable state returns 409 INVALID_STATE.

What stays human

Publication and approval are not part of this flow today; a reviewer or operator publishes the submitted DPP in the portal. See Regulatory boundary.