Get DDS Record
Fetch a single dds_record with its current version summary and
completeness counts.
โถPath params
| Param | Description |
|---|---|
recordId | DDS record id |
โถVisibility
Single rule: dds_record.organization_id โ ctx.effectiveOrgIds. A
record produced under one org is invisible to another and returns
404 so existence isn't leaked across orgs.
โถResponse
Same data.attributes shape as List DDS Records โ see that
endpoint for the field-by-field reference. Single-record envelope
(no meta.count); the meta object instead carries the
per-mode pre-submission readiness signal โ meta.submission.submit
and meta.submission.manual, each { can_submit, blockers } (see
Submission readiness).
โถlifecycle_status โ the dashboard verdict
lifecycle_status โ the dashboard verdictยง8.1 dashboard lifecycle state. Seven values, derived from the
audit log + TRACES round-trip status + the staleness flag (see
dds_mitigation_event_log). This is the field a UI renders to
tell the operator where the DDS sits in its lifecycle.
| Value | Meaning |
|---|---|
draft | Version exists; no submit-lifecycle event and completeness gates haven't all passed |
awaiting_submission | Draft + completeness gates pass (ready to file, just hasn't been clicked yet) |
submitting | version.submit_started is the latest submit-lifecycle event in the audit log |
submitted | version.submitted / version.marked_submitted landed; TRACES has not yet returned a terminal status |
accepted | TRACES returned ACCEPTED (GAP-13, not surfaced today) |
rejected | TRACES returned REJECTED (GAP-13, not surfaced today) |
stale | Submission landed but newer monitoring data has arrived since the snapshot (Article 10.5 review trigger, GAP-10, not surfaced today) |
lifecycle_statusโ the report builder'scomputed_status.status. The report exposes a narrower five-statestatusfield (draft | submitting | submitted | manually_submitted | submission_failed) that is the raw audit-log fact used for reconciliation.lifecycle_statuscomposes that with TRACES state and staleness โ asubmittedstatuscan resolve toaccepted/rejected/stalehere, and asubmission_failedstatusfalls back todraft/awaiting_submissionbecause the operator can retry. Reconcilers readstatus; UIs readlifecycle_status.
โถsubmitting_at / submitted_at
submitting_at / submitted_atCompanion timestamps to lifecycle_status. Both are derived from
the audit log (the same version.submit_started /
version.submitted / version.marked_submitted events that drive
lifecycle_status), so they will never contradict the status:
submitting_atis non-null only whenlifecycle_statusissubmittingโ carries theoccurred_atof the latestversion.submit_started.submitted_atis non-null wheneverlifecycle_statusissubmitted/accepted/rejected/staleโ carries theoccurred_atof the latestversion.submitted/version.marked_submitted.
โถunaccounted_plot_count
unaccounted_plot_countCount of plots that satisfy the record's stored filter today but are not yet in the current version snapshot โ i.e. plots that landed on the source asset_task after the version was cut and would qualify for inclusion if the filter were re-applied. Operationally: this is the trigger for cutting a v2.
0when no plots have been added since the snapshot, or when the record has no current version yet.data.links.unaccounted_plotsexposes the paginated sub-resource (Get Unaccounted Plots) that returns the actual rows.
Single record: returned by default. When you pass
?include=, it is returned only if the list namesunaccounted_plot_count: its cost grows with the source upload's plot count, so a caller that picks its blocks pays for it only on request.List view: opt-in. On the list endpoint (
GET /regulations/eudr/audit-trail) the field is computed per-record and is expensive on large orgs, so it is off by default and omitted from each item. Request it explicitly with?include=unaccounted_plot_count; the listlinks.selfpreserves the parameter so a followed/cached link returns the same projection.
โถoperator / commodity_details
operator / commodity_detailsEcho of the operator and commodity blocks supplied at create time.
Editable via Update DDS Record while no
version has been submitted. See Create DDS Record for the
field-by-field reference.
Note: the top-level attributes.commodity field is the record's
stamped homogeneity scope (the single commodity string the resolved
plot set collapsed to, e.g. "cocoa"). The
attributes.commodity_details block โ product type, HS code,
quantity, etc. โ is the per-shipment commodity metadata supplied at
create time.
โถsubmission โ the statement-level TRACES inputs
submission โ the statement-level TRACES inputsRead-only echo of the version's submission metadata (drives the TRACES
filing). Populated on drafts too (since the editable subset became
PATCHable โ GAP-23): pre-submit it reflects the staged values,
including the create-time placeholders (activity_type IMPORT,
operator_role OPERATOR until an authorisation sets it), and
freezes at filing. Use lifecycle_status / traces to tell whether
the record has been filed โ not this block's presence. This completes
the V3-TRACES filing view that operator + commodity_details start.
| Field | Meaning |
|---|---|
activity_type | DOMESTIC | IMPORT | EXPORT |
operator_role | OPERATOR or REPRESENTATIVE_OPERATOR (ยง9.1) |
internal_reference_number | Caller's own reference for the filing |
geo_location_confidential | Whether geolocation is filed confidentially |
comment | Free-text comment carried into the DDS |
country_of_activity | EU country the activity is filed under; null falls back to operator.country at submit |
border_cross_country | Mirrors country_of_activity, emitted only for IMPORT/EXPORT; null for DOMESTIC |
associated_statements | Referenced upstream DDS {reference_number, verification_number} pairs |
null only when the record has no current version.
internal_reference_numberis also surfaced at the top level ofdata.attributes(kept for back-compat โ it predates the always-populatedsubmissionblock, which now echoes the same value). It can be staged via Update DDS Record โ as canactivity_type,country_of_activity,geo_location_confidential, andcomment. It is optional; when left unset the submit flow auto-generates a stableE-<version-uuid>fallback at filing time.
โถSubmission readiness โ meta.submission
meta.submissionSurfaced on the envelope meta (single-record getter only โ the
list endpoint's meta stays pagination-only). Lets a client see what
is blocking a filing without attempting a submit; it runs the same
findPreSubmissionBlockers gate the submit path (POST .../submit)
runs.
Readiness is reported per filing mode under meta.submission:
meta.submission.submitโ predicts amode: "submit"(Epoch files to TRACES on the operator's behalf) filing.meta.submission.manualโ predicts amode: "manual"filing (the operator filed via the EU portal directly). Manual mode drops the submit-only gates (today:authorization_missing), so a record fileable only via the portal can reportmanual.can_submit: truewhilesubmit.can_submitis stillfalse.
Each mode carries:
blockersโ array of reasons that mode can't be filed. Empty[]means that mode's pre-submission gate is clear. Each entry is a discriminated union ontypewith a human-readablemessageplus type-specific detail fields.can_submitโ convenience boolean,trueonly when there is a current version, that mode'sblockersis empty, and the version isn't already in/through submission (lifecycle_statusnotsubmitting/submitted/accepted).can_submitis derived solely from that mode's blocker list โ the exact gate the submit endpoint runs โ so it can't claim a mode is fileable when the submit call would reject it, nor block a mode the submit call would accept.
type | Fires when | Key detail fields |
|---|---|---|
plots_missing_screening | Included plots have no pinned deforestation metric (haven't passed ยง6 full-resolution screening) | plot_ids |
plots_unresolved_verdict | Included plots have a pinned-but-indeterminate metric โ null verdict | plot_ids, total_count, truncated_count |
plots_non_compliant | Every included plot is non-compliant, so nothing would remain to file. Non-compliant plots are auto-excluded at submit (compliant-only default), so a mix of compliant + non-compliant does not block โ this fires only when the auto-exclude would empty the filing. Because those plots will be dropped, they are also subtracted from the per-plot defect blockers below (unmitigated_article10_risk, plots_geometry_invalid, plots_missing_geometry) โ a plot destined for auto-exclusion never blocks the filing on its own sanctions/geometry defect | plot_ids, total_count, truncated_count |
plots_missing_geometry | An included plot has NULL geometry (TRACES requires geometry on every producer) | plot_ids, total_count, truncated_count |
plots_geometry_invalid | Point-geometry plots violate the ยง10 area_ha rules โ must be 0.1โ4 ha; cattle (HS chapters 01/02) are exempt from the 4 ha max but still need area_ha โฅ 0.1 ha | plot_ids, reasons (per-plot cause), total_count, truncated_count |
too_many_producers | Included suppliers exceed the ยง15 per-commodity producer cap (1000) | producer_count, cap |
commodity_missing | Any of product_type / product_description / product_quantity_kg / hs_code is unset (commodity is optional at create, mandatory to file) | missing_fields |
operator_incomplete | The resolved operator (override, else org defaults) is missing TRACES-mandatory fields | missing_fields (operator_*) |
country_of_activity_invalid | The resolved country of activity (submission override for IMPORT/EXPORT, else operator country) is not in EU-27 + XI | country |
operator_country_invalid | The operator's own country is not a recognised ISO 3166-1 alpha-2 code (e.g. ZZ). Only fires when it isn't the resolved country of activity (that case is reported as country_of_activity_invalid), so a bogus operator country on an IMPORT/EXPORT with a valid country-of-activity override still blocks | operator_country |
operator_identifier_invalid | GAP-25: activity_type is IMPORT/EXPORT but the resolved operator is VAT-identified โ TRACES requires EORI on cross-border filings. The record PATCH rejects an edit that creates this combination; this blocker is the backstop for pre-existing (e.g. create-time default) combinations the change-scoped edit gate tolerates | activity_type, identifier_type |
activity_not_allowed_for_operator_country | GAP-26: the resolved operator's country is outside EU-27 + XI (Northern Ireland counts as EU for goods under the Windsor Framework) but activity_type is not IMPORT โ a non-EU operator may only file an IMPORT DDS. Same edit-gate/backstop pairing as operator_identifier_invalid | activity_type, operator_country |
invalid_producer_country | The record's country of production (country_code, derived from the plots and carried on every producer) is not a well-formed ISO alpha-2 code | country |
species_missing | A timber / timber-derived commodity (HS chapters 44, 47) has species_scientific_name and/or species_common_name unset โ ยง13 requires species for wood (non-timber commodities don't); checked once the base commodity is populated | missing_fields |
species_name_unresolved | A timber commodity's declared species_scientific_name is not an accepted binomial TRACES will value-validate โ a malformed non-binomial (bare epithet, digits, or a rank placeholder such as Quercus sp / Various species) an unaccepted_synonym of an accepted species, or a non_canonical_name (the binomial resolves, but the declared string carries extra tokens โ an author authority, an infraspecific tail, or a second species joined by a word โ and the V3 mapper files that string verbatim) โ resolved against the curated GBIF-authoritative timber snapshot (species_taxon_*, also served by GET /regulations/eudr/species). A structurally-plausible-but-unlisted name and hybrids pass in v1. Distinct from species_missing (which is presence/shape only), and only runs on names that passed that gate, so a blank/blob name is never double-reported | issues (per-name index, declared, reason, suggestion โ the accepted binomial for a synonym or a non-canonical name, else null), truncated_count |
commodity_description_too_long | product_description exceeds the TRACES V3 descriptionOfGoods cap (150 chars) | length, cap |
out_of_scope_hs_code | hs_code classifies outside EUDR Annex I scope and no horizontal exemption is claimed | hs_code, matched_cn_code |
hs_code_too_broad | hs_code is in scope but broader than its Annex I entry (matched_by: "broader") and has fewer than 6 digits (FAQ 7.24), and no horizontal exemption is claimed. Message: hs_code <code> is broader than its EUDR Annex I entry; declare it to the Annex I digit level (at most 6 digits) via PATCH /regulations/eudr/audit-trail/:recordId before submitting | hs_code |
hs_code_not_accepted_by_traces | hs_code is in scope but fm-apiโs reviewed acceptance policy does not accept it, and no shorter form of it is accepted (e.g. rubber 401290). Clear it by PATCHing commodity.hs_code to one of accepted_hs_codes; when that list is empty, the product can't be filed through TRACES today. Not waived by an exemption claim | hs_code, wire_hs_code, accepted_hs_codes ({ code, description }) |
supplementary_unit_required | IMPORT/EXPORT, and TRACES requires a supplementary unit on the heading sent (MTQ on 4403, 4406, 4408, 4410โ4413; NAR on 4011, 4013; KSD on 4701, 4702, 4704, 4705) but none is stored. Clear it by PATCHing commodity.supplementary_unit and supplementary_unit_qualifier. Not waived by an exemption claim | hs_code, wire_hs_code, heading, qualifier |
supplementary_unit_qualifier_mismatch | IMPORT/EXPORT, and the stored supplementary unit is not the one TRACES requires on the heading sent. Clear it by PATCHing the qualifier and the amount. Not waived by an exemption claim | hs_code, wire_hs_code, heading, qualifier, stored_qualifier |
unmitigated_article10_risk | Included plots carry an unmitigated Article 10 "stop-the-line" risk (e.g. sanctions exposure) | plot_ids, risk_categories |
self_referencing_dds | The version's own TRACES reference (from its filing statement, EPO-1816) appears in associated_statements (ยง16 "a DDS cannot reference itself") | own_reference_number |
authorization_missing | ยง9 step 3: no current-text-version operator authorisation for on-behalf-of filing (requireAuthorisation only) | text_version |
attachment_awaiting_verification | EPO-1448 ยง3.8: non-archived operator-evidence attachments on the version are still awaiting verification (verification_status โ approved). No-op today (attachments default to approved) | attachment_ids, filenames |
engagement_threads_open | Reserved (GAP-5, engagement domain not implemented) โ always empty today | thread_ids |
Parity with the submit gate. Each mode's list is produced by the same
findPreSubmissionBlockersthe submit endpoint runs, someta.submission.<mode>.can_submit+blockerspredict that mode's submit outcome (an empty list +can_submit: truemeans the submit will pass its pre-flight gates).submitis the superset;manualdrops the submit-only gates (authorization_missing). The internal reference is not a blocker in either mode โ it is optional and auto-generated at submit when unset. Detail arms usepassthrough, so new detail fields can appear on a blocker without a client change.
Example hs_code_not_accepted_by_traces blocker for a rubber record
declared as 401290:
{
"type": "hs_code_not_accepted_by_traces",
"hs_code": "401290",
"wire_hs_code": "401290",
"accepted_hs_codes": [
{
"code": "40129030",
"description": "Solid or cushion tyres, tyre treads and tyre flaps, of rubber"
}
],
"message": "TRACES does not accept hs_code 401290. Declare one of 40129030 via PATCH /regulations/eudr/audit-trail/:recordId before submitting."
}
A horizontal exemption (commodity.exemption_claim) waives only the
Annex-scope blockers (out_of_scope_hs_code, hs_code_too_broad).
The TRACES-wire blockers above still fire, because the filing still
goes to TRACES. See
HS codes TRACES accepts.
โถmeta.submission.advisories and meta.submission.wire_hs_code
meta.submission.advisories and meta.submission.wire_hs_codewire_hs_code is the HS code a filing sends to TRACES for this
record (e.g. 010229 โ 0102, 180100 โ 1801), or null when the
record has no hs_code or no scope version resolves. If you file via
the EU portal (mode: "manual"), enter this value.
advisories are non-blocking heads-ups shared by both modes. They
never affect can_submit. Each is { type, hs_code, wire_hs_code, message }.
type | Fires when | Example message |
|---|---|---|
hs_code_normalised | fm-api sends a shorter code than the one stored, because TRACES accepts only the shorter form | TRACES accepts this product only as 0102; hs_code 010229 will be submitted to TRACES as 0102. Confirm 0102 describes your product. |
hs_code_normalised | The stored code is not 2, 4 or 6 digits | hs_code 44031 is not a 2-, 4- or 6-digit HS code; it will be submitted to TRACES as 4403. |
hs_code_normalised | The stored code has a zero tail | hs_code 180100 will be submitted to TRACES as its Annex I heading 1801 (CN 1801); confirm this heading still describes your product. |
supplementary_unit_dropped | IMPORT/EXPORT with a supplementary unit stored, TRACES accepts the code, and the heading sent is one where TRACES refuses a unit; the unit is not sent | TRACES does not accept a supplementary unit on 4407; the supplementary unit (1.5 MTQ) will not be submitted. The net weight is still filed. |
low_confidence_hs_code | TRACES does not accept the code, and it is broader than its Annex entry, matches only a chapter, matches no entry, or is in scope under an 8-digit Annex entry (cut to 6 digits on the wire) | hs_code 1201 will be submitted to TRACES as 1201, which TRACES may reject (EUDR_COMMODITIES_HS_CODE_INVALID). Supply a CN heading/subheading TRACES recognises to be certain. |
Advisories are computed on every read from the current version, so
they stay visible after filing. The filed values themselves are on
traces_statements[].lines.
โถmeta.editable_fields โ the PATCH form descriptor
meta.editable_fields โ the PATCH form descriptorA self-describing list of every field Update DDS Record accepts, so a UI can render the edit form (and its validation) straight from this response instead of hard-coding it. Static per deployment. Each entry:
| Key | Meaning |
|---|---|
name | Dotted path within data.attributes (e.g. commodity.hs_code) |
label | Human-readable field label |
type | string | number | boolean | enum |
nullable | Whether null is an accepted value (clears the field) |
options | Allowed values for enum types (else null) |
max_length | Character cap for string fields (else null) |
required | Base requiredness (when required_when is null) |
show_when | Only render the field when every clause holds (else always) |
required_when | Required exactly when every clause holds (overrides required) |
show_when / required_when are AND-ed lists of clauses
{ field, operator, value }. Operators: in (value โ array),
equals, is_set (referenced field is non-null), hs_chapter_in
(first two HS digits โ array), hs_prefix_in (HS digits start with an
array entry, or are a broader prefix of one). The conditions encode the TRACES rules:
- species (
species_scientific_name/species_common_name) โ shown and required only for timber (hs_prefix_inover the Annex I timber prefixes: 4401-4421, 47, 48, 49, 940131-940191 (wooden seats), 940330-940391, 940610). - supplementary-unit pair โ each half is
required_whenthe otheris_set.
Only name, operator.* and commodity.* appear โ the submission
block is not editable via PATCH (it's finalised on the submit endpoint),
so it is absent from this descriptor.
The descriptor is the shape of the form, not a live permission:
whether the record is editable at all is governed by the audit lock (a
submitted version makes PATCH return 409), and operator.* is
all-or-nothing (patch the whole block).
โถtraces_statements
traces_statementsTRACES post-submission references (EPO-1816). Sourced from the
version's filing statement(s) in dds_submission_statement, resolved
via the dds_submission_member โ statement join โ the sole source of
the issued identifiers (the former scalar dds_record_version.traces_*
columns were dropped). Empty until the version is filed; a single
element for a within-cap filing; 1..N when an oversized DDS was split
across TRACES statements (EPO-821). Issued values are null until
TRACES returns them asynchronously via getDds.
| Field | Source |
|---|---|
statement_index | dds_submission_statement.statement_index (0-based) |
reference_number | dds_submission_statement.traces_reference (null until issued) |
verification_number | dds_submission_statement.traces_verification_number (null until issued) |
dds_uuid | dds_submission_statement.traces_dds_uuid |
lines | What fm-api sent TRACES per commodity line: [{ hs_heading, supplementary_unit }], the wire HS code and the supplementary unit (null when none was sent). Empty for a manual filing or one filed before fm-api recorded lines |
โถErrors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing/invalid API key |
| 404 | NOT_FOUND | Record isn't visible to the caller |
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. The examples show current fm-api behaviour,
including blocker messages for codes that remain unsupported.
Required scope:
ingestions:read