Axine Axine Evidence API reference Guide API OpenAPI reference

Axine EUDR evidence API

Reference for the pre-cutoff evidence routes, as they stand on 18 September 2026. Base URL https://lisaris.axinelabs.com/api/v1. Every response is JSON unless the route name ends in .pdf.

Authentication

Mint a key in the studio under Account → API keys (POST /api-keys with a session), then send it on every call:

Authorization: Bearer <key>

A key is bound to the workspace it was minted in; no workspace header is needed with a key. Browser sessions send X-Workspace-Id instead, or ?workspace=<id> on a plain link such as a PDF download.

If you use the portal

Portal users mint their own keys in the portal under Settings → API access (GET/POST /portal/api-keys, DELETE /portal/api-keys/{id}, each from a signed-in session; up to five active keys per workspace). No studio account is involved.

A key minted there carries the portal role, and reaches exactly what the portal seat reaches in the browser: everything under /portal (plot check and register, producers, dashboard, imagery timeline, evidence and due diligence statements), the alert adjudication routes under /eudr/alerts, and tiles under /tiles and /change/tiles. Other route families, /eudr/exoneration among them, answer 403 with {"code": "forbidden", "message": "portal members use the portal endpoints"}.

Every refusal has the same shape, and code is the part to branch on:

{"error": {"code": "evidence_cap", "message": "Trial covers 200 plots of evidence in all; ..."}}

The two ways in

you holdroute familywhat it does
a book of plots (polygons or points)/portal/plotsvalidates, registers, reads evidence per plot, returns JSON and PDF per plot
an analysis boundary already in the studio/eudr/exonerationthe same evidence object for a saved boundary, from its published analyses

A trial uses the first family. Plots of any size are accepted; the evidence floor applies to the verdict, not to intake.

Who may call what. /eudr/exoneration and /eudr/exoneration/export are for workspace members: a key with the portal role is refused there and uses the /portal/plots routes, which answer the same object for a register plot. Both alert routes below are open to portal keys and member keys alike, because the buyer of the alert lane is often a portal member.

1. Register plots

POST /portal/plots/validate?country=CI      multipart file → report only, nothing stored
POST /portal/plots/import?country=CI        multipart file → validated and registered

Accepted files: GeoJSON (Polygon, MultiPolygon or Point features), zipped shapefile, CSV of points with lon,lat columns. Per-feature properties read when present: ref (your plot id; matched on re-upload), commodity, area_ha, planted_year, producer, country.

country is stored on every plot the import registers, and it is what names the control campaign and the benchmarking classification on the plot's evidence when no producer with a country is attached (sections 4a and 4b). Give it as an ISO 3166-1 alpha-2 code (CI), an alpha-3 code (CIV), or the English or French name (Cote d'Ivoire, Ivory Coast). A country property on a feature wins over the query parameter, so one book can span a frontier. A file that names no country leaves a country already stored on a plot as it is. The query parameter also drives the axis-order check: without it that check is skipped and the report says so in notes, and the same is true of a country outside the list at GET /portal/plots/countries, which is still stored.

planted_year is stored when it is a year between 1900 and the present; anything else is left out and reported on the plot as bad_planted_year.

producer is a producer's name, matched without regard to case or spacing against the producers already in your workspace. Exactly one match attaches the plot to that producer. No match, or more than one, imports the plot without a producer and says so on the plot (producer_not_found, producer_ambiguous); the import does not create producers.

Re-importing a plot with a changed boundary withdraws its evidence. When a ref you already hold comes back with a boundary that differs by more than about a centimetre, the finding read over the earlier boundary no longer describes the plot: evidence_state returns to none, the register row carries no verdict, GET /portal/plots/{id}/evidence answers as for a plot never read and evidence.pdf answers 409 no_evidence. The plot is marked "evidence": "stale" in plots and counted in imported.evidence_stale; run evidence again to read the new boundary. A boundary that has not changed resets nothing. If a read was queued or running when the boundary changed, the plot is marked "evidence": "in_flight" instead; run it again with rerun: true once it finishes.

Response: a validation report per plot (findings, repairs, rejected), and on import the counts nested under imported:

{"imported": {"created": 812, "updated": 0, "skipped": 3,
              "duplicate_refs": 0, "unreferenced_inserted": 5, "registered": 812,
              "producers_matched": 790, "producers_unmatched": 22,
              "evidence_stale": 0, "evidence_in_flight": 0},
 "row_errors": [], "plots": [ "..." ], "notes": [ "..." ], "country": "CI"}

Rejected plots are stored too, with their findings, so the register can answer "what is still wrong".

statuscodemeaning
413file_too_largethe upload is over the limit the message states
422empty_filethe upload has no bytes
422unknown_countrythe country query parameter is not a recognised alpha-2 code, alpha-3 code or country name; the message lists the accepted forms
GET /portal/plots?limit=100&offset=0&state=valid&q=<ref fragment>&shipment_id=<id>
GET /portal/plots/{id}

Rows carry id, external_ref, commodity, country, area_ha, geometry_source, validation_state (valid | repaired | rejected | pending), planted_year, producer_name, evidence_state (none | queued | running | done | failed), verdict, reason, evidence_at, and state, what the record shows on the plot (section 4g). The list adds archive_review_price, the next step a withheld plot is given (section 4f). shipment_id narrows the list to the plots one shipment draws on (section 10). The detail route adds the geometry and the whole validation_report. A plot id outside your workspace answers 404 not_found.

Producers are created with POST /portal/plots/producers (422 name_required without a name, 409 duplicate_ref on a reference already held, 422 unknown_country on a country that is not a recognised code or name; the forms accepted are the ones the import accepts, and the alpha-2 code is what is stored) and attached with POST /portal/plots/{id}/producer. The producer's country decides which control campaign a finding is reported against; see section 4a.

2. Run evidence

POST /portal/plots/evidence
  {"all": true}                      every valid or repaired plot with a geometry and no evidence yet
  {"plot_ids": ["<id>", ...]}        named plots
→ {"job_id": "...", "queued": 37, "cap": 200, "used_after": 37}

One job per call, plots read in sequence, about three to five minutes per plot. A plot that cannot be read is marked failed with a plain reason and the job continues. When nothing needs evidence the call succeeds with job_id: null, queued: 0 and a note saying so, so a nightly integration can run unconditionally.

statuscodemeaning
403plan_tier_requiredthe plan includes no plot evidence; the message names the plan that unlocks it
403evidence_capthe plan's total is reached; the message states used and remaining
422no_plotsneither all nor plot_ids given
503queue_unavailablethe queue is down; send the same call again shortly
GET /portal/plots/evidence/summary
→ {"states": {...}, "verdicts": {...}, "reasons": {...},
   "cap": 200, "used": 37, "recovered": 0, "plan": "Trial"}

reasons counts finished plots by reason code, which is how to see whether withheld plots are withheld for the size floor or for a forest reading. Reading the summary also resolves plots left in flight by a job that died; recovered says how many.

3. Read evidence

GET /portal/plots/{id}/evidence        → {"plot_id", "external_ref", "state", "issued", "evidence": {...}}
GET /portal/plots/{id}/evidence.pdf    → the evidence document, same object rendered

The PDF is built from the stored object, so file and JSON cannot disagree.

statuscodemeaning
404not_foundno such plot in this workspace
409no_evidencethe read has not finished; the message names the state the plot holds

3a. Alert adjudication

A monitoring service flags a canopy change over a supplier's ground. This lane answers whether that ground was already under cultivation before the 31 December 2020 cutoff, in which case the alert is consistent with replanting on an established plot, or passes the alert back for review with the record attached.

POST /eudr/alerts/adjudicate
  [{"ref": "ALERT-2026-0412",
    "geometry": {"type": "Point", "coordinates": [-6.01, 5.33]},
    "alert_date": "2026-08-14",
    "producer_id": "<uuid>",            optional
    "commodity": "cocoa",               optional
    "country": "CI"}]                   optional, ISO-2
