Suppliers

Suppliers are the parties supplying the commodities you place on the EU market. In an EUDR DDS the supplier block carries each plot's identity, address, country, and statement of due diligence β€” and is part of the audit trail Article 9 expects you to retain.

Epoch maintains a supplier registry that is deduplicated across uploads, scoped per organization, and automatically associated with plots at ingestion time when your source data carries supplier columns. You don't have to call a registry endpoint up front β€” uploading supplier-bearing plot data is the canonical way to populate it.


Getting supplier data into Epoch

There are two ways supplier records appear in your account.

If your plot file carries supplier columns, Epoch links each plot to a supplier as part of the normal POST /ingestions/assets flow. No separate call is required.

Recognized supplier columns:

ColumnMaps toNotes
supplier_namenameTriggers the auto-write. If absent, the row is treated as having no supplier.
supplier_addressaddress_line1
supplier_countrycountryISO 3166-1 alpha-2; lower-case accepted.
supplier_statementstatementThe supplier-provided due-diligence statement.
supplieridexternal_idYour caller-side identifier, per-organization.

These columns apply uniformly across upload formats β€” CSV, GeoJSON properties, Parquet, GeoPackage, Excel. Rows that share an external_id (or whose identity fields produce the same fingerprint) reuse the existing supplier rather than creating a duplicate.

A facility upload that names its supplier via producer_name or Supplier_Address (instead of the supplier_* columns above) is also registered: a deduplicated supplier row is created and linked to the facility, and it resolves onto that facility's plots at DDS time. Previously such a supplier appeared only as a display name on the DDS and never became a first-class, filterable registry row β€” see Supplier-name fallback.

Shapefiles (.zip) are the exception: the DBF attribute table truncates field names to 10 characters, so supplier_name, supplier_address, etc. arrive mangled and won't be matched. For shapefile uploads, attach the supplier with the upload-wide supplier object instead (next section).

Country and other identity fields are validated as part of upload validation. Invalid values surface as soft validation issues on the asset task, not as a hard 400 β€” the upload still completes and the rows missing valid supplier data get flagged for review.

2. Inline as an upload-wide supplier object

When a whole file represents one supplier β€” or the format can't carry per-row columns cleanly (shapefiles) β€” pass a structured supplier object on the POST /ingestions/assets body (JSON flow) or as a JSON-stringified supplier form field (multipart). It applies to every location in the upload as the fallback supplier; any per-row supplier columns still win where present.

"supplier": {
  "name": "CafΓ© Verde Cooperativa",   // required
  "external_id": "CV-001",
  "address_line1": "Rua do CafΓ© 100",
  "city": "PatrocΓ­nio",
  "region": "MG",
  "postcode": "38740-000",
  "country": "BR",                     // ISO 3166-1 alpha-2, validated
  "statement": "100% smallholder, deforestation-free."
}

Unlike the per-row columns (which the file format limits), the object accepts the full supplier field set and validates country against the ISO 3166-1 alpha-2 set. It writes to the same deduplicated registry, so re-uploading the same identity reuses the existing row. See Asset Upload β€” Attaching a supplier.

3. Manually via the registry endpoint

When you don't have a CSV column for a supplier (e.g. you're onboarding a small subset by hand), POST /parties/suppliers creates or reuses a supplier row directly. The supplier is automatically linked to your organization on create.

4. Attach a supplier to existing locations after upload

When locations were already uploaded without supplier information β€” or you need to correct or assign a supplier later β€” you don't have to re-upload. POST /parties/suppliers/:id/locations attaches an existing registry supplier to locations that are already in Epoch, writing the same link the ingestion path would have.

  • The path :id is the supplier's UUID or your external_id (supplierid) β€” Epoch tries it as a UUID first, then falls back to your organization's external_id namespace.
  • Choose locations one of two ways (you can combine them):
    • location_ids β€” the id values returned by GET /locations (staged-location ids).
    • asset_task_ids β€” attach the supplier to every location in those uploads.
  • Both fields accept a single value or an array.
  • It is latest-wins: attaching a different supplier to a location replaces the previous link.
