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.
βΆ1. Inline with your plot upload (recommended)
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:
| Column | Maps to | Notes |
|---|---|---|
supplier_name | name | Triggers the auto-write. If absent, the row is treated as having no supplier. |
supplier_address | address_line1 | |
supplier_country | country | ISO 3166-1 alpha-2; lower-case accepted. |
supplier_statement | statement | The supplier-provided due-diligence statement. |
supplierid | external_id | Your 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_nameorSupplier_Address(instead of thesupplier_*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, sosupplier_name,supplier_address, etc. arrive mangled and won't be matched. For shapefile uploads, attach the supplier with the upload-widesupplierobject 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
supplier objectWhen 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
:idis the supplier's UUID or yourexternal_id(supplierid) β Epoch tries it as a UUID first, then falls back to your organization'sexternal_idnamespace. - Choose locations one of two ways (you can combine them):
location_idsβ theidvalues returned byGET /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.
| Field | Required | Notes |
|---|---|---|
name | yes | Free text, 1β512 chars. |
address_line1 | no | Free text. |
address_line2 | no | |
city | no | |
region | no | State / province / region. |
postcode | no | Free text β no country-specific format validation. |
country | no | ISO 3166-1 alpha-2. Returned upper-case ("CI"). |
statement | no | Up to 4,000 chars. The due-diligence statement supplied by the supplier. |
external_id | no | Your caller-side identifier. Distinct per organization β two organizations can hold different external_id values against the same supplier. |
fingerprint | read-only | Dedup 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_idagainst it. external_idis 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/suppliersreturns 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 key | Matches | Conditions |
|---|---|---|
supplier_id | the global supplier UUID (from the suppliers rollup / GET /parties/suppliers) | eq, in |
supplier_external_id | your per-org identifier (supplierid) | eq, in |
supplier_name | supplier.name | eq, in, contains, startsWith |
supplier_address_line1 / supplier_address_line2 | address lines | string conditions |
supplier_city / supplier_region / supplier_postcode | address parts | string conditions |
supplier_country | supplier.country | string conditions |
supplier_statement | the due-diligence statement | string 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 recordsupplier.propagatedβ a registry edit that propagated into this DDSsupplier.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 rowsupplier.frozen_on_submitβ supplier identity locked at submitsupplier.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.
| Endpoint | Purpose |
|---|---|
GET /parties/suppliers | List the suppliers your organization has linked. |
POST /parties/suppliers | Create or reuse a supplier and link it to your organization. |
POST /parties/suppliers/:id/locations | Attach an existing supplier to already-ingested locations (by location id and/or asset task id). |
GET /parties/suppliers/:id | Fetch one supplier. |
PATCH /parties/suppliers/:id | Edit identity fields on the registry row. |
DELETE /parties/suppliers/:id | Soft-delete; rejected if any pending DDS still references the supplier. |
GET /regulations/eudr/audit-trail/:recordId/suppliers | Per-supplier rollup for the record's current version. |
PATCH /regulations/eudr/audit-trail/:recordId/suppliers/:supplierId | Override supplier identity for this DDS only. Rejected after freeze. |
GET /regulations/eudr/audit-trail/:recordId/events | Audit log of all DDS state changes, including supplier mutations. |