→ [{"ref": "ALERT-2026-0412", "plot_id": "...", "job_id": "...",
    "read_window": {"area_ha": 4.0, "constructed": true}}]

One to 500 alerts per call, geometry a GeoJSON Point, Polygon or MultiPolygon, and one job for the batch. Each alert is registered as a plot with geometry_source alert, so it appears in the register listing and carries the same evidence object as any other plot. Every alert is checked before any of them is written, so a batch lands whole or is refused whole.

The limits. A footprint over 5 km² is refused: an alert is a canopy change on one stand, and one verdict over a district would be true of part of it and false of the rest. An alert_date before 1 January 2021 is refused: the obligation runs on land cleared after 31 December 2020. country, or the assigned producer's country, is what makes the axis-order check decidable, and it is the same check the upload path runs, so a file in [latitude, longitude] order is caught here rather than adjudicated over open water.

The read window. A polygon of 4 hectares or more is read exactly as sent. A point, or a polygon under 4 hectares, is read over a square of 4 hectares centred on it, because that is the area from which the evidence floor issues a finding at all. The alert's own footprint is kept beside the window, and read_window.constructed says which of the two was read. Both areas are also on the plot, under validation_report.alert, with the alert date.

statuscodemeaning
403plan_tier_requiredthe plan includes no plot evidence; the message names the plan that unlocks it
403evidence_capthe plan's total is reached; the message states used and remaining
422too_many_alertsmore than 500 in one call
422no_alertsan empty list
422ref_in_usea reference is repeated in the batch or already in the register; the message names up to ten
422bad_geometrythe geometry is not a Point, Polygon or MultiPolygon, or could not be read
422axis_order_swappedthe coordinates fall in the expected country only when longitude and latitude are exchanged
422coordinates_out_of_rangea coordinate is not on Earth
422footprint_too_largethe footprint is over 5 km²
422alert_before_cutoffalert_date is before 1 January 2021
503queue_unavailablethe queue is down. Nothing was registered and no reference was taken, so send the same batch again
GET /eudr/alerts/{plot_id}
→ {"plot_id", "ref", "state", "issued",
   "alert": {"alert_date", "footprint_area_ha", "window_area_ha", "window_constructed"},
   "adjudication": {"outcome": "prior_management", "statement": "..."},
   "ahead": 2,
   "evidence": {...}}

evidence is the object described in section 4, calibration block included, so the alert answer and the evidence document rest on the same figures. A plot that is not an alert answers 404 not_found.

outcomeissued whenstatement
prior_managementthe verdict is prior_management_evidencedThe ground under this alert was under cultivation before 31 December 2020. A canopy change here is consistent with replanting on an established plot.
unresolvedany other finished state, including a withheld verdict and a failed readPrior management could not be established from the record on this ground. The alert is passed for review.
unresolvedthe read was never queuedEvidence was not read for this alert. Submit it again.
pendinga job is reading itEvidence is being read.

pending is said only of an alert a job is holding, and ahead counts the evidence jobs queued in front of it in your workspace, so a wait can be planned rather than polled. An unresolved alert goes to a person, with the seasons, the instruments and the scene identifiers already assembled for them.

Scanning published alerts onto your register

The routes above take alerts you already hold. These two close the gap before that: hand us plot ids and get back the plots that carry a published alert, then adjudicate only those.

Axine does not detect deforestation. Nothing here runs change detection. These routes retrieve alerts that other institutions have published, over the ground in your register, and hand what they find to the adjudication above. Every alert names the product, dataset and version it came from.

The products. Both are read through the Global Forest Watch Data API, at the newest version the catalogue reports as loaded rather than at the latest alias, which can name a version that has been retired.

productdatasetpublishersensorspublished accuracylicence
integrated (default)gfw_integrated_alertsUMD/GLAD and WURSentinel-1, Sentinel-2 and Landsat, combined at 10 m, 30°N to 30°S, dailyThe publisher states no single figure and records that accuracy varies across the coverage with the three contributing systemsCC BY 4.0
raddwur_radd_alertsWURSentinel-1 C-band SAR at 10 m, primary humid tropical forestValidation of confirmed alerts in the Congo Basin gave 2% false positives and 5% false negatives for disturbances greater than 0.2 ha. Those three qualifiers are part of the figureCC BY 4.0

Licence and what travels. Both products are CC BY 4.0 with no further restriction recorded on the dataset, which permits commercial redistribution of the alert geometry on the single condition of attribution. So the centroid and the bounding box of each alert are returned, and every alert carries the attribution line its publisher asks to be shown. If a product's licence ever changes to one that does not permit this, source.geometry_withheld is true, the centroid and bbox are omitted, and the fact, date and confidence of the alert are returned without its place. POST /eudr/alerts/scan-and-adjudicate then refuses with geometry_not_redistributable, because an alert that cannot be placed cannot be adjudicated.

What an alert is. These are raster products: the API answers one row per 10 m pixel. Pixels that touch and were detected within 90 days of one another are one event, and that event is one alert here. Ninety days is RADD's own confirmation window. area_ha is the summed ground area of the pixels in the cluster, which reproduces the publisher's own area__ha aggregate.

GET /eudr/alerts/scan?plot_id=<uuid>&plot_id=<uuid>&start=2021-01-01&end=2026-09-20
    plot_id      repeated, or comma-separated. 1 to 50 registered plots
    start, end   default 2021-01-01 to today
    product      integrated | radd, default integrated
    min_area_ha  drop alerts smaller than this, default 0

→ {"window": {"start": "2021-01-01", "end": "2026-09-20"},
   "product": "integrated",
   "plots": [
     {"plot_id": "...", "ref": "CI-0001", "area_ha": 78.0,
      "truncated": false,
      "source": {"product": "Integrated deforestation alerts",
                 "dataset": "gfw_integrated_alerts", "version": "v20260920",
                 "publisher": "UMD/GLAD and WUR", "licence": "CC by 4.0",
                 "attribution": "...", "citation": "...",
                 "published_accuracy": "...", "geometry_withheld": false},
      "alerts": [
        {"first_date": "2021-07-25", "last_date": "2021-07-25",
         "confidence": "high", "confidence_pixels": {"high": 3},
         "pixels": 3, "area_ha": 0.0368,
         "centroid": {"type": "Point", "coordinates": [-6.00305, 4.99985]},
         "bbox": [-6.00315, 4.99985, -6.00295, 4.99985],
         "source": {...}}]}],
   "plots_with_alerts": 1,
   "alerts_found": 1}

Read-only. Nothing is queued, nothing is written, and a scan does not count against the evidence allowance; this is the step you take before deciding what to spend it on. A plot nobody has raised an alert over answers "alerts": [] with the product that was read still named, which is how you tell "no alerts" from "not read": the latter carries a refusal string instead. Confidence is nominal, high or highest, the last meaning two of the three integrated systems agree on that pixel; a cluster reports the strongest level in it.

POST /eudr/alerts/scan-and-adjudicate
  {"plot_ids": ["<uuid>", "<uuid>"],
   "start": "2021-01-01",              optional
   "end": "2026-09-20",                optional
   "product": "integrated",            optional
   "min_area_ha": 0.0}                 optional

→ {...the scan answer above...,
   "adjudicated": [{"ref": "CI-0001-integrated-2021-07-25-0001",
                    "plot_id": "...", "job_id": "...",
                    "read_window": {"area_ha": 4.0, "constructed": true}}]}

The same scan, then the adjudication above, called rather than copied: the plan gate, the reference check, the axis-order and size and date checks, the read window, the workspace lock, the evidence cap and the rollback on a failed enqueue all behave exactly as they do when you send your own alerts, and every limit in the table above still applies. Each found alert becomes one plot of evidence against your allowance, so a scan over a long window can be expensive; min_area_ha is how you adjudicate the larger alerts first.

