Skip to content

Record provenance from your ERP

Business systems see events DICE never witnesses: a goods receipt in SAP, an inspection sign-off in your MES, a sale closed in your commerce platform. Declared Transactions let your integration write those moments onto a Thread’s history as they happen — each one an attributed, permanent entry that travels with the item’s provenance and can appear on its Public Page.

This recipe wires an ERP (the same shape fits a WMS, MES, or any system of record) to POST /api/v1/events/declare, authenticating as a Service Account and attributing every entry to the human operator who acted in your system.

  1. Your integration exchanges its Service Account credential for a short-lived bearer token (Authentication).
  2. Something happens in your system — a goods issue, an inspection, a repair closure.
  3. Your integration calls POST /api/v1/events/declare with the Thread id, a headline, and the claim’s time and place, sending the operator’s identity in the Dust-Ctx-Declared-Actor header.
  4. The entry appears in the Thread’s Transaction History in DICE, marked Declared, attributed to the Service Account acting for your operator.
  • A Service Account with an API key or OAuth client — see Authentication. The Service Account needs edit access to the Threads it will write to (grant it access to the owning Team).
  • Your organization id for the Dust-Ctx-Org-Id header, and the Team id if the Service Account should act as a specific Team — see Request conventions.
  • The Thread ids of the items involved. An integration usually resolves these by searching on the field it shares with your system — a serial number, batch, or order number — via GET /api/v1/threads (see the Threads API guide).

One call records one entry on one Thread:

Terminal window
curl -fsS "https://apid.dustid.io/api/v1/events/declare" \
-H "Authorization: Bearer $DUST_TOKEN" \
-H "Dust-Ctx-Org-Id: $DUST_ORG_ID" \
-H 'Dust-Ctx-Declared-Actor: {"id": "JDOE", "system": "SAP", "displayName": "Jane Doe"}' \
-H "Content-Type: application/json" \
-d '{
"threadId": "0b9e7c9a-2f9d-4d8a-9a51-1c2e57ab8d10",
"title": "Incoming inspection passed",
"note": "Visual and dimensional inspection against PO 4500012345.",
"kind": "inspection",
"edtf": "2026-08-06",
"location": { "name": "Plant 1710, Springfield" }
}'

Request fields — everything except threadId is optional, but an entirely empty declaration is rejected:

Field Type Notes
threadId UUID The Thread the entry belongs to. Requires edit access.
title string ≤ 80 Short headline — what feeds and pages show as the entry’s title.
note string ≤ 4000 Free-text detail of what happened.
kind string ≤ 64 Open-ended classification: sale, inspection, repair, service, … your vocabulary. Defaults to other.
edtf string ≤ 64 When it happened, at the precision you actually know — see below. Omit to record it as of now.
location object The asserted place: { "name": string, "latitude"?: number, "longitude"?: number }. name is what renders.
resIds UUID[] ≤ 25 Evidence: ids of files already attached to the Thread that document the entry — an inspection report, a certificate. Ids of files not attached to that Thread are rejected.

The response returns the materialized claim — the kind, title, note, and a structured when object carrying the claim’s display string, precision, and bounds.

edtf takes a subset of EDTF (ISO 8601-2), so the claim carries exactly the precision your system has — a year, a month, a day, a range, or an approximation:

Claim edtf Renders as
An exact day 2026-07-14 Jul 14, 2026
A month 2026-07 July 2026
A year 1968 1968
A closed range 1968/1970 1968–1970
Circa 1835~ Circa 1835
Before a date ../1970-03 Before March 1970
After a date 2019/.. After 2019

The claim is displayed everywhere at the precision you stated — a 1968/1970 range is never collapsed to a fabricated exact date. Send the precision you actually have, not a midnight-timestamped guess.

A Service Account authenticates your system. The Dust-Ctx-Declared-Actor header names the person who acted in it, per request:

Dust-Ctx-Declared-Actor: {"id": "JDOE", "system": "SAP", "displayName": "Jane Doe", "role": "Quality Inspector"}

id is required; system, displayName, and role are optional; the JSON value must stay under 1 KB (URI-encode it if it contains non-ASCII characters). The declared actor is recorded verbatim on every entry the request writes and shown in history as declared attribution — supplied by your integration, not verified by DICE, and never affecting permissions. An organization admin can make it mandatory, in which case writes without it are rejected with 403 ATTRIBUTION_REQUIRED. Full semantics: Declared actor attribution.

For declared provenance this header is worth treating as required in your own code: “Inspected — passed” is a far stronger claim with “Jane Doe, Quality Inspector” attached than with only “SAP Connector”.

