Assessments on Ingestion Responses

Including assessments on ingestion responses

The fastest way to read a location's assessments alongside its ingestion record is the opt-in assessments include on the asset-results endpoint:

curl '/ingestions/assets/{asset_task_id}?include=assessments' \
  -H 'x-api-key: ...'

{asset_task_id} is the id returned when you upload (POST /ingestions/assets) or process (PUT /ingestions/assets) a batch. The response is the normal paginated asset-results envelope; each asset gains a relationships.assessments object carrying every assessment partition for that location in one fetch โ€” no second round-trip per partition. This is usually the first integration point for a new consumer: ingest, then read the confidence score, hazard tier, distances, and EUDR risk levels off the same record.

For deeper queries against a single partition โ€” filtering by risk level, paginating one row per applicable law, or refreshing โ€” use the dedicated /assessments/{location_type}/{slug} endpoints. This include is the wide, one-row-per-location view; those slugs are the narrow, filterable, per-partition view. Both read the same underlying assessment rows.

The same aggregate is also available on the locations surface and on the DDS plot endpoints. All three surfaces share one schema, so a partition looks identical wherever you read it.

On the locations surface

The locations endpoints carry the same aggregate under the opt-in assessment include โ€” note the singular assessment here (the ingestion surface uses the plural assessments; they address the same rows):

# Every location in a list
curl '/locations?include=assessment' -H 'x-api-key: ...'
# A single location (also works on the typed convenience routes)
curl '/locations/plots/{id}?include=assessment' -H 'x-api-key: ...'
curl '/locations/{id}?include=assessment'        -H 'x-api-key: ...'

The include is valid on the list routes (GET /locations, /locations/facilities, /locations/sheds, /locations/plots, /locations/commodity/{commodity}) and the single route (GET /locations/{id}). Each location gains a relationships.assessment object with the identical partition shape documented below.

The base list/single response is gated on locations:read; the three EUDR-scope partitions (art9, art10, eudr_profile) are additionally gated on assessments:read, exactly as on the ingestion surface โ€” see EUDR-scope gate. Without include=assessment the response is unchanged (the relationship is omitted entirely). For deeper per-partition queries โ€” filtering by risk level, paginating one row per applicable law, or refreshing โ€” use the dedicated /assessments/{location_type}/{slug} endpoints.

Response shape

relationships.assessments.attributes carries one slot per partition. Every slot is nullable โ€” a slot is null when the partition was never produced for that location, when your organisation can't see it, or (for the EUDR-scope partitions) when your key lacks the scope to read it (see EUDR-scope gate).

{
  "data": [
    {
      "type": "asset",
      "id": "โ€ฆ",
      "attributes": { "location_type": "plot", "status": "completed", โ€ฆ },
      "relationships": {
        "assessments": {
          "type": "assessments",
          "attributes": {
            "core":                 { "confidence_score": 0.91, "confidence_level": "high", "flags": { โ€ฆ }, "sco2_run_at": "2026-01-12T08:04:11Z" },
            "hazard":               { "flags": { "natural_hazard_risk_level": "low", "flood_zone_pct": 4.2, โ€ฆ }, "sub_options": null, "sco2_run_at": "โ€ฆ" },
            "infrastructure":       { "distances": { โ€ฆ }, "shed": { โ€ฆ }, "climate": { โ€ฆ }, "sub_options": { โ€ฆ }, "sco2_run_at": "โ€ฆ" },
            "deforestation_screen": { "flags": { "deforestation": { โ€ฆ }, "deforestation_check_failed": false }, "sub_options": null, "sco2_run_at": "โ€ฆ" },
            "art9":                 { "flags": { "legality_risk_level": "low", โ€ฆ }, "sub_options": null, "sco2_run_at": "โ€ฆ" },
            "art10":                { "flags": { "article10_risk_level": "low", โ€ฆ }, "sub_options": null, "sco2_run_at": "โ€ฆ" },
            "eudr_profile":         { "flags": { "eudr_country_risk_tier": "standard", โ€ฆ }, "sub_options": null, "sco2_run_at": "โ€ฆ" },
            "legality_risk_level":  "low",
            "article10_risk_level": "low",
            "failure_reason":       null
          }
        }
      }
    }
  ],
  "meta": { "asset_task_id": "โ€ฆ", "overall_status": "completed", โ€ฆ },
  "links": { "self": "โ€ฆ" }
}