Each alert is adjudicated from its bounding box when that box is itself 4 hectares or more and 5 km² or less, and from its centroid otherwise, which the 4 hectare read window then widens to the evidence floor. plots[].alerts[].adjudicated_as says which was used. References are built as <your plot ref>-<product>-<first date>-<n>, so a row in the register traces back to the alert that produced it, and running the same scan twice answers 422 ref_in_use rather than paying for the same ground again.

A scan that finds nothing queues nothing and is not an error: adjudicated is empty and statement says so.

statuscodemeaning
422no_plotsno plot id was given
422too_many_plotsmore than 50 plots in one request
422bad_productproduct is not integrated or radd
422bad_windowend is before start
422alert_before_cutoffstart is before 1 January 2021
422too_many_alertsthe plots carry more than 500 published alerts in this window; narrow it, send fewer plots, or raise min_area_ha
422geometry_not_redistributablethe product's licence does not permit passing alert geometry on, so its alerts can be reported but not adjudicated
404not_foundno such plot in this workspace

Both routes are reachable by portal keys and member keys alike, like the rest of /eudr/alerts.

4. The evidence object

{
  "aoi_name": "GB-013",                 // your reference
  "cutoff": "2020-12-31",
  "issued": "2026-09-18T20:31:02+00:00",
  "plot_area_km2": 0.0784,
  "evidence_floor_km2": 0.04,           // no exoneration is issued under this
  "calibration": {                      // what has been measured on this ground
    "region": "Cote d'Ivoire", "countries": ["CI"], "state": "validated",
    "commodities": ["cocoa", "rubber", "oil palm"], "controls": 122,
    "forest_cleared": "0/57", "forest_cleared_rerun": "0/30",
    "established_crop_filable": "51/65",
    "measured_on": "2026-09-16",
    "reference_map": "BNETD-CIGN 2020 national land cover, 10 m", "note": "..."
  },
  "risk": {                             // Article 10 context, never a verdict; section 4b
    "protected_area": {"intersects": false, "nearest": {"name": "Forêt Classée de la Haute Dodo"},
                       "distance_km": 6.2, "source": "OpenStreetMap, via the Overpass API"},
    "country": {"classification": "standard", "instrument": "(EU) 2025/1093 ...",
                "version_date": "2025-05-23"},
    "neighbourhood": {"prevalence_pct": 0.66, "dataset": "umd_tree_cover_loss", "version": "v1.13"}
  },
  "settlement": {                       // buildings on and beside the plot, never a verdict; section 4c
    "present_inside_at_cutoff": false, "present_in_buffer_at_cutoff": true,
    "reading": "No buildings were detected inside the plot in 2016, 2020 or 2023, and buildings stood within 100 m of its boundary in 2020 and 2023.",
    "source": "Google Open Buildings 2.5D Temporal", "licence": "CC BY 4.0"
  },
  "history": {                          // the ground in each year since 1990, never a verdict; section 4d
    "moist_forest_2020_share": 0.02, "never_moist_forest_share": 0.11,
    "deforestation_year": {"share_before_2021": 0.87, "share_2021_or_later": 0.0, "before_2021": {"year_min": 1988, "year_median": 1996, "year_max": 2004}},
    "reading": "The Landsat record places 98.0% of this plot outside tropical moist forest at the end of 2020; its DeforestationYear band dates the loss of moist forest on 87.0% of the plot to 1988 to 2004 (median 1996), and 11.0% was not moist forest in any year of the record. 2.0% is classed as moist forest at the end of 2020.",
    "source": "JRC Tropical Moist Forest (TMF) v1_2025"
  },
  "footprint": "uploaded boundary",     // or "circle of 1.5 ha around the declared point"
  "read": {                             // what was read, under what gate
    "pixels": 784, "pitch_m": 10.0, "scenes_searched": 155, "scenes_kept": 101,
    "min_clear_fraction": 0.3, "min_pixels": 25, "start": "2016-07-01", "end": "2021-06-30",
    "sensors": ["sentinel-2", "landsat (HLS L30)"], "landsat": {"scenes_kept": 19}
  },
  "verdict": "prior_management_evidenced",
  "reason": "cycle_and_structure_agree",
  "reasons": ["cycle_and_structure_agree"],
  "finding": "The canopy fell and refoliated on a repeating annual phase before the cutoff date ...",
  "seasons": [
    {"season": 2017, "label": "2017–18", "observations": 20, "plateau": 0.5241, "trough": 0.1964,
     "trough_date": "2018-03-29", "depth": 0.3277, "dipped": true, "no_answer": false,
     "truncated": false, "last_observed": "2018-06-23"}
  ],
  "seasons_observed": 4, "seasons_with_dip": 4,
  "phase_locked": false, "phase_sd_days": 26.6, "mean_depth": 0.2787,
  "seasons_without_answer": [],
  "structure": {"layer": "shade_fraction", "shade_at_trough": 0.3907, "seasons_matched": 4,
                "forest_like": false, "aot_at_trough": 0.268, "withheld_for_dust": null},
  "radar": {"instrument": "ALOS PALSAR L-band, annual mosaic", "tile": "N05W010",
            "mirror": "Digital Earth Africa",   // or "Planetary Computer" outside Africa; same files
            "years_read": 4,
            "plot_minus_local_forest_db": -3.29, "local_forest_baseline_db": -9.95,
            "sigma_db": 0.0, "hh_hv_db": 5.62,
            "forest_like": false, "refusal": null},
  "height": {"instrument": "ETH Global Canopy Height 10 m, 2020 (Lang et al. 2023)", "tile": "N03W009",
             "median_m": 13.0, "p10_m": 10.0, "p90_m": 19.0, "pixels": 784,
             "forest_like": false, "refusal": null},
  "terrain": {"mean_slope_deg": 3.2, "source": "Copernicus DEM GLO-30",
              "outside_characterised_range": false},
  "scenes": ["S2A_MSIL2A_20170712T104021_R008_T29NQF_20210210T074637", "..."],
  "skipped": [{"scene": "...", "at": "...", "why": "72% of this plot is cloud ... Weather, not ground."}],
  "posture": "This is a measurement of canopy phenology ...",
  "baseline_note": "Felling and replanting an established plantation falls outside what Article 2(3) ..."
}

Both families return the same object: GET /portal/plots/{id}/evidence carries it under evidence, and GET /eudr/exoneration returns it directly, adding sensors and landsat for the state of the Landsat half of that read.

Every instrument answers forest_like as true, false or null, where null means the instrument could not answer and refusal says in words why.

Readings, not decision lines. Each block carries what was measured on the boundary: hh_hv_db, sigma_db, shade_at_trough, aot_at_trough, median_m, mean_slope_deg, the per-season depths, and the instrument's own categorical answer. It does not carry the numeric line that answer was taken against: those lines are this service's operating point rather than a fact about the ground, they move as controls accumulate, and a filed document quoting one would be wrong the day after it moved. radar.evergreen_hh_hv_min_db, radar.threshold_db, structure.threshold, structure.aot_limit, height.crop_line_m, height.forest_line_m and terrain.max_slope_deg were served until 2026-09-18 and have been withdrawn; read forest_like, outside_characterised_range, withheld_for_dust and refusal for what each instrument concluded.

reason and reasons

reason is the single code the decision reached, from the table in section 6. reasons is the full list the finding carries, and it is what to read when there is more than one thing to know:

concession

Plots whose centroid falls inside Indonesia's extent carry a concession block, whatever country was stated for them, and plots elsewhere do not carry the key at all. The extent is a bounding box that also takes in Malaysia, so a Malaysian plot carries the block too, reading inside: false. It records whether the boundary intersects a licensed industrial pulpwood plantation concession in Global Forest Watch's idn_wood_fiber layer. A planted forest is forest under Article 2 of the regulation, and the instruments here read a pulpwood stand the way they read an agricultural plantation, so a positive finding is not issued inside one: the verdict comes back as prior_management_weak with ["inside_licensed_pulpwood_concession", "cycle_and_structure_agree"], the second code being what the instruments reached. Unlike the risk block, this one can change the verdict, and only ever from a positive finding to a withheld one.

