Update DDS Record
Patch the name, operator, commodity, and/or the editable
submission metadata (internal_reference_number, activity_type,
country_of_activity, geo_location_confidential, comment) on an
existing DDS record. These inputs live on the record's current
version (latest version_number); only the fields the caller
actually sends are written, absent fields keep their current value
(RFC 7396 — JSON Merge Patch semantics).
The record's filter and scope (commodity / country_code), version
data, and TRACES post-submission references are immutable and never
touched here. The filing identity fields (operator_role,
associated_statements) are also not
editable here — they fix the legal entity the DDS is filed under and
are stamped at record creation. No post-create writer exists for them
today: the Create Authorisation
endpoint records consent only (its former submission_metadata stamp
was removed so a re-grant can never overwrite staged fields).
▶Internal reference number
internal_reference_number is the operator's own reference for the
submission (TRACES V3: max 35 chars). It is optional — you may
stage it on the draft here so it round-trips on reads (surfaced as
the top-level internal_reference_number attribute), or leave it
unset and the Submit DDS flow auto-generates a
stable fallback at filing time. Send null to clear a staged
value back to auto-generate-on-submit; absent leaves it unchanged.
▶Audit lock
Editing is gated on the current version not having any
submit-lifecycle event in the audit log
(dds_mitigation_event_log). Once version.submit_started,
version.submitted, or version.marked_submitted has been
appended for the current version, its operator / commodity inputs
are part of the audit trail and the endpoint responds 409 CONFLICT. The gate is purely on submission lifecycle — the
dashboard lifecycle_status (accepted / rejected / stale)
and the audit-log computed_status.status are surface views over
the same events.
Effectively: edit while pending, lock once filed (or mid-filing).
▶Path params
| Param | Description |
|---|---|
recordId | DDS record id |
▶Request body
All blocks and fields are optional. The example shown above patches
operator.phone, the commodity of a rubber tyre import
(commodity.hs_code 4011, commodity.product_quantity_kg, and the
40 NAR supplementary unit TRACES requires on 4011 for IMPORT), and
the staged submission metadata; everything else stays as it was. A "set this
back to default" semantics is not supported for required fields —
once provided at create time, fields cannot be cleared. For nullable
fields (name, internal_reference_number, country_of_activity,
comment, commodity.species_common_name,
commodity.species_scientific_name, commodity.supplementary_unit,
commodity.supplementary_unit_qualifier) sending null clears the
value, and absent leaves it.
▶Editable blocks at a glance
| Block | What it represents | Editable here? |
|---|---|---|
operator | The legal entity actually placing the product on the EU market (the EUDR-accountable party). | Yes — partial. Same field shape as on create. |
commodity | The goods this DDS covers (HS code, product type, description, product_quantity_kg, the goodsMeasure supplementary_unit / supplementary_unit_qualifier pair, species). | Yes — partial. Same field shape as on create. |
internal_reference_number | The operator's own reference for the submission (top-level attribute). | Yes — optional; null clears it (auto-generated at submit when unset). |
activity_type / country_of_activity / geo_location_confidential / comment | The remaining statement-level TRACES inputs, flat on attributes — the same editable subset the Submit DDS one-call body accepts, so a draft can be fully staged before filing. Staged values read back on the draft's submission block. | Yes — see the field rules below. |
operator_role / associated_statements | The filing identity (who the DDS is filed for). | No — 400. Writable only atomically with a fresh consent grant via Create Authorisation. |
See Create Audit Trail for the
field-by-field reference, per-field validation rules, and the
full "who/what each block represents" preamble. Validation
applies to every field that is present: an invalid email or
hs_code is still a 400 even if the rest of the body is valid.
▶TRACES V3 field rules enforced here
-
commodity.supplementary_unit/supplementary_unit_qualifier— the optional goodsMeasure second TARIC unit (net weight in kg always rides onproduct_quantity_kg). The qualifier is a UN/CEFACT Rec20 code:MTQ(cubic metres — timber volume),NAR(number of articles) orKSD(kilograms of dry substance — pulp).KGMis rejected (it is net weight's unit). The amount and qualifier must be present together: setting one and clearing the other — after merging with the stored values — is a422. -
commodityspecies —species_scientific_name/species_common_nameare required only for timber / timber-derived commodities (HS chapters 44, 47); enforced at submit via thespecies_missingpre-submission blocker, not rejected here. -
activity_type—DOMESTIC,IMPORT, orEXPORT. Cross-field rules run against the merged (stored + patch) state, so patching one half of an invariant is a422: DOMESTIC requires the mergedcountry_of_activityto be null; IMPORT/EXPORT require the merged operator to be EORI-identified (not VAT); a non-EU operator (outside EU-27 + XI) can only file IMPORT. Two ergonomic carve-outs: (a) the activity/operator rules fire only when the patch actually changes a participating value — re-sending a field at its stored value is a no-op, and a pre-existing (e.g. create-time default) combination is tolerated until theoperator_identifier_invalid/activity_not_allowed_for_operator_countrypre-submission blockers stop it at filing; (b) switching to DOMESTIC without sendingcountry_of_activityauto-clears any stored country (the field is hidden on DOMESTIC forms, so requiring an explicit null would trap them) — the record_update audit event carries the cleared value ascountry_of_activity_autocleared. Explicitly sending DOMESTIC together with a non-nullcountry_of_activityis still a422.A
422from the two activity/operator rules carries adata.details.issuesarray anchoring the rejection to the request-body fields that produced it, shaped like flattened Zod issues ({ "path": [...], "message": "..." }). Both participating fields are listed, because either one is a valid fix — an IMPORT with a VAT-identified operator is resolved by switching the identifier toEORIor by switching the activity toDOMESTIC, and the API cannot know which the caller intended:{ "error": "For IMPORT the operator must be identified by EORI (not VAT); ...", "code": "VALIDATION_ERROR", "tracking_id": "req-1a2b3c", "details": { "issues": [ { "path": ["data", "attributes", "activity_type"], "message": "..." }, { "path": ["data", "attributes", "operator", "identifier_type"], "message": "..." } ] } }activity_not_allowed_for_operator_countryanchors todata.attributes.operator.countryinstead ofidentifier_type. The other422arms on this endpoint (the DOMESTIC/country contradiction, the supplementary-unit pair) name no single field and so omitdetailsentirely — readcodefirst, thendetailsif present. -
country_of_activity— EU-27 orXI(Northern Ireland) only (TRACES allow-list); lowercase input is uppercased. Only meaningful for IMPORT/EXPORT (it doubles as the border-cross country);nullclears it back to "derive fromoperator.countryat filing". -
comment— free text, trimmed, max 1024 chars;nullclears.
▶Clearing an HS-code blocker
Coffee policy note: TRACES acceptance accepts bare 0901 (IMPORT and EXPORT
re-probed on 2026-10-19), and Epoch now accepts the bare heading alongside its five subheadings.
Production has not been verified. Coffee acceptance fields and examples here
describe the current fm-api policy.
A rubber record declared as 401290 reads
hs_code_not_accepted_by_traces in meta.submission on
Get DDS Record. The blocker's
accepted_hs_codes lists 40129030. PATCH that code:
{ "data": { "type": "dds_record", "attributes": { "commodity": { "hs_code": "40129030" } } } }
The next GET no longer carries the blocker. The same PATCH clears
hs_code_too_broad. supplementary_unit_required and
supplementary_unit_qualifier_mismatch clear with a PATCH of
commodity.supplementary_unit and supplementary_unit_qualifier. See
HS codes TRACES accepts.
▶Per-field validation
All string fields are trimmed server-side; pure-whitespace input is rejected. The lengths and formats listed in Create Audit Trail apply identically here for any field the caller chooses to patch.
Top-level attributes keys that don't map to an editable field are
rejected with a 400 (the attributes object is strict — a typo'd
or non-editable key surfaces as an error rather than a silent no-op):
| Field | Where it lives instead |
|---|---|
filter | Stamped at create time and immutable |
commodity (top-level scope) | Derived at create from the resolved plot set |
country_code | Same as above |
operator_role / associated_statements | Stamped at record creation; no post-create writer (the authorisation endpoint records consent only) |
traces | Populated post-submission by the submit flow |
▶Response
Returns the full record envelope (same shape as Get DDS Record)
reflecting the post-patch state. updated_at is bumped to the
request time. The current version's operator + commodity blocks
reflect the merged result; unchanged fields show their existing
value.
▶Errors
| Status | Code | When |
|---|---|---|
| 400 | BAD_REQUEST | A provided field failed Zod validation (bad HS code, malformed email, unsupported supplementary_unit_qualifier, non-TRACES country_of_activity, unknown/non-editable attribute key, etc.) |
| 401 | UNAUTHORIZED | Missing/invalid API key |
| 404 | NOT_FOUND | Record isn't visible to the caller |
| 409 | CONFLICT | Current version is mid- or post-submission |
| 422 | OPERATOR_INCOMPLETE | An operator patch can't form a complete override (version resolves live from org defaults) |
| 422 | VALIDATION_ERROR | Merged state breaks a TRACES invariant — the supplementary-unit pair was split; DOMESTIC with a country of activity; IMPORT/EXPORT with a VAT-identified operator; non-EU operator on a non-IMPORT activity |
Required scope:
ingestions:write