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 error.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).

File fields​

A category field of type: file does not take a URL or free text. Send the id of a document your company already holds, as returned by POST /api/v1/products/{id}/documents, and the server stores that document's file in the field.

  • The document must belong to your company and to the key's mode (live or test).
  • It must not be Infected, ScanFailed or Rejected. It also must not be Pending while public documents are served only when they are Clean.
  • Its file type must match the field's accept.
  • Any document id that cannot be used is refused the same way, whatever the reason: 422 FIELD_VALIDATION_ERROR with a field error of code FILE_REFERENCE_UNRESOLVED. Nothing is written.

To clear the matching certificate rule at the publish gate, upload the document under the exact documentName and documentType that the field's evidence object names in GET /api/v1/categories/{code}/schema. evidence is present only on a top-level file field that the certificate registry knows, and carries documentName, documentType and, where a certificate rule applies, certificateType. The gate counts the evidence only for a document of that exact name.

PATCH /api/v1/products/{id} (dpp:draft:write) updates a draft under an optimistic lock. Send expectedLockVersion with the lockVersion from your last read; a stale value returns 409 LOCK_CONFLICT.

Step 5: Close the publish gates​

Some publish gates need structured writes beyond plain fields:

  • Traceability: POST /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.
  • Components: POST /api/v1/products/{id}/components (dpp:draft:write) adds sub-components. Self-references and cycles are rejected (422 SELF_REFERENCE / 422 CIRCULAR_REFERENCE).
  • GS1 prefixes: POST /api/v1/companies/me/gs1-prefixes (dpp:draft:write) declares GS1 company prefix claims (6 to 12 digits each). A declared claim does not close the GS1 gate: Traceable verifies it against your GS1 licence first, and the licence upload is available in the portal only. GET lists the verified prefixes, and the status of every claim under registrations.
  • Suppliers: GET /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.
  • Evidence: POST /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 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), ok (ready to submit), publishReady, requiredFields ({filled, total}), warningCount and warnings[]. Warnings are checks the EC guidance does not make mandatory for the category; they never change ok. 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. The public DPP then stays online as an archived passport: it answers HTTP 200 with an availability block and the header X-DPP-Availability: archived (see Archived passports). 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. If the product's status changes while the request is being processed, the answer is 409 STATUS_CHANGED: re-read the product, then decide whether to send the request again.

Battery units (after publication)​

Once a model has been published by a person in the portal, an integration can add its battery units, one passport per battery. POST /api/v1/products/{id}/units (dpp:draft:write, a signed write with an Idempotency-Key) takes { "units": [{ "serial": "…", "batch": "…" }] } and does what the portal's unit-list upload does. It never publishes or changes the model.

  • Up to 1,000 distinct serials per call; a larger fleet goes in several calls (413 TOO_MANY_UNITS above that). A body with more than 2,000 rows is refused with 400 VALIDATION_ERROR before the serials are counted.
  • Serials are trimmed and de-duplicated, and the first row wins. A serial that already exists comes back as existing and is never created twice, so a retry is safe. An existing unit keeps its stored batch (storedBatch).
  • A serial or batch that breaks the identifier rule is a per-row invalid result, not a failed call.
  • The plan's battery passport limit is checked for the whole call before the first unit is created (402 PLAN_LIMIT_REACHED, nothing created).
  • A model that is not published returns 409 PRODUCT_NOT_PUBLISHED.
  • Units are created in chunks of 100. If a call fails part-way, the earlier chunks are kept and the same list can be sent again.
  • The answer is 201 when at least one unit was created and 200 when every row already existed or was invalid. The body carries results[] (serial, batch, status of created, existing or invalid, and code, upi and storedBatch or error), the counts created, existing and invalid, and chunks.

GET /api/v1/products/{id}/units (dpp:read) lists the units of a product your company owns, one row per battery with its serial, batch, opaque code and the registry details recorded on this platform. Pass limit (1 to 1000, default 100) and, for the next page, the previous page's nextAfter as after; nextAfter is null on the last page.

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.

Was this page helpful?