"concession": {
  "checked": true,                      // false when the layer did not answer
  "inside": true,                       // true, false, or null for not known
  "share": 0.62,                        // fraction of the plot inside, or null
  "licence_type": "HT",                 // the licence class of the concession, or null
  "source": "Global Forest Watch Data API, idn_wood_fiber v20200725",
  "dataset": "idn_wood_fiber", "version": "v20200725",
  "licence": "View Only, Not Downloadable.",
  "citation": "...", "caution": "...", "note": "..."
}

Any intersection counts as inside. The layer is published view only, so the block reports the intersection, its share and the licence class, and does not reproduce the concession's name or any other record from the layer. inside: false means outside the concessions the layer maps; its publisher describes it as incomplete and the block carries that caution in their words. When the layer cannot be read, checked is false and inside is null. A positive finding is then withheld with concession_layer_unavailable when the plot's country is ID, or when no country was given and the centroid is not also inside Malaysia's extent; on any other plot the finding stands and the block records that the layer was not read. An unread layer is never reported as outside. The block is stored with the finding, so a document produced later shows the answer the layer gave on the day of issue. Give every plot a country: it decides what an unread layer does, and nothing else about this block.

4a. Calibration

Every finding carries a calibration block saying what has been measured on the ground the plot sits on. The lane reads the same archives everywhere, so it answers anywhere; the margins it answers against were measured on controls, and the block says whether they were measured *here*.

statemeaning
validateda control campaign has run in this region; the block carries its numbers, sample size and date
in_campaigna campaign is under way; no accuracy is quoted until it finishes
readsthe archives cover this ground and every instrument reads it; the margins were measured elsewhere

A validated block states counts, not percentages, so the denominator travels with the number. controls is how many blocks or boundaries the campaign assessed, forest_cleared is how many forest blocks reached a positive finding over how many were read, and established_crop_filable is how many established pre-cutoff crop blocks reached a filable finding over how many were read. forest_cleared_rerun appears where a later re-read of the forest half was run at evidence-floor scale, and is a second sample rather than a restatement of the first.

Which region a plot is reported against. The producer's country decides it, which is one reason to give every plot a producer with a country. With no country the plot's centroid decides it, and only when the centroid falls inside exactly one campaign region. Regions are held as bounding boxes and boxes overlap along frontiers, so a centroid inside two of them returns the reads block carrying the note:

Set the producer's country to receive the regional calibration.

4b. Risk context

Every finding carries a risk block for the Article 10 risk assessment. This lane serves Article 9, the information step: what the archive shows about land use before the cutoff. Article 10 asks the operator to weigh that against the conservation status of the land, the country of production and the surrounding area. The block puts three such measurements next to the finding so they are not read off a different screen.

It is context, not a verdict. It changes no verdict, no reason, no reasons and no finding sentence, and the same finding assessed with and without it is byte identical. In particular an intersection with a protected area is not a finding of illegality: producing inside one may be lawful, licensed, pre-existing, or inside a buffer zone where agriculture is permitted, and which it is turns on that designation's own rules and on national law. The block reports the intersection and names the area. It draws no conclusion from it.

"risk": {
  "note": "Context for an Article 10 risk assessment, not a verdict ...",
  "article": "Regulation (EU) 2023/1115 Article 10",
  "protected_area": {
    "available": true, "intersects": true,
    "source": "OpenStreetMap, via the Overpass API",
    "dataset": "OpenStreetMap protected areas (boundary=protected_area, boundary=national_park, leisure=nature_reserve)",
    "licence": "Open Database License (ODbL) 1.0",
    "attribution": "© OpenStreetMap contributors",
    "snapshot": "2026-09-20T08:11:01Z",      // the mirror's own database timestamp
    "search_radius_km": 25.0,
    "areas": [{"name": "Parc National de Taï", "designation": "national park",
               "iucn_category": "II", "osm_type": "area", "osm_id": 3603588120}],
    "distance_km": 0.0,
    "coverage_note": "...", "excluded_sources": "..."
  },
  "country": {
    "country": "CI", "country_name": "Cote d'Ivoire",
    "classification": "standard", "enumerated": true,
    "instrument": "Commission Implementing Regulation (EU) 2025/1093 of 22 May 2025 ... OJ L, 2025/1093, 23.5.2025",
    "eli": "http://data.europa.eu/eli/reg_impl/2025/1093/oj", "celex": "32025R1093",
    "version_date": "2025-05-23", "checked_on": "2026-09-20", "amended_by": null,
    "source": "European Commission country classification list, the residual under Article 1(2) ...",
    "consequence": "Articles 10 and 11 apply in full. Member State checks cover at least 3% of operators, Article 16(8)."
  },
  "neighbourhood": {
    "available": true, "loss_ha": 412.3, "prevalence_pct": 0.66,
    "ring_km": 12.0, "ring_area_ha": 62911.8,
    "since_year": 2021, "canopy_density_threshold_pct": 30,
    "source": "Global Forest Watch Data API",
    "dataset": "umd_tree_cover_loss", "version": "v1.13", "licence": "CC BY 4.0",
    "citation": "Hansen, M. C., P. V. Potapov, R. Moore ... Science 342: 850-853 ...",
    "accuracy": "..."
  }
}

A source that did not answer says so. Each of the three parts is always present. available: false with a note naming the source means nothing was measured; intersects: false means the check ran and the answer was no. Those never collapse into each other, and an absence is never served as a zero.

Protected areas

Read from OpenStreetMap through the Overpass API, under the Open Database License 1.0, which permits commercial use with attribution. The block carries © OpenStreetMap contributors and the mirror's own database timestamp as snapshot.

Two searches run against one point: every protected area whose geometry comes within 25 km, with that geometry, and every area that contains the point. The second is what answers for a plot deep inside a large park, where nothing is within 25 km of a boundary. Each plot is then tested against the polygon itself, so a boundary crossing the plot counts as an intersection and an area near but not touching it gives distance_km to its nearest point. A plot in no protected area reports a distance rather than a null; beyond 25 km the block says the search stops there.

designation comes from protection_title, designation, or the tag that marked the feature. iucn_category is reported only where OpenStreetMap carries one, from iucn_level or from the seven protect_class values that are IUCN management categories. National classes that reuse protect_class are not reported as IUCN categories.

What this layer is. OpenStreetMap protected areas are contributed rather than an official register, and coverage differs between countries. A plot reported as intersecting none intersects none in that snapshot.

Why not the WDPA. The World Database on Protected and Conserved Areas is the comprehensive global layer and is free to download, and its terms of use at protectedplanet.net/en/legal bar Commercial Use without prior written permission. Commercial Use there means "any use by, on behalf of, or to inform or assist the activities of, a commercial entity", and the bar extends to "any work derived from or based upon" the materials. There is no carve-out for reporting a result rather than redistributing the geometry: the clause permitting non-downloadable publication sits under the same commercial bar. The copy on Google Earth Engine (WCMC/WDPA/current/polygons) and the copy on the Global Forest Watch Data API (wdpa_protected_areas) carry the UNEP-WCMC terms unchanged. The licensed routes are an IBAT subscription (ibat-alliance.org), which is not free data, or written permission from UNEP-WCMC. Either one would replace this layer with better coverage; until then OpenStreetMap is the layer the licence allows.

Why no key biodiversity areas. The World Database of Key Biodiversity Areas (keybiodiversityareas.org/termsofservice) carries the same two-limb Commercial Use definition and routes commercial users to IBAT. Its section 3 does say the KBA Partners "place no restrictions on the identity of named KBAs and the criteria under which they qualify", which arguably permits naming a KBA commercially, but it does not clearly permit the intersection computation that produces the name. No KBA layer is read, and the block says so in excluded_sources.