POST /parties/suppliers/2fdff3fb-480c-43b0-84d8-6dd8aaacce61/locations
{
  "location_ids": ["b1c0…", "b1c1…"],   // ids from GET /locations
  "asset_task_ids": "9f3a…"             // or sweep a whole upload
}

The response echoes the supplier and a meta block reporting how many staged-locations were linked and which ids didn't resolve to a location visible to your organization (unresolved_location_ids, unresolved_asset_task_ids) β€” the request still succeeds for the ids that did resolve. Only an unknown or unlinked supplier returns 404.

Everything is scoped to your acting organization. As a parent organization, use the x-acting-as header to attach to a child organization's locations.

Each arm accepts up to 500 ids, but asset_task_ids expand to every location in those uploads. A single request that resolves to more than 10,000 distinct locations is rejected with 413 PAYLOAD_TOO_LARGE before anything is written β€” split the work across smaller batches of asset_task_ids / location_ids and retry.

Once attached, the supplier flows into any DDS you build from those locations through the normal plot β†’ shed β†’ facility resolution.

Required scope: parties:write


Supplier fields

All fields are user-supplied except fingerprint, created_at, and updated_at, which Epoch maintains.

FieldRequiredNotes
nameyesFree text, 1–512 chars.
address_line1noFree text.
address_line2no
cityno
regionnoState / province / region.
postcodenoFree text β€” no country-specific format validation.
countrynoISO 3166-1 alpha-2. Returned upper-case ("CI").
statementnoUp to 4,000 chars. The due-diligence statement supplied by the supplier.
external_idnoYour caller-side identifier. Distinct per organization β€” two organizations can hold different external_id values against the same supplier.
fingerprintread-onlyDedup key derived from the identity fields. See Dedup behavior.

Dedup behavior

Suppliers are deduplicated globally across Epoch by a fingerprint computed from the identity fields (name, address_line1, address_line2, city, region, postcode, country). Whitespace is collapsed and the comparison is case-insensitive.

Practical consequences:

  • Re-uploading the same supplier (or a supplier whose name only differs in case or whitespace) reuses the existing row.
  • Two organizations sourcing from the same physical supplier share one underlying row, but each keeps its own external_id against it.
  • external_id is not part of the fingerprint β€” different organizations can assign different identifiers to the same supplier without breaking dedup.
  • Identity-field edits (PATCH /parties/suppliers/:id) update the fingerprint. If the new fingerprint matches an existing supplier, your link is repointed to it; if not, the row is updated in place when only your org references it, or forked into a new row when multiple organizations do.

How suppliers appear on the DDS

When you create a DDS record (or cut a new version) from a set of plots, Epoch resolves a supplier per plot/shed at version-create time and records the supplier identity against that version.

Before submit, the version is editable:

  • GET /regulations/eudr/audit-trail/:recordId/suppliers returns a per-supplier rollup of the current version.
  • The rollup tracks the live supplier identity β€” if you edit the supplier via PATCH /parties/suppliers/:id, the change is reflected for any pending (unsubmitted) DDS that references it, and an audit event is recorded.
  • You can also override the identity for one specific DDS only (without affecting the registry row) via PATCH /regulations/eudr/audit-trail/:recordId/suppliers/:supplierId.

At submit (POST /regulations/eudr/dds/:recordId/submit), the supplier identity is locked in: a snapshot is captured per shed, the rollup's frozen flag flips to true, and subsequent registry edits no longer bleed in. The locked-at-submit values are what TRACES sees and what your audit trail retains.

At version cut (POST /regulations/eudr/audit-trail/:recordId/tasks with operation: create_version), the previous version is also frozen using the same mechanism so re-cutting a record always preserves a faithful snapshot of what the prior version stated.

Supplier-name fallback for unattached suppliers

When a plot or shed has no registry supplier attached β€” for example data uploaded before supplier registration named suppliers via producer_name / Supplier_Address β€” the supplier rollup falls back to the normalised supplier name from the facility upload (supplier_name, then producer_name, then the Supplier_Address blob). This keeps the DDS showing a human-readable supplier instead of a raw shed identifier.

