EUDR Audit Trail From Deforestation Metrics (Plots Only)

End-to-end recipe that goes from a raw plot-only upload to an exported EUDR DDS report — and shows how a caller-supplied CSV column (here clientId) is projected into that report via report_data_fields.

Plot-only upload. This workflow uploads location_type: plot assets exclusively. Facilities (and other location types) are out of scope here — the audit record is built from the upload's plots.

It chains three subsystems that each have their own per-endpoint docs:

  1. Ingestion — upload plot geometry, wait for it to stage.
  2. Deforestation metrics — run processBatchStats over the upload and wait for it to finish.
  3. EUDR audit trail — create an audit record from the upload's plots (carrying a report_data_fields projection), poll the create task for the new record id, inspect / replace the projection list, and finally export the report so the projected column visibly lands under each plot's data_fields.

Everything is keyed off a single ddsdf_task_id (the upload), so the create filter simply reuses filter[assets.task_id].

Endpoint map

StepMethodPathThreads to next
01POST/ingestions/assets (location_type: plot; CSV carries a custom clientId column)ddsdf_task_id
02GET/ingestions/{task_id} (poll until staged)—
03PUT/ingestions/assets?filter[assets.task_id]=eq:{task_id} (metrics:["deforestation"])ddsdf_metrics_task_id
04GET/ingestions/{metrics_task_id} (poll until terminal)—
05POST/regulations/eudr/audit-trail (task filter + report_data_fields)ddsdf_create_task_id
06GET/regulations/eudr/audit-trail/tasks/{create_task_id} (poll until complete)ddsdf_record_id
07GET/regulations/eudr/audit-trail/{record_id}/data-fields(confirm projection)
08PUT/regulations/eudr/audit-trail/{record_id}/data-fields (full-replace)(replace semantics)
09GET/regulations/eudr/audit-trail/{record_id}/report(projected report)
10—Render a standalone HTML report (Bruno-side preview)—
11GET/regulations/eudr/audit-trail/{record_id}/audit-trail/htmlsigned download URL
12POST/regulations/eudr/audit-trail/{record_id}/authorisationsclears authorization_missing
13POST/regulations/eudr/dds/{record_id}/submitddsdf_submit_task_id
14GET/regulations/eudr/audit-trail/tasks/{submit_task} (poll until complete)TRACES identifiers

Steps 12–14 file the record with EU TRACES — the end of the customer's flow. Submit files only the compliant supply base and requires a live authorisation for the current version (step 12), which must be the last write before submit.

Variables exchanged

  • ddsdf_task_id — set by step 01 (the upload); read by steps 02, 03, 05.
  • ddsdf_metrics_task_id — set by step 03 (equals ddsdf_task_id when the filter fast-path reuses the existing task); read by step 04.
  • ddsdf_create_task_id — set by step 05; polled in step 06.
  • ddsdf_record_id — set by step 06 from the create task's result.records[0].record_id; read by steps 07–09.
  • ddsdf_*_poll_retries — used by the polling helper to bound retries.

How report_data_fields threads through

The clientId column is not a tag and not a filter input — it is an uploaded data column that the report projects. Two independent threads run side by side:

  • Selection — filter[assets.task_id] picks which plots become the audit record (step 05). This is how the upload is bound to the record.
  • Projection — report_data_fields declares client_id ← first-non-null of ["clientId", "client_id"]. It is stored on the version (read back via the data-fields endpoint, step 07) and resolved per plot at report-build time: each plot_register entry's data_fields block carries { value, label, matched_path } for every projection key (step 09). See the Report Data Fields concept page for the resolution rules and the inherit / clear / replace tri-state on version cut.

Why this is a workflow, not a per-endpoint test

Each endpoint is covered in Endpoints/ and the ingestion/metrics flows in their own Workflows folders. This collection exercises the composition: an upload's plots flow into an audit record, a custom CSV column survives the whole chain, and the projection is observable on the final report. Treat it as runnable documentation.