Country benchmarking

From a versioned table in the service, not fetched at request time, so a filed document says which version it was assessed against.

fieldmeaning
classificationlow, standard or high, or null when no country was given
enumeratedwhether the Commission names this country, as against the residual rule
instrument, eli, celexthe implementing regulation, its ELI and its CELEX number
version_datethe date the version in force was published
checked_onthe date the table was checked against EUR-Lex for amendments
amended_bythe amending act, or null while the original text stands
consequencewhat the level changes for the operator, by article

The instrument is Commission Implementing Regulation (EU) 2025/1093 of 22 May 2025, OJ L, 2025/1093, 23.5.2025, ELI http://data.europa.eu/eli/reg_impl/2025/1093/oj, CELEX 32025R1093, adopted under Article 29(2) of Regulation (EU) 2023/1115 and in force since 26 May 2025. Checked against EUR-Lex on 20 September 2026: no amending, corrigendum or repealing act is recorded against it, so amended_by is null. The European Parliament's resolution of 9 July 2025 (TA-10-2025-0149) called on the Commission to repeal it; that resolution is not binding and has no suspensive effect. A review of the list was announced for 2026 and had not been adopted on that date. When one lands, version_date, checked_on and amended_by all move.

The Annex enumerates 4 high-risk countries (Belarus, the Democratic People's Republic of Korea, Myanmar, the Russian Federation) and 140 low-risk countries. Article 1(2) then does the rest: "A standard level shall be maintained for all countries not listed in the Annex." The 50 countries the Commission enumerates as standard on its own country classification list are held as well, so a country it has named is reported as named. Anything in none of the three lists is standard under Article 1(2) and carries enumerated: false.

The ISO codes are ours. The Annex gives country names and nothing else; the Commission publishes no machine-readable list and no codes. Every ISO 3166-1 alpha-2 code in the table was mapped from the OJ names by hand and is the part that can be wrong. A territory or dependency is not given its parent state's level: it falls to the residual with enumerated: false, which says the Commission has not named it rather than that it was assessed.

Classification is by country of production, which the producer's country supplies, the same field the calibration block uses. Without one, classification is null and the block says why, because a country cannot be read off a boundary.

Neighbourhood

Tree cover loss since the cutoff in the ring around the plot: the same ring the L-band forest baseline is drawn from, with the plot cut out of it, because a neighbourhood containing the plot is partly a measurement of the plot.

umd_tree_cover_loss v1.13 on the Global Forest Watch Data API, CC BY 4.0, cited as Hansen et al. 2013. Loss is counted from 2021, the first full year after the 31 December 2020 cutoff, over pixels carrying at least 30% canopy density in 2000, which is the threshold Global Forest Watch's own published statistics are cut at. loss_ha is the hectares lost, ring_area_ha the ring, prevalence_pct the first over the second.

What the dataset measures. Stand-replacement disturbance of any cause at 30 m, harvest, fire and windthrow included. It is a disturbance record, and a neighbourhood with loss in it is a neighbourhood where canopy came down, for whatever reason. The authors report 99.6% user's accuracy and 87.1% producer's accuracy for loss in the humid tropics (Hansen et al. 2013, supplementary materials); accuracy is lower for small clearings and in the year immediately after a loss event. Version 1.13 carries loss years 2001 to 2025, and the publishers note that the whole series was not reprocessed under the current method.

Where the Global Forest Watch source cannot be read it reports available: false naming the source, like any other absence.

4c. Settlement

Every finding carries a settlement block: whether the building record shows a structure inside the boundary, and separately within 100 m of it, in 2016, 2020 and 2023. A smallholding seen on sub-metre imagery shows a farmstead, a yard and footpaths that no 10 to 25 m canopy instrument resolves, and shade cocoa under a thinned upper storey reads as degraded forest on all of them. A building standing inside the plot in 2020 is evidence that the ground was settled before the cutoff.

It is supporting evidence, not part of the finding. It changes no verdict, no reason, no reasons and no finding sentence, and the same finding assessed with and without it is byte identical. A plot with a building on it can still have been cleared since, and a producer who lives in the village leaves no building on the farm. The inside and the ring are reported separately and never added: a village at the edge of a forest block is beside it.

"settlement": {
  "note": "Supporting evidence beside the finding, not part of it ...",
  "source": "Google Open Buildings 2.5D Temporal", "version": "v1",
  "licence": "CC BY 4.0", "attribution": "...", "citation": "W. Sirko ... arXiv:2310.11622, 2023.",
  "presence_threshold": 0.4,            // the record's own, Sirko et al. 2023 Table 11
  "threshold_source": "...", "min_building_m2": 32.0, "buffer_m": 100.0, "pitch_m": 2.0,
  "cutoff_year": 2020, "years_read": [2016, 2020, 2023],
  "available": true,
  "present_inside_at_cutoff": true,     // true, false, or null for not read
  "present_in_buffer_at_cutoff": false,
  "years": {
    "2020": {
      "available": true, "coverage": true,
      "inside": {"pixels": 16274, "valid_pixels": 16274, "read": true,
                 "presence_share": 0.00455, "building_m2": 296.0, "present": true,
                 "buildings_estimate": 16.8, "max_height_m": 5.1},
      "buffer": {"pixels": 33349, "valid_pixels": 33349, "read": true,
                 "presence_share": 0.0, "building_m2": 0.0, "present": false,
                 "buildings_estimate": 0.1, "max_height_m": null},
      "tiles": ["10474_2020_06_30/tile_uifYMcE8-1U.tif"], "epsg": 32631
    },
    "2016": {"...": "..."}, "2023": {"...": "..."}
  },
  "reading": "Buildings covering about 0.5% of the plot were present in 2020 and 2023 and none were detected in 2016."
}

The record is Google Open Buildings 2.5D Temporal v1: annual rasters of building presence, building height and a fractional building count, inferred by a model from stacks of Copernicus Sentinel-2 images centred on 30 June of each year, published at 0.5 m with an effective resolution of about 4 m, and covering Africa, South Asia, South-East Asia, Latin America and the Caribbean. It is dual-licensed CC BY 4.0 or ODbL 1.0; the block carries it under CC BY 4.0 with the attribution the licence asks for. Its GeoTIFF tiles are cloud-optimised and public, so each plot is read as one window off the bucket at 2 m and nothing is downloaded whole.

presence_share is the share of the region's read cells at or above the threshold, so it is a share of ground that was read rather than of ground that exists; valid_pixels against pixels says how much was read. The threshold is the record's own: the technical report's Table 11 gives 0.40 as the score that minimises built-up area error on its Africa validation set, and the block cites it. present is whether the built-up area clears 32 m2, two cells at the effective resolution, which is the floor at which a single bright cell or a tiling seam stops reading as a building. buildings_estimate is the spatial sum of the fractional count layer, which the record defines as a count; max_height_m is the tallest cell above the threshold. reading is one sentence naming the years buildings were seen inside the plot and the years they were not, and whether more stood in the ring.

A year that could not be read says so. Each of the three years is always present. available: false with a note naming the year means nothing was measured; coverage: false means the record publishes no tile over this ground; present: false means the year was read and the built-up area inside the region was under the floor. An absence is never served as a zero, and a failed read is asked for again on the next plot rather than remembered. present_inside_at_cutoff is null whenever 2020 could not be read.

What the record measures. The authors report a segmentation mIoU of 79 on their Africa validation set against 85.5 for the high-resolution model that taught it, and a building count R2 of 0.91 (0.84 on the harder Africa set). Small or scattered structures may be missed, adjoining buildings may merge, and the model's confidence for the same building can move between years, which is why three years are read side by side and the sentence names each one.

4d. History

Every finding carries a history block: what the Joint Research Centre's Tropical Moist Forest record (Vancutsem et al. 2021; Landsat, 1990 to 2025, 30 m, pan-tropical) classes the boundary as in the first year of its record and at the end of the cutoff year, and the year it first dates a change on each part of it. The instruments above read the canopy as it stands. Shade cocoa under retained forest trees reads as degraded forest on all of them. The regulation's question is about a date, and ground the record places outside moist forest in 1995 and in every year since answers that date in a way no canopy measurement can.

It decides one thing. Where the record classes at least half the plot as undisturbed moist forest at the end of 2020, a positive finding is withheld with reason landsat_record_reads_undisturbed_forest; on ground in Indonesia and Malaysia a record that could not be read withholds it with landsat_record_unavailable. Short forest, peat swamp forest among it, can read like a plantation on canopy height and radar, and the record separates them. Otherwise the block is read beside the finding and changes nothing in it.

post_cutoff_loss sits at the top level of the evidence object: where the record dates the loss of moist forest on at least 5% of the plot, and at least three of its 30 m pixels, to 2021 or later, it carries share, area_ha, years and source, and the document states it directly under the finding. It is null below that. It changes no verdict. The record reads some old plantations as forest, so under a positive finding the document says the loss is consistent with replanting; under any other finding it says this is the part of the plot the operator's risk assessment turns on. The register row carries it as post_cutoff_loss_share.

"history": {
  "note": "Supporting evidence, read beside the finding and not part of it ...",
  "source": "JRC Tropical Moist Forest (TMF) v1_2025", "version": "v1_2025",
  "resolution_m": 30, "record": "Landsat, 1990 to 2025",
  "layers": ["AnnualChange_1990", "AnnualChange_2020", "DeforestationYear", "DegradationYear", "TransitionMap_Subtypes"],
  "licence": "Free of charge, without restriction of use (JRC TMF data page); the journal article must be cited and the JRC acknowledged",
  "citation": "Vancutsem, C., F. Achard, J.-F. Pekel ... Science Advances 7(10): eabe1603 ...",
  "acknowledgement": "Source: European Commission, Joint Research Centre (JRC) ...",
  "definition": "Tropical moist forest in this product is closed evergreen and semi-evergreen forest ...",
  "cutoff_year": 2020,
  "checked": true, "available": true, "tile": "N10_W10", "pixels": 3136,
  "classes_1990": {"undisturbed_forest": 0.85, "degraded_forest": 0.04, "deforested_land": 0.0, "regrowth": 0.0, "water": 0.0, "other_land_cover": 0.11},
  "classes_2020": {"undisturbed_forest": 0.0, "degraded_forest": 0.02, "deforested_land": 0.80, "regrowth": 0.07, "water": 0.0, "other_land_cover": 0.11},
  "moist_forest_2020_share": 0.02,      // undisturbed plus degraded at the end of 2020
  "never_moist_forest_share": 0.11,     // no moist forest class in 1990 and no year dated since
  "deforestation_year": {
    "share_ever": 0.87, "share_before_2021": 0.87, "share_2021_or_later": 0.0,
    "year_min": 1988, "year_median": 1996, "year_max": 2004,
    "before_2021": {"year_min": 1988, "year_median": 1996, "year_max": 2004},
    "from_2021": {"year_min": null, "year_median": null, "year_max": null}
  },
  "degradation_year": {"...": "the same shape, from the DegradationYear band"},
  "last_moist_forest_year": {"median": 1995, "max": 2020, "share_at_cutoff": 0.02},
  "plantation_2025_share": 0.0,         // transition sub-types 81 to 86, the product's tree plantation classes
  "reading": "The Landsat record places 98.0% of this plot outside tropical moist forest at the end of 2020; ...",
  "note_on_read": null                  // set when few pixels fall inside, or when nothing could be read
}

The shares are of the 30 m pixels whose centre falls inside the boundary; pixels the product carries no class for are dropped rather than counted as other land cover, and pixels is the count that remains. The class names are the product's. DeforestationYear and DegradationYear are the product's own band names and give the year a pixel's first event began; share_before_2021 is the share of the plot dated 2020 or earlier and before_2021 the spread of those years, so a sentence about the pre-cutoff share never quotes a post-cutoff year. never_moist_forest_share is ground classed outside moist forest in 1990 with no year dated since: the product observed nothing before 1990, so "never" means never in its record. last_moist_forest_year is taken over the pixels that were ever moist forest, the cutoff year where the pixel still is and the year before its first DeforestationYear where it is not. reading is one to four sentences built from these numbers, each naming the record or its band. Under 12 pixels, note_on_read says how few there are.

Where it is read from. The JRC serves each 10 degree tile through a download endpoint that ignores HTTP byte ranges (measured 22 September 2026: a 1 KB range request returned the whole 114 MB tile), so a plot cannot be read from the JRC without a tile download per layer. The block reads the source.coop epoch/jrc-tmf re-packaging of the same tiles as cloud-optimised GeoTIFFs, which answers byte ranges and whose publisher states the pixel values are identical to the JRC source; the DeforestationYear window over every Ivorian and Ghanaian control block read identically from the mirror and from the JRC tile downloaded whole. The acknowledgement names both, as the mirror's terms ask. The product's own metadata bands for the start of monitoring and the count of valid observations exist only in its Earth Engine collection, which is not used, so no per-plot observation count is reported.

Ground the product does not cover says so. The product is pan-tropical, 86 tiles. A plot outside them carries checked: false with a note and no read is attempted. A tile that could not be read carries checked: false, available: false and a note naming the product, and nothing is remembered about it, so the next plot asks again. Neither is served as a plot with no history.

What the record measures. Tropical moist forest in this product is closed evergreen and semi-evergreen forest as Landsat sees it at 30 m; dry forest, open woodland and savanna fall in other land cover from the first year, so a plot the record never classes as moist forest may still have carried trees. Mature tree crops can read as moist forest and their felling cycles as dated events: over the SOGB rubber estate at Grand-Bereby, planted from the 1950s, the DeforestationYear band dates 77% of a 2 x 2 km window to 1984 to 2020 and the transition map classes 73% of it as tree plantation. The block reports the product's classes as the product gives them.

4e. Forest type in 2020 and published crop maps

Three blocks sit beside the finding.

forest_types reports the share of the plot the Commission's 2020 forest-type map (JRC Global map of forest types 2020, version 1, 10 m, CC BY 4.0) classes as primary, naturally_regenerating and planted forest, and as not_forest, each a fraction of the plot, with source, licence and citation. It decides one thing: where most of the plot is planted forest, a positive finding is withheld with reason planted_forest_2020, because the Regulation counts planted forest as forest and a planted timber stand can read like a plantation on the structural instruments. Measured on the control campaigns before it shipped, it withholds every Indonesian pulpwood block the instruments read as plantation and no oil palm, rubber or cocoa control. Where the map cannot be read the block carries available: false and a note, and nothing is withheld on its account.

commodity_maps reports what published crop maps place on the plot: oil_palm from Descals et al. 2024 (planted_by_2020, the share with a planting year of 2020 or earlier; median_planting_year; industrial_2021 and smallholder_2021 extent) and rubber from Ahrends et al. 2026 (rubber_2020, or outside_coverage: true outside Southeast Asia), with citations. Both CC BY 4.0. Supporting evidence: it changes no verdict.

whisp carries FAO's Whisp rating for perennial crops and, in words, the rule that produced it, where the deployment has it switched on; otherwise available: false.

4f. Next step

next_step is null on a positive finding and an object on every other one:

{"code": "archive_imagery_review",
 "note": "The open satellite record was read in full over this plot and stops short of a positive finding. A review of archive imagery dated before the cutoff, at sub-metre resolution, shows what stood on the ground on that date: planting rows, crown size, buildings and paths. ..."}

The open record is the pilot tier. A plot under the 4 ha floor carries the floor's own note; any other withheld finding carries the archive referral. The archive review returns a dated statement read from a sub-metre scene, filed with the same document; it never returns the imagery. code is stable and a pipeline may route on it.

The review's price travels beside the register, the book summary (format=json), every shipment and the archive coverage route as archive_review_price (review_price on coverage):

{"priced": true, "price_usd": 90.0, "volume_price_usd": 60.0, "volume_from_plots": 100,
 "delivery_days": 10,
 "line": "Archive review: $90 per plot, imagery included, delivered within 10 days; $60 per plot from 100 plots"}

Until a price is set the object reads {"priced": false, ..., "line": "Archive review: quoted on request"}. line is what every page and document prints.

4g. The state on every plot

Every register row, shipment plot and book summary row carries state, whatever the plot's size and whether or not it was read:

{"tone": "record", "label": "Record only, under the evidence floor",
 "summary": "Commission's 2020 maps: not forest; Landsat record: cleared before 2021; 5 of 5 seasons readable"}

It says what the record shows on the plot: how the Commission's 2020 forest maps class it, how the Landsat record classes it at the end of 2020, how many dry seasons of the open record could be read, and the finding where there is one. It is never a finding: it issues and withholds nothing, and verdict and reason are unchanged beside it.

tonelabelwhen
findingEstablished before the cutoffthe evidence carries a positive finding
findingCultivated on the archive scene datean archive reading on record supports cultivation before the cutoff
recordRecord only, under the evidence floorunder 4 ha; the record is shown in summary
forest_mappedForest on the 2020 mapsthe Commission's 2020 maps class most of the plot as forest
forest_mappedForest in the Landsat recordthe Landsat record classes most of the plot as undisturbed moist forest at the end of 2020
recordRecord onlyread, no finding, and neither of the above
recordNot read yet, Queued, Reading, Could not be readthe evidence has not been read; summary says so

The words in summary come from a fixed vocabulary: the maps read mostly forest, mostly planted forest, partly forest, not forest or not read; the Landsat record reads undisturbed forest at the end of 2020, degraded forest at the end of 2020, cleared before 2021, outside moist forest, mixed cover at the end of 2020, does not cover this ground or not read. A pipeline may branch on tone; label and summary are for display. The book summary counts plots by label (state_tones, state_labels) and its CSV carries state and record columns.

5. Verdicts

verdictmeaning
prior_management_evidencedestablished plantation before the cutoff; the record and the structural readings agree
prior_management_weakwithheld: supporting evidence only; reasons says what withheld it
no_prior_management_evidencedno annual canopy cycle over a well-observed record
insufficient_recordtoo few measured seasons; seasons_without_answer lists which

A withheld plot goes to a planting record, a title document or a field visit, with its reasons attached.

6. Reason codes

codeissued when
cycle_and_structure_agreeevery measured season wintered and the structural readings agree
structure_confirmed_single_troughone verified trough and a structural instrument confidently reads crop
shade_veto_overruled_by_two_confident_readingsshade read forest-like, radar and height both confidently crop
shade_veto_overruled_by_structure_and_recordshade read forest-like; one of radar and height confidently crop, and the Landsat record dates the clearing of most of the plot to 2015 or earlier with no moist forest or regrowth on it at the cutoff
shade_veto_overruled_by_structure_and_eu_mapsshade read forest-like; one of radar and height confidently crop, and both Commission 2020 maps (forest map V4, forest-type map) put at most a tenth of the plot in forest. Measured 2026-09-27: no forest block or inside-forest window cleared
structural_reading_says_foresta structural instrument read forest-like
cycle_not_consistentsome measured seasons wintered, others did not
no_structural_readingno structural instrument could answer
single_structural_reading_short_of_marginone instrument answered, without confidence
terrain_outside_characterised_rangeground steeper than the instruments were characterised on
evergreen_canopy_unsupportedno cycle, and a structural reading or the radar's polarimetry says cultivated
no_canopy_cycleno cycle over a well-observed record
record_too_thintoo few measured seasons
plot_below_evidence_floorthe plot is under 4 ha; rides in reasons beside the reason the instruments reached
inside_licensed_pulpwood_concessiona positive finding on an Indonesian plot that intersects a licensed pulpwood concession; the reason the instruments reached follows it in reasons
concession_layer_unavailablea positive finding on an Indonesian plot whose concession check could not be made; running the plot again settles it
landsat_record_reads_undisturbed_forestthe Landsat record classes most of the plot as undisturbed moist forest at the end of 2020
landsat_record_unavailableon ground in Indonesia or Malaysia, the Landsat record could not be read
planted_forest_2020the Commission's 2020 forest-type map classes most of the plot as planted forest, which the Regulation counts as forest; the reason the instruments reached follows it in reasons
eu_forest_maps_read_forestthe Commission's 2020 forest maps (the forest map V4, or the forest-type map's primary and naturally regenerating classes) class at least half the plot as forest; no positive finding is issued against them from the satellite record alone. Measured 2026-09-26 to withhold no established-crop control in any region