The partition envelope

Most partitions share one envelope shape:

KeyMeaning
flagsThe partition's data keys (a strict, enumerated object โ€” unknown keys are dropped, never echoed).
sub_optionsSnapshot of the check_* sub-options active when the assessment was produced. Only infrastructure populates it; null everywhere else.
sco2_run_atISO 8601 timestamp of when the row was last produced.

Two partitions deviate:

  • core promotes its headline fields to the top of the slot โ€” confidence_score, confidence_level, confidence_comment, producer_confirmation_score, producer_confirmed, producer_confirmation_comment โ€” with the remaining validation keys under flags. (confidence_score / confidence_level come from dedicated columns and are not duplicated in flags.)
  • infrastructure replaces the flags grab-bag with three named sub-objects โ€” distances, shed, climate โ€” sourced across partitions. See the Infrastructure section below.

Partitions at a glance

Slot (response key)Public slugScopeContents
corecoreingestionConfidence score/level + geometric & facility-plausibility flags.
hazardhazardingestionNatural-hazard risk + physical-risk metrics (flood, heat, water stress, grid distance).
infrastructureinfrastructureingestionNearest-feature distances (distances), supply-shed footprint (shed), climate overlay (climate).
deforestation_screendeforestation-screeningestionValidation-time deforestation summary + deforestation_check_failed flag.
art9legalityEUDRArticle 9 isochrone deforestation + legality columns.
art10country-riskEUDRArticle 10 procedural-risk scoring + corruption index.
eudr_profileeudr-profileEUDRGemini facility profile + Article-29 country tier.
legality_risk_levelโ€”ingestionHeadline Article 9 level, promoted from art9 (always surfaced).
article10_risk_levelโ€”ingestionHeadline Article 10 level, promoted from art10 (always surfaced).
failure_reasonโ€”ingestionPer-asset processing failure reason, or null.

