Core exchange profile

The implemented SCDx 0.1.0 message structure, validation rules, and exchange behavior.

Working draft · Version 0.1 · For review and prototypingView source on GitHub ↗

Status: experimental SCDx profile 0.1.0. This profile is the contract implemented by the local Python prototype. It is not a WCO declaration, an EPCIS document, a DCSA message, or a complete supply-chain document model.

The JSON Schema describes structural constraints. The OpenAPI contract describes the API. Pydantic models add cross-field rules, and the exchange service enforces access and persistence rules. Passing a generic JSON Schema validator alone does not establish all of these conditions.

Message envelope

Field Requirement
schema_version MUST be 0.1.0.
message_id Issuer-local identifier used with issuer_id to recognize repeat submissions.
message_type shipment_event, document_notice, or correction.
issuer_id MUST match the authenticated organization when submitted.
recipient_ids One to 50 distinct organization identifiers; MUST exclude the issuer.
created_at RFC 3339 creation timestamp string including seconds and a timezone.
journey Journey identity, trade flow, origin, and destination.
shipment Scheme-qualified shipment identity, goods, and transport legs.
event One reported event, its occurrence time, location, and stated authority.
documents Up to 50 document references; defaults to an empty array.
supersedes Exchange identifier of the previous record; required only for a correction.

Unknown fields are rejected. Object identifiers are one to 128 characters, beginning with a letter or digit and followed by letters, digits, periods, underscores, colons, or hyphens. An Identifier instead contains a nonempty scheme and value, each limited to 200 characters. The draft does not validate those values against an external identifier registry.

Journeys and locations

journey contains journey_id, trade_flow, origin, and destination. The supported flows are:

  • import: origin is outside Canada and destination is in Canada.
  • export: origin is in Canada and destination is outside Canada.
  • interprovincial: both endpoints are in Canada and have distinct, nonempty subdivisions.

Each location contains identifier, name, node_type, country, and optional subdivision. Node types are port, terminal, warehouse, supplier, receiver, and facility. The country field must contain two uppercase letters. A supplied Canadian subdivision must be one of AB, BC, MB, NB, NL, NS, NT, NU, ON, PE, QC, SK, or YT. Country values and non-Canadian subdivisions are not checked against an external registry, and the model does not verify that a named facility exists.

The flow rules check endpoint classification. They do not determine customs treatment, validate every intermediate route, or decide which law applies.

Shipments and events

A shipment contains identifier, one to 100 goods items, and one to 30 transport legs. Each goods item contains identifier, description, a finite positive quantity, and unit. The unit is a nonempty label; no unit conversion or external code-list validation is implemented.

Each leg has a unique leg_id, mode, operator_id, origin, and destination. Modes are ship, rail, and truck. This release does not enforce continuity between adjacent legs or prove that their operator identifiers represent verified organizations.

An event contains event_type, occurred_at, location, authority, and optional leg_id and release_scope. Like created_at, occurred_at MUST be an RFC 3339 string including seconds and a timezone, for example 2026-10-26T10:30:00-07:00 or 2026-10-26T17:30:00Z. Fractional seconds are allowed; numeric Unix timestamps are rejected. If supplied, leg_id must reference a leg in the message. Event types are planned, departed, arrived, transferred, stored, released, delivered, document_issued, and corrected.

For a released event, release_scope MUST be customs, carrier, terminal, or collection. For other event types it MUST be absent or null. The scope distinguishes the reported type of release; authority remains a descriptive assertion rather than a verified credential. Consumers MUST NOT infer a government approval solely from either field.

Document references

A document reference contains identifier, document_type, status, issuer_id, and optional sha256. Status is issued, replaced, or cancelled. A supplied digest must be 64 lowercase hexadecimal characters. A document_notice message must include at least one document reference.

Documents are references only. No file upload, issuer-signature verification, digest verification against a file, legal-original check, or transfer-of-title mechanism is implemented.

Repeat submissions and corrections

The exchange assigns a server-generated UUID exchange_id to each new record. API retrieval and correction links use this value, not the issuer-local message_id.

An issuer MUST reuse its original message ID only when retrying the same validated message. An identical validated payload returns the existing record with duplicate: true. A different payload under the same issuer and message ID returns 409. Comparison uses the model's normalized JSON representation; this is not arbitrary semantic equivalence between different units or identifier schemes.

A correction MUST have a new message ID, message_type: correction, and supersedes pointing to an earlier visible record. It must come from the original issuer, retain the same journey ID and scheme-qualified shipment identifier, and retain exactly the same recipient set. It must supersede the latest revision; branching the correction chain is rejected. Previous records remain retrievable by their authorized audience.

Each returned record includes superseded_by. It contains the immediate successor's exchange identifier when a correction exists, or null when the record is the latest revision. Consumers MUST check this field before treating a record as current and follow successor links to the latest revision when necessary. A null value indicates the latest revision in this exchange; it does not independently verify that the reported business fact remains accurate.

Receipts and errors

A named recipient may submit one receipt per record with status: accepted or status: rejected and optional detail. The server adds the recipient organization and recording time. An identical repeat returns the original receipt; changing it returns 409. The issuer cannot record a recipient's receipt. Authorized viewers of the message can see its receipts.

401 means invalid credentials, 403 means a visible operation is not permitted, 404 hides missing or inaccessible records, 409 signals a conflicting retry or correction, 413 rejects a body over one MiB, and 422 reports validation failures. A receipt is business feedback on this record, not proof of regulatory acceptance. See the prototype guide.

Copyright © 2026 Stephen Makonin.