New uploads register the supplier instead of relying on this fallback. A facility upload naming its supplier via any of those columns now creates a deduplicated registry row and links it (see above), so it resolves through the normal supplier path and is visible/filterable. The read-time fallback below mainly covers data ingested before that behaviour, and any upload you deliberately opt out of registration for.

This fallback is enabled by default. To turn it off for a record, set resolve_supplier_by_name: false on the create body:

POST /regulations/eudr/audit-trail
{
  "data": {
    "type": "dds_record",
    "attributes": {
      "filter": { "assets.task_id": { "eq": "…" } },
      "resolve_supplier_by_name": false
    }
  }
}

With the flag set to false, a supplier that isn't attached to the registry is not backfilled from the upload's normalised name β€” it surfaces as the raw shed name (or an unresolved/null supplier in the rollup) instead. Suppliers that are attached (per-row columns, the upload-wide supplier object, or a registry link) are unaffected.

The choice is frozen on the version and inherited when you cut a new version, so a record created with the fallback disabled stays disabled across re-cuts unless you explicitly create it again with the flag enabled.


Build a DDS for one supplier

You can create a single DDS record that gathers all of a supplier's plots β€” across every facility and every uploaded file β€” by filtering on the supplier when you create the record. The DDS filter accepts a supplier_* family of keys; each resolves plots through their link to the matching supplier:

Filter keyMatchesConditions
supplier_idthe global supplier UUID (from the suppliers rollup / GET /parties/suppliers)eq, in
supplier_external_idyour per-org identifier (supplierid)eq, in
supplier_namesupplier.nameeq, in, contains, startsWith
supplier_address_line1 / supplier_address_line2address linesstring conditions
supplier_city / supplier_region / supplier_postcodeaddress partsstring conditions
supplier_countrysupplier.countrystring conditions
supplier_statementthe due-diligence statementstring conditions

Filter by supplier_id alone to pull a supplier's whole footprint into one record:

POST /regulations/eudr/audit-trail
{
  "data": {
    "type": "dds_record",
    "attributes": {
      "name": "CafΓ© Verde β€” all facilities",
      "filter": { "supplier_id": { "eq": "2fdff3fb-480c-43b0-84d8-6dd8aaacce61" } }
      // operator + commodity blocks omitted for brevity
    }
  }
}

Multiple supplier_* keys AND together against the same linked supplier (e.g. supplier_country + supplier_name). The resolved plot set must still be homogeneous (single commodity, single country) for the record to create. See EUDR Compliance β€” Process per supplier for the end-to-end workflow and Create Audit Trail for the full filter reference.


Auditing supplier changes

Every supplier mutation on a pending DDS appends a row to the mitigation-event log, retrievable through GET /regulations/eudr/audit-trail/:recordId/events. The supplier-related actions are:

  • supplier.overridden β€” a per-DDS PATCH on this record
  • supplier.propagated β€” a registry edit that propagated into this DDS
  • supplier.forked β€” a registry edit that forked into a new global row (multi-org case)
  • supplier.merged β€” a registry edit that merged into an existing global row
  • supplier.frozen_on_submit β€” supplier identity locked at submit
  • supplier.frozen_on_version_cut β€” supplier identity locked when a new version was cut

See the Mitigation Audit Log endpoint reference for the full action catalog and filter syntax.


Endpoint summary

For payload shapes, query parameters, and response envelopes, see the OpenAPI reference.

EndpointPurpose
GET /parties/suppliersList the suppliers your organization has linked.
POST /parties/suppliersCreate or reuse a supplier and link it to your organization.
POST /parties/suppliers/:id/locationsAttach an existing supplier to already-ingested locations (by location id and/or asset task id).
GET /parties/suppliers/:idFetch one supplier.
PATCH /parties/suppliers/:idEdit identity fields on the registry row.
DELETE /parties/suppliers/:idSoft-delete; rejected if any pending DDS still references the supplier.
GET /regulations/eudr/audit-trail/:recordId/suppliersPer-supplier rollup for the record's current version.
PATCH /regulations/eudr/audit-trail/:recordId/suppliers/:supplierIdOverride supplier identity for this DDS only. Rejected after freeze.
GET /regulations/eudr/audit-trail/:recordId/eventsAudit log of all DDS state changes, including supplier mutations.