EUDR Compliance Use Case
Learn how to use Coolset APIs to meet EU Deforestation Regulation requirements.
Overview
The EU Deforestation Regulation (EUDR) requires companies to prove that imported commodities don't contribute to deforestation. Coolset's EUDR API automates compliance workflows.
Compliance Workflow
Step 1: Check the Risk Assessment
Coolset runs EUDR risk assessments for your orders — they are not created through the API. Each order carries the identifier its assessments are filed under in pulse_params.identifier:
curl -X GET "https://developers-scranton.coolset.com/api/orders/123/?order_action=buying" \
-H "Authorization: ApiKey YOUR_API_TOKEN"
{
"id": 123,
"assessment_status": "passing",
"pulse_params": {
"identifier": "scranton$Order$123",
"model_identifier": "scranton$Order"
}
}
Look up the order's latest assessment by that identifier. There is no per-ID retrieve endpoint — filter the list instead:
curl -G https://developers-pulse.coolset.com/api/compliance/risk-assessments/ \
-H "Authorization: ApiKey YOUR_API_TOKEN" \
--data-urlencode 'identifier=scranton$Order$123' \
--data-urlencode 'assessment_type=eudr_order_assessment' \
--data-urlencode 'latest_per_identifier=true'
Response:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 789,
"identifier": "scranton$Order$123",
"display_name": "PO-2024-001",
"assessment_type": "eudr_order_assessment",
"assessment_run_id": "a1b2c3d4-e5f6-4789-a012-3456789abcde",
"assessment_summary": {
"status": "passing"
},
"results": {},
"metadata": {},
"assessment_date": "2024-10-28T12:00:00Z"
}
]
}
Assessments run asynchronously, so a fresh one starts out pending with an empty results. Read assessment_summary.status:
| Status | Meaning |
|---|---|
pending | Nothing has been evaluated yet |
in_progress | Some controls are still being evaluated |
failing | At least one control failed — remediate before filing a DDS |
mitigated | Risks were found and mitigated — a DDS can be filed |
passing | All controls pass — a DDS can be filed |
results, metadata and the rest of assessment_summary are free-form JSON whose shape depends on assessment_type and may change between assessment versions. Key your integration off assessment_summary.status and assessment_run_id, and treat the other fields as informational.
Keep the assessment_run_id — Step 2 pins the Due Diligence Statement to it.
Step 2: Create the Due Diligence Statement
Once the risk assessment resolves to passing or mitigated, create a DDS and attach the assessed order(s) directly via orders:
curl -X POST https://developers-scranton.coolset.com/api/due-diligence-statements/ \
-H "Authorization: ApiKey YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "EUDR",
"orders": [123, 456],
"assessment_run_ids": ["a1b2c3d4-e5f6-4789-a012-3456789abcde"]
}'
type— the regulation the statement is filed under (EUDR,EUTR, orPPWR).orders/products— optional arrays of order/product IDs to attach at creation time; you don't need a separate call to link them.assessment_run_ids— required fortype=EUDRwhen you don't supplydds_identifier. It pins the statement to the risk assessment run(s) that justify it, and is immutable after creation.dds_identifier— optional; if you already have a DDS filed with EU TRACES, pass its identifier here to retrieve/link it instead of filing a new one (in that caseassessment_run_idsisn't required).name— optional free-text label.
Prerequisite: the linked risk assessment(s) in
assessment_run_idsmust have resolved topassingormitigatedbefore a DDS can be created from them. Submitting against apendingassessment fails with a validation error, and afailingassessment must be remediated first — see Issue: High Risk Assessment below — before you retry.
Adding more orders to an existing DDS later (optional)
If you need to attach additional orders to a DDS after it's already been created, add order traces:
curl -X POST https://developers-scranton.coolset.com/api/traces/orders/ \
-H "Authorization: ApiKey YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"dds_id": [789],
"order_id": [789, 1011]
}'
Both dds_id and order_id accept a single integer or an array, and the orders are added to the existing statement(s) — this is not a prerequisite for creating a DDS; use it only when you need to expand an already-filed statement.
Step 3: Upload Supporting Documents
Add evidence to support your DDS:
# 1. Create the document record. `identifiers` links it to the order.
curl -X POST https://developers-pulse.coolset.com/api/documents/ \
-H "Authorization: ApiKey YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Certificate of Origin.pdf",
"content_type": "application/pdf",
"identifiers": ["scranton$Order$123"]
}'
# Response: {"document_id": 5521, "upload_url": "https://storage.googleapis.com/...", ...}
# 2. Upload the file to `upload_url` within 15 minutes (max 100 MB).
curl -X PUT "UPLOAD_URL" \
-H "Content-Type: application/pdf" \
-H "x-goog-content-length-range: 0,104857600" \
--upload-file "Certificate of Origin.pdf"
# 3. Confirm the upload. 200 = done, 202 = not visible yet, retry shortly.
curl -X POST https://developers-pulse.coolset.com/api/documents/5521/confirm_upload/ \
-H "Authorization: ApiKey YOUR_API_TOKEN"
Step 4: Download Evidence Package
See which folders and files the assessment's evidence bundle contains:
curl -X GET https://developers-pulse.coolset.com/api/compliance/risk-assessments/789/evidence-bundle/ \
-H "Authorization: ApiKey YOUR_API_TOKEN"
{
"groups": [
{
"assessment_run_id": "a1b2c3d4-e5f6-4789-a012-3456789abcde",
"identifier": "scranton$Order$123",
"assessment_type": "eudr_order_assessment",
"display_name": "PO-2024-001",
"items": [
{
"key": "<blob key>",
"name": "certificate-of-origin.pdf",
"size_bytes": 182311,
"content_type": "application/pdf",
"last_modified": "2024-10-28T12:00:00Z"
}
]
}
]
}
Then download the whole bundle as a ZIP — or only some files, by passing their keys as selected_items:
curl -X POST https://developers-pulse.coolset.com/api/compliance/risk-assessments/789/evidence-downloads/ \
-H "Authorization: ApiKey YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"selected_items": []}' \
--output evidence-package.zip
Complete Example: Python
import time
import requests
PULSE_API_URL = "https://developers-pulse.coolset.com/api"
SCRANTON_API_URL = "https://developers-scranton.coolset.com/api"
HEADERS = {
"Authorization": "ApiKey YOUR_API_TOKEN",
"Content-Type": "application/json"
}
SUBMITTABLE = {"passing", "mitigated"}
UNRESOLVED = {"pending", "in_progress"}
def latest_order_assessment(identifier):
# No per-ID retrieve endpoint: filter the list by identifier.
page = requests.get(
f"{PULSE_API_URL}/compliance/risk-assessments/",
headers=HEADERS,
params={
"identifier": identifier,
"assessment_type": "eudr_order_assessment",
"latest_per_identifier": "true",
},
).json()
return page["results"][0] if page["results"] else None
def eudr_compliance_workflow(order_id):
order = requests.get(
f"{SCRANTON_API_URL}/orders/{order_id}/",
params={"order_action": "buying"},
headers=HEADERS,
).json()
identifier = order["pulse_params"]["identifier"]
# Step 1: wait for Coolset's assessment of the order to resolve
assessment = latest_order_assessment(identifier)
while assessment is None or assessment["assessment_summary"].get("status") in UNRESOLVED:
time.sleep(30) # use a real retry/backoff policy in production
assessment = latest_order_assessment(identifier)
status = assessment["assessment_summary"]["status"]
print(f"Assessment status: {status}")
# Step 2: only passing/mitigated assessments can back a DDS
if status in SUBMITTABLE:
dds = requests.post(
f"{SCRANTON_API_URL}/due-diligence-statements/",
headers=HEADERS,
json={
"type": "EUDR",
"orders": [order_id],
"assessment_run_ids": [assessment["assessment_run_id"]],
},
).json()
print(f"DDS Created: {dds['id']} (status: {dds['status']})")
return dds
# Failing assessments need remediation first
print("Assessment failing - gather evidence before filing")
return None
result = eudr_compliance_workflow(123)
Key Concepts
Assessment Types
EUDR assessment types you will see on assessment_type:
eudr_order_assessment- Assess an entire order (backs a DDS)eudr_order_item_assessment- Assess individual order itemseudr_origin_assessment- Assess a geographic origineudr_origin_harvest_assessment- Assess an origin for a specific harvesteudr_supply_chain_assessment- Assess the full supply chaineudr_company_maturity_self_assessment- Supplier maturity self-assessmenteudr_due_diligence_statement_assessment- Assess a Due Diligence Statement
Required Information
For EUDR compliance, you need:
✅ Commodity type and quantity
✅ Country of production
✅ Geolocation coordinates (min 4 decimal places)
✅ Harvest date
✅ Supplier information
✅ Certificate of origin
Best Practices
1. Monitor Assessment Status
Coolset does not send webhooks yet. Check assessments on a schedule, or whenever your own system changes an order, and route anything that isn't submittable to your compliance team:
def check_order(order):
assessment = latest_order_assessment(order['pulse_params']['identifier'])
status = assessment['assessment_summary'].get('status') if assessment else None
if status == 'failing':
send_alert_to_compliance_team(order, assessment)
2. Track Geolocation Accuracy
Ensure coordinates meet EUDR requirements (4 decimal places = ~11m accuracy):
def validate_geolocation(lat, lon):
# Check decimal precision
lat_precision = len(str(lat).split('.')[-1])
lon_precision = len(str(lon).split('.')[-1])
return lat_precision >= 4 and lon_precision >= 4
3. Maintain Evidence Packages
Download and archive evidence packages regularly:
import schedule
def archive_evidence_monthly():
assessments = get_monthly_assessments()
for assessment in assessments:
package = download_evidence_package(assessment['id'])
store_in_archive(package, assessment['identifier'])
# Run on first day of each month
schedule.every().month.at("00:00").do(archive_evidence_monthly)
Compliance Checklist
Use this checklist to ensure EUDR compliance:
- All orders have resolved risk assessments
- Geographic origins are documented
- Geolocations meet precision requirements
- Harvest dates are recorded
- Supplier due diligence completed
- Supporting documents uploaded
- DDS created for compliant orders
- Evidence packages archived
- Non-compliant orders flagged
- Regular compliance audits scheduled
Common Issues
Issue: Insufficient Geolocation Data
Solution: Request precise coordinates from suppliers upfront
def validate_supplier_data(data):
required_fields = [
'geolocation.latitude',
'geolocation.longitude',
'harvest_date',
'certificate_url'
]
missing = [f for f in required_fields if not data.get(f)]
if missing:
send_data_request_to_supplier(missing)
return False
return True
Issue: High Risk Assessment
Solution: Gather additional evidence; the assessment is re-evaluated once the new evidence is in Coolset
def handle_high_risk(assessment):
# Request additional documentation
documents_needed = [
'Certificate of Origin',
'Deforestation-Free Declaration',
'Satellite Imagery',
'Chain of Custody Documentation'
]
request_documents_from_supplier(documents_needed)
# Schedule manual review
create_compliance_task(assessment['id'], 'high_risk_review')
API Endpoints Used
This use case demonstrates:
GET /orders/{id}/- Supply Chain APIGET /compliance/risk-assessments/- Compliance APIPOST /due-diligence-statements/- Supply Chain APIPOST /traces/orders/- Supply Chain APIPOST /documents/- Data APIGET /compliance/risk-assessments/{id}/evidence-bundle/- Compliance APIPOST /compliance/risk-assessments/{id}/evidence-downloads/- Compliance API