7. Plan limits

planplot evidence
Standardnone; the lane refuses with plan_tier_required, naming the plan that unlocks it
Trial200 plots in all, 30 days
Professional, Enterpriseuncapped

Alerts draw on the same allowance as register plots: one alert is one plot of evidence.

8. Exporting one boundary

GET /eudr/exoneration?aoi_id=<id>
GET /eudr/exoneration/export?aoi_id=<id>&format=pdf|compliance|csv|json

The export is built by calling the JSON route, so the file and the API answer come from one computation. pdf is the analyst report; compliance is the version written for a competent authority or an auditor reading it with nobody present to explain it, and takes operator, plot_ref and commodity for its cover; csv carries the per-season record with the caveats and scene identifiers in the file; json is the object as served. Any other value refuses with 422 bad_format, a layer with no trajectory refuses with 422 bad_layer, and a boundary outside your workspace answers 404 not_found.

The first call over a boundary queues the Landsat half of the record, once, and answers on Sentinel-2 alone until that read lands. landsat.state says which of the two states the answer is in.

9. What the evidence is

The verdict is a measurement of canopy behaviour before the cutoff, from public archives (Sentinel-2, Landsat through NASA HLS, ALOS PALSAR, the ETH 2020 height model, the Copernicus DEM). Every figure is reproducible from the scene identifiers in the object. Every archive is global, so a plot anywhere is read and answered with the same object; the radar names which mirror served it under radar.mirror, and the calibration block names what has been measured where it sits. The risk block carries the Article 10 context described in section 4b, from sources named and licensed on every response.