When one business event touches many items — a goods receipt of 200 serialized units, a lot-level inspection — declare once across all of them:

Terminal window
curl -fsS "https://apid.dustid.io/api/v1/events/declare/batch" \
-H "Authorization: Bearer $DUST_TOKEN" \
-H "Dust-Ctx-Org-Id: $DUST_ORG_ID" \
-H 'Dust-Ctx-Declared-Actor: {"id": "JDOE", "system": "SAP"}' \
-H "Content-Type: application/json" \
-d '{
"threadIds": ["0b9e7c9a-…", "4f1d22c0-…", "9a8b11de-…"],
"title": "Incoming inspection passed",
"kind": "inspection",
"edtf": "2026-08-06",
"location": { "name": "Plant 1710, Springfield" }
}'
  • threadIds takes 1–500 Thread ids; the write is all-or-nothing and requires edit access to every Thread in the batch.
  • The same claim lands on every Thread — there is no per-Thread variation in a batch. Data that differs per item (serial, batch, measurement results) belongs in Thread fields, not in the claim.
  • The response includes a shared operationId. Store it: it is the batch’s correction handle (below).

/declare/batch writes one claim to many Threads. When you have many different claims to post — a historical backfill, a migration off a spreadsheet system, a day’s worth of shop-floor events — use /declare/rows instead. Every row is written to every Thread in threadIds, and the whole thing shares one operationId:

Terminal window
curl -fsS "https://apid.dustid.io/api/v1/events/declare/rows" \
-H "Authorization: Bearer $DUST_TOKEN" \
-H "Dust-Ctx-Org-Id: $DUST_ORG_ID" \
-H 'Dust-Ctx-Declared-Actor: {"id": "JDOE", "system": "SAP"}' \
-H "Content-Type: application/json" \
-d '{
"threadIds": ["0b9e7c9a-…"],
"rows": [
{ "title": "Inspected", "kind": "inspection", "edtf": "2024-03-01", "location": { "name": "Geneva" } },
{ "title": "Sealed for shipment", "kind": "shipment", "edtf": "2024-03-04" },
{ "title": "Customs cleared", "edtf": "2024-03-11" }
]
}'
  • Up to 100 rows and 500 Threads, capped at 2,000 total entries (threadIds.length × rows.length) per request. Split larger backfills.
  • All-or-nothing across the whole request: one unrecognized date rejects every row, rather than leaving a partial backfill of entries that can only be retracted one by one.
  • Rows carry no anchor and no evidence resIds — both are single-claim gestures. Use /declare for those.
  • /declare/batch is the one-row case of this endpoint; keep using it when the claim really is one claim.

This is the same endpoint DICE’s own CSV import posts to. If your customers are backfilling by hand rather than from a system, point them at Recording past events instead of building an integration.

Declared entries are immutable — there is no edit and no delete. The correction is a retraction: a second attributed entry stating the first was wrong. The original stays in the history marked retracted, and both travel downstream with the record — errata, never erasure.

To retract everything one batch wrote (say the goods receipt was reversed in your ERP), post the stored operationId:

Terminal window
curl -fsS "https://apid.dustid.io/api/v1/events/retract/by-operation" \
-H "Authorization: Bearer $DUST_TOKEN" \
-H "Dust-Ctx-Org-Id: $DUST_ORG_ID" \
-H "Content-Type: application/json" \
-d '{
"operationId": "7c3f0f9e-5b7a-4a4f-8f7d-2f1d0e6a9b21",
"reason": "Goods receipt reversed (movement type 102)."
}'

This retracts every still-live entry the operation wrote — entries already retracted individually are skipped — and requires edit access to every Thread involved. A single entry is retracted by its event id instead: POST /api/v1/events/{event_id}/retract with an optional reason. Event ids come from the Thread’s history (GET /api/v1/events?threadId=…).

After retracting, record a corrected entry with a fresh declare — that pair, wrong entry plus correction, is the honest shape of the record.

Response Meaning
400 INVALID_DATA The edtf value is outside the supported subset or not a real calendar date, the declaration is empty, or an evidence id is not attached to that Thread.
400 INVALID_REQUEST Malformed body — e.g. a field over its length bound.
403 ATTRIBUTION_REQUIRED The Service Account’s policy requires a declared actor and the request carried none.
404 NOT_FOUND A Thread id the caller cannot see or that does not exist. On the batch endpoint, any one such id fails the whole batch.

Error bodies follow the standard contract — see Request conventions.