Everything an enforcement agency or platform partner needs to reproduce the EUDR Enforcement Dashboard using the Epoch SCO2 API.
The Enforcement Dashboard provides two complementary workflows for EUDR compliance monitoring:
All data is fetched from the Epoch SCO2 REST API. The dashboard is a static HTML page — no backend required beyond the API.
All API requests require a Bearer token. Obtain one by calling the /refresh_token endpoint with user credentials in a JSON body:
POST https://epoch-sco2-api.com/refresh_token
Content-Type: application/json
{ "email": "USER", "password": "PASS" }
Response:
{ "access_token": "eyJhbGciOiJSUzI1NiIs..." }
Include the token in all subsequent requests:
Authorization: Bearer <access_token>
x-internal-api-key and x-user-id headers instead of Bearer tokens. Contact Epoch to receive your API key.
Base URLs:
https://epoch-sco2-api.comhttps://dev-epoch-sco2-api.comReturns all producers/facilities as streaming NDJSON. Each line is a GeoJSON Feature with compliance metrics and metadata. This is the primary endpoint for loading the Country Commodity Assessment tab.
| Parameter | Value | Purpose |
|---|---|---|
stat_type | "all" | Return all supply sheds regardless of assessment type |
table_extension | "eudr" | Query the EUDR-specific results table |
limit | 100000 | Max rows (omit for all) |
GET /fetch_supply_shed?stat_type=all&table_extension=eudr&limit=100000
| Field | Type | Description |
|---|---|---|
collection_id | string | Unique identifier for this assessment |
company_name | string | Producer/supplier name |
commodity | string | Commodity type (rubber, timber, palm_oil, etc.) |
country | string | Facility country |
admin1 | string | Admin level 1 region (province/state) |
locations_confidence | string | high/medium/low = assessed; anything else = skip reason |
noncompliance_plot_count | int | Number of plots with critical deforestation alerts |
noncompliance_area_ha | float | Total non-compliant area in hectares |
noncompliance_area_perc | float | Non-compliant area as fraction of total area (0.0 to 1.0). Used for risk classification. |
noncompliance_area_rate | float | Annualized non-compliance rate |
area_ha_supply_shed | float | Supply shed area (> 0 = supply shed assessment) |
stat_type | string | Assessment type (contains supply_shed if supply shed) |
legality_risk | string | Article 10 risk: Low / Medium / High |
ingestion_date | timestamp | When the assessment was processed |
metadata | JSON string | Custom metadata (supplier IDs, part IDs, contact emails) |
Returns plot-level deforestation results for a specific producer. Used for the drill-down detail view when a user clicks a row in the aggregate table.
| Parameter | Value | Purpose |
|---|---|---|
aggregate | false | Return individual plots (not aggregated) |
limit | 100000 | Max plots |
filename | e.g. "0x49a7..." | Collection ID to fetch plots for |
GET /fetch_deforestation_check?aggregate=false&limit=100000&filename=0x49a7...
| Field | Type | Description |
|---|---|---|
uuid | string | Unique plot identifier |
area | float | Plot area in hectares |
noncompliance_area | float | Deforested area in hectares |
noncompliance_area_perc | float | Deforestation percentage of plot area |
deforestation_alert | string | "critical", "non-critical", or "no deforestation" |
deforestation_confidence | string | Detection confidence (very high, high, medium, low, very low) |
geometry | GeoJSON | Plot polygon |
Runs real-time location screening on uploaded GeoJSON, CSV, or XLSX files. Used by the DDS Screening and Producer Screening tabs. Supports both plot polygons and facility points. CSV/XLSX files are automatically geocoded via the Google Geocoding API.
| Parameter | Value | Purpose |
|---|---|---|
file | GeoJSON / CSV / XLSX | Location geometries to screen (multipart/form-data) |
location_type | "plot" or "facility" | Type of locations being screened |
commodity_type | e.g. "palm_oil" | Expected commodity (used for commodity presence check). If omitted, auto-detect is attempted. |
include_deforestation | true | Include deforestation detection. Plots: per-plot 30 m check. Facilities: 50 km radius, natural forest only, post-2021. |
include_article10_check | true | AI web search Article 10 risk analysis. Plots: 1 call for collection area. Facilities: per-facility with Google Places enrichment. Sources ranked by relevance & freshness. |
check_road_access | true | (Facility only) Check proximity to road networks. Default: true |
check_water_access | true | (Facility only) Check proximity to waterways |
check_port_proximity | true | (Facility only) Check proximity to ports |
normalize_input | true / false | Whether to normalize input geometries. Auto-detect: false for GeoJSON/JSON/SHP files, true for CSV/XLSX (which need geocoding) |
production_volume | e.g. 1000 | (Plot only) Declared annual production in tonnes/year. Used for circumvention detection — compares declared volume against expected yield for the total plot area |
limit | 10000 | Max locations to process |
POST /validate_locations?location_type=plot&include_deforestation=true&commodity_type=palm_oil&limit=10000
Content-Type: multipart/form-data
Body: file=@plots.geojson
POST /validate_locations?location_type=facility&commodity_type=timber&check_road_access=true&normalize_input=true
Content-Type: multipart/form-data
Body: file=@facilities.csv
Returns a GeoJSON FeatureCollection with collection-level properties containing validation results:
| Field | Type | Description |
|---|---|---|
overall.risk | string | Overall risk level (high / medium / low) |
total_plots | int | Number of plots screened |
total_plot_area_ha | float | Total area in hectares |
plot_validity.shape_validity | object | risk, issues (count by type: self-intersections, slivers, etc.) |
plot_validity.commodity_presence | object | risk, commodity_absence_count (plots not overlapping declared commodity) |
plot_validity.protected_area_overlap | object | risk, protected_area_overlap_count (plots overlapping WDPA areas) |
deforestation | object | risk, plots_with_deforestation, deforestation_area_ha, plot_deforestation_pct |
circumvention | object | risk, deviation_pct (spatial deviation from expected patterns) |
cherry_picking | object | risk, deforestation_pct (comparison of inside-vs-outside deforestation rates) |
overall.investigate_categories | object | Map of category name to plot count needing investigation |
Each feature in the collection includes per-plot scores for shape validity, commodity, protected areas, and deforestation.
Returns a GeoJSON FeatureCollection with per-facility confidence scoring. Each feature includes:
| Field | Type | Description |
|---|---|---|
confidence_score | float | Per-facility confidence score (0-1). Penalty-based: starts at 1.0 and deducts for missing attributes. See Scoring. |
confidence_level | string | high (≥0.7) / medium (≥0.45) / low |
commodity_type | string | Detected or declared commodity type |
commodity_presence | float | Commodity overlap score (0-1) |
poi_presence | int | 1 if facility is near known POIs (mills, factories), 0 otherwise |
building_presence | int | 1 if building footprints detected in 100m buffer, 0 otherwise |
high_density | int | 1 if building density > 50% of buffer area (urban), 0 otherwise. Negative signal — penalizes score. |
road_access | int | 1 if road access within 500m (default: checked) |
water_access | int | 1 if waterway access within 1km (optional) |
port_proximity | int | 1 if port within 50km (optional) |
producer_name | string | Extracted producer/company name from input columns (if available) |
producer_confirmed | bool | Whether producer confirmation score exceeds threshold |
producer_confirmation_score | float | Producer confirmation score (0-1) |
Collection-level properties include aggregated counts:
| Field | Type | Description |
|---|---|---|
total_facilities | int | Number of facilities screened |
confidence_score | float | Average confidence score (0-1) |
confidence_level | object | Distribution: { "high": N, "medium": N, "low": N } |
commodity_presence | int | Facilities near declared commodity |
poi_presence | int | Facilities near points of interest |
high_density | int | Facilities in high building-density areas |
road_access | int | Facilities with road access (if checked) |
water_access | int | Facilities with waterway access (if checked) |
port_proximity | int | Facilities near ports (if checked) |
Submit screened locations for full asynchronous processing. /batch_supply_shed is used for facility points (generates isochrone-based supply sheds), /batch_process is used for plot polygons (direct deforestation assessment).
| Parameter | Value | Purpose |
|---|---|---|
file | GeoJSON | Selected features from screening results |
collection_name | string | Collection name (auto-suggested: {commodity}_{country}_{date}) |
crop_type | e.g. "timber" | Commodity type for the batch |
stats | "eudr_deforestation" | Statistics to compute |
validate_locations | "false" | Skip validation (already validated during screening) |
force_reprocess | "false" | Whether to force reprocessing of existing collections |
eudr_dds | "true" | (batch_supply_shed only) Generate DDS audit trail |
POST /batch_supply_shed
Content-Type: multipart/form-data
Body:
file=@timber_Suriname_2026-03-11.geojson
collection_name=timber_Suriname_2026-03-11
crop_type=timber
stats=eudr_deforestation
validate_locations=false
force_reprocess=false
eudr_dds=true
Returns a JSON object with the batch job status and collection ID. Processing runs asynchronously via Prefect flows.
Downloads the EUDR audit trail as a ZIP archive containing the full Due Diligence Statement with deforestation results, satellite references, and compliance determination per plot.
GET /export_eudr_dds/?collections=0x49a7...
Response: Binary ZIP file. Trigger as a browser download.
locations_confidence = high/medium/low). Disable the download button for "Not Assessable" rows.
Endpoint: POST /validate_locations
The default view after login. Upload GeoJSON, CSV, or XLSX files to screen locations. The tab supports two location types:
| Option | Type | Default | Description |
|---|---|---|---|
| Location Type | select | plot | Plots (polygons) or Facilities (points/addresses) |
| Commodity Type | select | Auto-detect | cocoa, coffee, palm, rubber, soy, timber, cattle |
| Include deforestation | checkbox | unchecked | (Plot only) Run deforestation overlay |
| Production volume | number | — | (Plot only) For circumvention detection (t/yr) |
| Check road access | checkbox | checked | (Facility only) Check road network proximity |
| Check water access | checkbox | unchecked | (Facility only) Check waterway proximity |
| Check port proximity | checkbox | unchecked | (Facility only) Check port proximity |
After screening, the sidebar shows summary cards and the map displays screened locations. A data table below the map shows all facilities/plots with sortable columns.
Facility table columns:
| Column | Source |
|---|---|
| Facility ID | facility_id or id or name, fallback: 1-based index |
| Address | facility_address or address, fallback: facility_name |
| Commodity | commodity_type |
| Country | country |
| Score | confidence_score (numeric, 0-1) |
| Level | confidence_level (high/medium/low) |
| Comm. Presence | commodity_presence |
| POI | poi_presence |
| Building | building_presence |
| Road | road_access |
| Water | water_access |
| Port | port_proximity |
| High Density | high_density |
| Producer Name | producer_name |
| Confirmed | producer_confirmed |
| Warning | primary_warning |
After screening, select facilities/plots via checkboxes and submit to batch processing. The collection name auto-suggests as {commodity}_{country}_{date} and updates on re-screening. Facilities go to /batch_supply_shed, plots go to /batch_process.
Endpoint: GET /fetch_supply_shed?stat_type=all&table_extension=eudr&limit=100000
Loads all producer assessments and displays them in a sortable table with risk-tiered compliance status.
Two sub-views:
company_name. Each group shows its worst-case status and total NC plots. Expandable/collapsible.Columns:
| Column | Source |
|---|---|
| Producer | company_name |
| Commodity | commodity |
| Admin 1 | admin1 |
| Country | country or country_code |
| Assessment | Derived: "Supply Shed" or "Direct Plots" (see risk logic) |
| Status | Derived: High Risk / Medium Risk / Compliant / Not Assessable |
| NC Plots | noncompliance_plot_count |
| NC % | noncompliance_area_perc * 100 (display as percentage) |
| Article 10 | legality_risk |
| Date | ingestion_date |
| Audit | "Download DDS" button |
Summary cards: Total Producers, Compliant, High Risk, Medium Risk (≤5%), Not Assessable.
Triggered when a user clicks a producer row. Fetches plot-level data via:
GET /fetch_deforestation_check?aggregate=false&limit=100000&filename={collection_id}
Shows individual plot polygons on the map with binary compliance status (Compliant / Non-Compliant based on >0.5 ha noncompliance area). Summary cards show Total Plots, Compliant, and Non-Compliant counts.
Facility confidence uses a penalty-based model: each facility starts at 1.0 and has points deducted for missing or negative attributes.
| Check | Condition | Penalty |
|---|---|---|
| Commodity Presence | commodity_presence == 0 | -0.50 |
| Commodity Presence (low) | 0 < commodity_presence < 1.0 | -0.25 |
| POI Presence | poi_presence == 0 | -0.20 |
| Building Presence | building_presence == 0 | -0.15 |
| Road Access | road_access == 0 | -0.15 |
| High Density (urban) | high_density == 1 | -0.10 |
water_access and port_proximity are informational only — they do not affect the confidence score.
| Level | Score Range | Color |
|---|---|---|
| High | ≥ 0.70 | Green (#16a34a) |
| Medium | ≥ 0.45 | Yellow (#ca8a04) |
| Low | < 0.45 | Red (#dc2626) |
A facility is classified as high_density (urban) when building footprint area exceeds 50% of the 100m buffer area around the facility point. This is a negative signal — commodity processing facilities are typically in rural or peri-urban areas, not dense urban centers.
On hover, the map tooltip shows the full scoring breakdown including each attribute's value and its penalty contribution. high_density = Yes should display in red (negative signal), while high_density = No displays in green (positive — rural area).
The API extracts producer_name from input CSV/XLSX columns by scanning for columns matching keywords: "name", "company", "producer", "facility". Columns that are part of the API's own output (facility_address, facility_id, commodity_type, scoring fields, etc.) are excluded from the scan to prevent false matches (e.g. address leaking into producer name).
Risk status is derived client-side from the API response fields. The key field is noncompliance_area_perc — the fraction of total assessed area flagged as non-compliant.
| Status | Condition | Color | Badge CSS Class |
|---|---|---|---|
| High Risk | noncompliance_area_perc > 0.05 (more than 5% of area) OR noncompliance_plot_count > 100 | Red (#dc2626) | high-risk |
| Medium Risk | noncompliance_area_perc > 0 or noncompliance_plot_count > 0 (any non-compliance, but below High Risk thresholds) | Yellow (#ca8a04) | medium-risk |
| Compliant | No non-compliance detected | Green (#16a34a) | compliant |
| Not Assessable | locations_confidence not in (high, medium, low) and no noncompliance data | Gray (#475569) | unknown |
function getComplianceStatus(p) {
const canDo = isAssessable(p.locations_confidence)
|| p.noncompliance_plot_count != null
|| p.noncompliance_area_ha != null;
if (!canDo) return { label: 'Not Assessable', cls: 'unknown' };
const ncPerc = Number(p.noncompliance_area_perc || 0);
const ncPlots = Number(p.noncompliance_plot_count || 0);
const ncArea = Number(p.noncompliance_area_ha || 0);
if (ncPerc > 0.05 || ncPlots > 100) return { label: 'High Risk', cls: 'high-risk' };
if (ncPerc > 0 || ncPlots > 0 || ncArea > 0)
return { label: 'Medium Risk', cls: 'medium-risk' };
return { label: 'Compliant', cls: 'compliant' };
}
Plot-level compliance uses a simple binary classification based on the noncompliance_area field (deforested area in hectares). A plot is non-compliant if more than 0.5 ha of deforestation is detected.
| Status | Condition | Color |
|---|---|---|
| Non-Compliant | noncompliance_area > 0.5 (ha) | Red (#dc2626) |
| Compliant | noncompliance_area ≤ 0.5 (ha) | Green (#16a34a) |
// Binary compliance for individual plots
const ncArea = Number(p.noncompliance_area || 0);
const isNonCompliant = ncArea > 0.5;
const status = isNonCompliant ? 'Non-Compliant' : 'Compliant';
/fetch_supply_shed). The plot detail view from /fetch_deforestation_check uses binary compliant/non-compliant.
if (area_ha_supply_shed > 0 && stat_type.includes('supply_shed')):
type = "Supply Shed" // isochrone-based area assessment
else:
type = "Direct Plots" // specific plot polygons provided
function isAssessable(confidence) {
return ['high', 'medium', 'low'].includes(confidence?.toLowerCase());
}
// A producer can be assessed if confidence is valid OR noncompliance data exists
function canAssess(p) {
return isAssessable(p.locations_confidence)
|| p.noncompliance_plot_count != null
|| p.noncompliance_area_ha != null;
}
The POC uses deck.gl with MapLibre GL. Any map library works.
Facility points colored by risk status. Sort features so higher-risk points render on top (last in the array):
const riskOrder = { 'compliant': 0, 'unknown': 1, 'medium-risk': 2, 'high-risk': 3 };
pointFeatures.sort((a, b) =>
(riskOrder[getComplianceStatus(a.properties).cls] || 0) -
(riskOrder[getComplianceStatus(b.properties).cls] || 0)
);
Fill colors:
[220, 53, 69, 200][202, 138, 4, 200][148, 163, 184, 200][22, 163, 74, 200]Point coordinates come from the feature's GeoJSON geometry, or parse facility_geo from metadata (WKT point) for facilities without geometry.
Facility points colored by confidence level:
Plot polygons colored by per-plot risk score or filtered by metric (overall risk, deforestation, protected area overlap).
Plot polygons from /fetch_deforestation_check, colored by binary compliance (>0.5 ha noncompliance area):
[220, 53, 69] (red)[22, 163, 74] (green)Overlay the supply shed boundary (blue outline) from the parent feature's geometry, and mark the facility location (yellow dot) if available.
The screening-to-processing workflow in the dashboard:
/validate_locations to get confidence scores and validation results./batch_supply_shed or /batch_process respectively.The collection name auto-suggests as {commodity}_{country}_{date} (e.g. timber_Suriname_2026-03-11) based on the first feature's properties. It updates automatically on re-screening.
stats=eudr_deforestation — Always set for EUDR enforcement workflowsvalidate_locations=false — Already validated during screening stepforce_reprocess=false — Prevents duplicate processing of existing collectionseudr_dds=true — (batch_supply_shed only) Generates the DDS audit trailEndpoint: GET /export_eudr_dds/?collections={collection_id}
Provide a "Download DDS" button for each assessed producer. Trigger a binary ZIP download. Disable the button for "Not Assessable" rows.
The ZIP contains the full Due Diligence Statement including:
Both /fetch_supply_shed and /fetch_deforestation_check return Newline-Delimited JSON (NDJSON). Each line is a complete JSON object (GeoJSON Feature).
async function fetchNDJSON(url, token) {
const resp = await fetch(url, {
headers: { 'Authorization': `Bearer ${token}` }
});
const features = [];
const reader = resp.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop(); // keep incomplete line in buffer
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) continue;
try {
const obj = JSON.parse(trimmed);
if (obj.type === 'Feature') features.push(obj);
else if (obj.type === 'FeatureCollection') features.push(...(obj.features || []));
} catch (_) {}
}
}
// Process remaining buffer
if (buffer.trim()) {
try { const obj = JSON.parse(buffer.trim()); features.push(obj); } catch (_) {}
}
return features;
}
import requests, json
def fetch_ndjson(url, token):
resp = requests.get(url, headers={"Authorization": f"Bearer {token}"}, stream=True)
features = []
for line in resp.iter_lines(decode_unicode=True):
if line.strip():
features.append(json.loads(line))
return features
/refresh_token — store the Bearer token/validate_locations with location_type=facility or plot/batch_supply_shed (facilities) or /batch_process (plots)/fetch_supply_shed?stat_type=all&table_extension=eudr&limit=100000noncompliance_area_perc (see Section 6)/fetch_deforestation_check?aggregate=false&limit=100000&filename={collection_id}noncompliance_area > 0.5 ha, green otherwise/export_eudr_dds/?collections={collection_id}