Validated on 122 boundaries in Côte d'Ivoire (cocoa, rubber, oil palm, natural forest), measured 16 September 2026: none of 57 forests cleared, and 51 of 65 established plantations reaching a filable finding. A re-run on 18 September 2026 over 30 forest windows of evidence-floor size cleared none of them, a second and smaller sample beside the 0 of 57.

Validated on 133 blocks in Malaysia (oil palm, natural forest, Johor and Sabah): none of 30 forests cleared; 22 of 30 established stands reaching a filable finding, re-measured 22 September 2026.

Validated on 60 blocks in Ghana (cocoa, oil palm, natural forest): none of 45 forests cleared; 12 of 16 commercial oil palm stands and 2 of 40 cocoa plots of 4 ha or more reaching a filable finding, re-measured 23 September 2026. Ghanaian cocoa is shaded agroforestry, which this method withholds, and the two commodities are reported separately for that reason.

Validated on 180 blocks in Indonesia (oil palm, natural forest, Riau, North Sumatra and Kalimantan): none of 19 forest blocks outside licensed timber plantation concessions cleared, the licensed stands inside them being planted forest the EU forest layer counts as forest; 34 of 45 established oil palm stands reaching a filable finding, re-measured 22 September 2026.

All of these hold from 4 ha up, on a plot drawn inside one cover. Other regions read today and are calibrated on request; nothing is quoted for a region before its own control campaign has run.

10. Shipments