The response keys are the internal partition names (art9, art10). The regulation-neutral public slugs (legality, country-risk, eudr-profile) are the URL segments on the /assessments/* endpoints. They address the same rows.

EUDR-scope gate

The base response is gated on ingestions:read (or locations:read on the locations surface). The three EUDR-scope partitions โ€” art9 (legality), art10 (country-risk), and eudr_profile โ€” are additionally gated on assessments:read:

  • A key with assessments:read (or an unrestricted key) sees these slots populated when the data exists.
  • A key without assessments:read always sees them as null โ€” indistinguishable from "no such row." We don't reveal that gated data exists.

The two promoted scalars โ€” legality_risk_level and article10_risk_level โ€” stay ungated: they're always surfaced under the base scope so a list or table row carries the headline EUDR risk levels without a second fetch. The full detail (sources, applicable laws, scoring breakdown) stays on the gated art9 / art10 slots and the dedicated /assessments/{location_type}/legality and /country-risk slugs.

How the EUDR-scope partitions get populated varies: art9 / art10 only by a DDS create (POST /regulations/eudr/audit-trail); eudr_profile either by opting into include_eudr_profile on a plain ingestion (POST / PUT /ingestions/assets) or force-run at DDS creation. An ingestion that enables only include_hazard / include_infrastructure / include_core (i.e. without include_eudr_profile) leaves all three null regardless of scope. See assessments-overview.md for how each partition is produced.

Field reference

The field-by-field reference for the ingestion-scope partitions lives in assessment-fields.md โ€” the keys are identical here, nested under each slot's flags (or, for core, split between the promoted top-level fields and flags):

The two partitions split out after that page was written โ€” eudr_profile and deforestation_screen โ€” are documented here.

infrastructure

infrastructure projects three named sub-objects rather than a flat flags object:

Sub-objectKey fields
distancesnearest_water_km, nearest_port_km, nearest_road_km, port_road_km, nearest_port_name, nearest_port_address, nearest_port_point, nearest_water_point, nearest_grid_km, nearest_grid_point.
shedshed_area_ha, commodity_area_ha, natural_forest_area_ha.
climateflood_zone_pct, flood_event_count, precip_trend_pct, water_stress_score, extreme_heat_wbgt.

Which distance keys appear depends on the check_* flags set at request time; sub_options records that snapshot. Field descriptions: Infrastructure group.

eudr_profile flags (EUDR-scope)

The opt-in EUDR enrichment split out of art10: the Gemini facility extractor outputs plus the deterministic Article-29 country classification.

FieldTypeDescription
facility_descriptionstringFree-text facility profile description.
facility_profileobject | stringStructured Gemini facility profile (upstream-owned, shape-variant).
eudr_country_risk_tierstringEUDR Article-29 country tier โ€” low | standard | high.
eudr_article_29_citationstringCitation for the Article-29 classification in force.
eudr_article_29_effective_datestringEffective date of that classification (YYYY-MM-DD).
eudr_assessment_typestringDerived assessment-type discriminator.

deforestation_screen flags (ingestion-scope)

The validation-time deforestation summary, promoted out of core into its own partition. Opt in with include_deforestation on either ingestion endpoint. The reprocess PUT also accepts include_deforestation_screen, which reaches the same sco2api primitive but runs the type as a standalone full assessment and requires the eudr:write scope; the upload POST rejects it.

FieldTypeDescription
deforestationobjectsco2api deforestation summary โ€” { risk, deforestation_present, deforestation_area_ha } (upstream-owned, shape-variant).
deforestation_check_failedbooleantrue only when the upstream deforestation check errored; false on the happy path.

The summary's three fields come back null when the screen could not measure the location โ€” an unusable geometry, or an upstream reduce that errored. That is deliberately distinct from a measured zero (risk: "low", deforestation_present: false, deforestation_area_ha: 0), so read risk rather than treating a 0 area as a clean result.

The measurement differs by location type even though the shape does not. A facility's figure is deforestation over its supply-shed catchment (the isochrone, or a 50 km buffer when the isochrone solve fails). A plot's or shed's is deforestation over the uploaded polygon itself. The by_assessment progress for the group therefore folds from a different sco2api sub-task per type.

risk is a presence test, so do not compare the band across grains. It is high for any non-zero area and low for exactly zero โ€” the same rule at both grains, but over very different areas. A plot banded high has deforestation inside its own boundary. A facility banded high has an alert somewhere in its sourcing catchment, which can be ~785,000 ha when no isochrone was solved, over a roughly two-year alert window. Treat the facility band as a screening flag for follow-up, not as a finding about the site.

To rank facilities by severity, compute the annual rate of natural forest lost rather than reading the band: deforestation_area_ha รท natural_forest_area_ha, divided by the window ending at deforestation_monitoring_end_year (the EUDR cutoff is 31 Dec 2020). All three fields ride the art9 partition the same run writes.

Two things that rate deliberately is not:

  • Not a cumulative share. Cumulative loss climbs on its own as the 2020 cutoff recedes, so two catchments measured in different years stop being comparable. Divide by the window.
  • Not a share of shed_area_ha. The loss is a strict subset of the natural-forest extent, so that extent is the denominator that means something; catchment area is context, not a divisor.

There is deliberately no finer band than low/high on either grain, because the thresholds have never been calibrated.

Booleans are JSON booleans

Presence and access flags (water_access, port_proximity, road_access, poi_presence, high_density on core.flags, and deforestation_check_failed on deforestation_screen.flags) are real JSON true / false, not the 0 / 1 integers sco2api emits upstream โ€” fm-api coerces them at ingestion. Tri-state gradients (commodity_presence, building_presence) stay numeric (0 | 0.5 | 1) on purpose.

core.flags.commodity_invalid is true on a row whose commodity cell names no supported commodity, and absent otherwise. See The commodity column.

Back-compatibility

Without include=assessments the response is unchanged: assets carry no assessments relationship. The include is purely additive and opt-in; existing integrations that don't request it see the same payload as before.