A shipment, or import batch, groups the plots one consignment draws on, with the commercial facts you give for it. A plot can sit in several shipments. A shipment produces two things: the shipment report (one PDF per batch) and the due diligence statement assembled from it. Nothing here files anything: the statement is filed from your own TRACES account, as for every statement (/portal/statements).

GET    /portal/statements/shipments                       list, with counts per batch
POST   /portal/statements/shipments                       create
GET    /portal/statements/shipments/{id}                  the batch, its plots as register rows, its producers
PATCH  /portal/statements/shipments/{id}                  change any field
DELETE /portal/statements/shipments/{id}                  remove the batch; plots and statements stay
POST   /portal/statements/shipments/{id}/plots            add plots
POST   /portal/statements/shipments/{id}/plots/remove     remove plots {"plot_ids": [...]}
DELETE /portal/statements/shipments/{id}/plots/{plot_id}  remove one plot
GET    /portal/statements/shipments/{id}/report.pdf       the shipment report
GET    /portal/statements/shipments/{id}/statement?format=json|soap|xml|geojson
POST   /portal/statements/from-shipment                   assemble and keep a statement from a batch

Create takes reference (required, your own, at most 50 characters, unique in the workspace), commodity, description_of_goods, hs_code (2 to 8 digits; dots and spaces are dropped), quantity and unit (kg, t or m3), destination_country (the EU member state the goods are placed on the market in), activity_type (IMPORT by default, or EXPORT, DOMESTIC, TRADE), shipped_on and arrives_on (YYYY-MM-DD), producer_ids (producers named on the batch beside those of its plots), species and note. It may also carry plots in the same shapes as the add route. Nothing missing is defaulted: these are your commercial facts, and a field not given is reported on the statement as waiting on you.

Species, for wood. species is a list of {"scientific_name", "common_name"}, or of plain strings read as the scientific name. Article 9(1)(a) and Annex II point 2 of Regulation (EU) 2023/1115 ask for the full scientific name and the common name of every species in a product made of wood, and the Information System makes the scientific name mandatory for every product under the commodity wood and checks it against the EPPO list (Commission FAQ, version 5, question 7.27). Each name is 1 to 100 characters and a statement takes at most 500 entries, as the Commission's schema states; an entry with no scientific name answers 422 species_scientific_name_required. A batch is wood when its commodity is wood or timber, or, with no commodity given, when its HS code starts with 44, 47, 48, 49, 9401, 9403 or 9406. For a wood batch the statement carries speciesInfo in the schema's place (after hsHeading, before producers), a wood heading with no species is reported among the problems as waiting on you, and the statement page of the shipment report names the species field and the producers' country as the country of harvest. The species are your declaration about the consignment. POST /portal/statements and PATCH /portal/statements/{id} take the same species field, and the statement rows carry it back.

Add plots takes any of {"plot_ids": [...]}, {"refs": [...]} (your own plot references) and {"producer_id": "..."} (every plot of that producer), together if you like, and answers {"added", "already_in", "not_found", "n_not_found", "n_plots"}. A reference or id not in your register is listed in not_found and nothing is guessed. A plot that cannot be filed stays in the batch; the statement names it among its problems rather than dropping it.

The shipment report is what the record shows on the plots in this shipment: every plot with its state (section 4g), its finding or the reason it is withheld and its next step; the share with a finding, the share withheld and why, and the share not read yet, each as a count of the batch; the open cases on those plots where the workspace keeps a case register; the sources; the limitations; and a last page that lays out the statement's fields in the order the Information System asks for them, filled from the batch and the register and checked by the same assembly the statements router runs, with whatever is still missing named. It is one input to the operator's own due diligence and makes no claim beyond what the record shows.

The statement, GET .../statement?format=json, answers {"ready", "schema_checked", "problems", "payload"} without storing anything; soap, xml and geojson are the same files a stored statement exports and answer 409 not_ready with the problems until the statement can be built. POST /portal/statements/from-shipment {"shipment_id": "...", "internal_reference": "..."} stores it as a statement (the reference defaults to the shipment's), links it to the batch, and from there it is filed and recorded like any other.

statuscodemeaning
404not_foundno such shipment, plot or producer in this workspace
409duplicate_referencea shipment or statement with that reference already exists
409not_readythe statement cannot be built yet; the message lists what is missing
422reference_required, reference_too_longthe reference is missing or over 50 characters
422bad_hs_code, bad_quantity, bad_unit, bad_destination, bad_activity, bad_datea field that a statement would refuse, refused here first
422bad_species, species_scientific_name_requireda species entry the statement's schema would refuse, or one with no scientific name
422no_plots, too_many_plotsnothing to add, or more than 5,000 plots in one call

11. Webhooks

Register a URL and we POST to it when your work lands, so an integration is told on completion instead of polling the summary. Endpoints are made from the portal under Settings, API access, or through these routes, with a key or a session:

GET    /portal/webhooks                            endpoints (secrets as hints), the events, signing and retry
POST   /portal/webhooks                            {"url", "events"?, "label"?} → the endpoint and its secret, once
PATCH  /portal/webhooks/{id}                       change url, events, label or active
DELETE /portal/webhooks/{id}                       remove it and its delivery log
POST   /portal/webhooks/{id}/rotate-secret         a new secret, once; the old one stops verifying at once
POST   /portal/webhooks/{id}/test                  send a webhook.test event now; answers what your server said
GET    /portal/webhooks/deliveries?endpoint_id=&status=&limit=   the delivery log, newest first
GET    /portal/webhooks/deliveries/{id}            one delivery with the event it carried
POST   /portal/webhooks/deliveries/{id}/retry      send a finished delivery again now

The URL must be https and must resolve to a public address; it is checked when saved and again before every attempt, and redirects are not followed. Up to five endpoints per workspace. events defaults to all of them:

eventsent whendata carries
plot.evidence.completeda plot's evidence is storedplot_id, ref, commodity, area_ha, result (the result code, section 5), finding (true on a positive finding), reason, reasons, next_step (section 4f), issued_at, evidence_url, document_url
plot.evidence.faileda plot could not be readplot_id, ref, error in words, next_step (run_again), failed_at
batch.completedthe last plot on a shipment without a finished read is read; sent again if a plot on it is read againshipment_id, reference, commodity, plots, read, failed, findings, without_finding, shipment_url, report_url
alert.openeda case opens on the alert queuealert_id, plot_id, ref, monitor_id, opened_at, severity, flagged_area_km2, alert_url
archive.reading.fileda dated reading from a sub-metre scene is filed against a plotplot_id, ref, reading (as archive_review carries it on the evidence route), evidence_url, document_url

Each delivery is one JSON body, {"id", "type", "created", "workspace_id", "data"}, with these headers:

X-Axine-Event: plot.evidence.completed
X-Axine-Event-Id: evt_...            the same on every attempt; deduplicate on it
X-Axine-Delivery: <uuid>             one per endpoint per event
X-Axine-Timestamp: 1791014400        Unix seconds, when this attempt was signed
X-Axine-Signature: v1=<hex>          HMAC-SHA256 of "<timestamp>.<raw body>" under the endpoint's secret

Verify over the raw bytes as received, before parsing, and refuse a timestamp more than 300 seconds from your clock: the timestamp is inside what is signed, so a captured delivery cannot be replayed. Examples in Python and Node are on the developers page (/developers).

A 2xx answer delivers the event. Anything else, a timeout (10 seconds) or a refused connection is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours, seven attempts in all, then the delivery is marked failed. Delivery is at least once. The log keeps every delivery for 30 days with status (pending, sending, delivered, failed), attempts, last_status_code, last_error and the times of the last and next attempts.

statuscodemeaning
404not_foundno such endpoint or delivery in this workspace
409webhook_limitthe workspace has five endpoints
409in_flightthe delivery is still being tried
422bad_urlnot https, credentials in the URL, or a host that is not a public address; the message says which
422bad_eventsan event name not in the table above