Skip to main content
Before you can register a batch or generate a Due Diligence Statement, you must upload the GeoJSON farm-boundary polygons that define where your commodity was grown. AgriBackup runs each polygon through Copernicus satellite imagery and assigns a deterministic EUDR compliance status. Because screening can take several seconds to minutes, the ingestion endpoint returns 202 Accepted immediately and processes the work asynchronously. You track progress with a job ID returned in the response body, and receive a polygon.verified webhook event when the job completes.
Ingestion accepts up to 5,000 features per request. If your supply chain exceeds this limit, split the payload into multiple requests and include a unique Idempotency-Key header for each one.

Prerequisites

  • A live or sandbox API key (prefix sk_live_ or sk_test_).
  • Your farm boundaries exported as a GeoJSON FeatureCollection conforming to RFC 7946.
  • A registered webhook endpoint to receive the polygon.verified completion event (see Webhooks Setup).

GeoJSON format requirements

Each feature in your features array must satisfy the following:
  • type must be "Feature".
  • geometry.type must be "Polygon" or "MultiPolygon".
  • Coordinates follow [longitude, latitude] order (RFC 7946 §3.1.1).
  • The first and last coordinate of each ring must be identical (closed ring).
  • Include a properties object with at minimum farmer_id, commodity, area, and unit.
Polygons with self-intersecting rings or coordinate arrays fewer than 4 positions are rejected synchronously with a 400 Bad Request. Validate your GeoJSON client-side before sending.
Here is a minimal valid feature:
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [36.8, -1.2],
            [36.9, -1.2],
            [36.9, -1.3],
            [36.8, -1.3],
            [36.8, -1.2]
          ]
        ]
      },
      "properties": {
        "farmer_name": "Global Coffee Farmer #1",
        "farmer_id": "TEST_FARMER_100",
        "plot_name": "Nyeri Hill Farm Block B",
        "area": 2.5,
        "unit": "HECTARES",
        "commodity": "Coffee"
      }
    }
  ]
}

Step-by-step ingestion

1

Submit the polygon ingestion request

Send a POST to /api/v1/enterprise/eudr/polygons with your GeoJSON features array. Include a UUID Idempotency-Key header so that retries on network failures do not create duplicate jobs.The endpoint responds with 202 Accepted and a JobAcceptedResponse body containing the jobId you will use to poll progress.
import agribackup
from agribackup.rest import ApiException
import uuid

configuration = agribackup.Configuration()
configuration.api_key['ApiKeyAuth'] = 'sk_live_YOUR_API_KEY'

polygon_payload = {
    "features": [
        {
            "type": "Feature",
            "geometry": {
                "type": "Polygon",
                "coordinates": [
                    [
                        [36.8, -1.2],
                        [36.9, -1.2],
                        [36.9, -1.3],
                        [36.8, -1.3],
                        [36.8, -1.2]
                    ]
                ]
            },
            "properties": {
                "farmer_name": "Global Coffee Farmer #1",
                "farmer_id": "TEST_FARMER_100",
                "plot_name": "Nyeri Hill Farm Block B",
                "area": 2.5,
                "unit": "HECTARES",
                "commodity": "Coffee"
            }
        }
    ]
}

with agribackup.ApiClient(configuration) as api_client:
    try:
        polygons_api = agribackup.EnterprisePolygonsApi(api_client)
        job = polygons_api.ingest_polygons(
            idempotency_key=str(uuid.uuid4()),
            polygon_ingestion_request=polygon_payload
        )
        print("Job accepted:", job.job_id)
        print("Estimated duration (s):", job.estimated_duration_sec)
    except ApiException as e:
        print("Error:", e)
import { AgriBackupClient } from '@agribackup/sdk';
import { randomUUID } from 'crypto';

const client = new AgriBackupClient({ apiKey: 'sk_live_YOUR_API_KEY' });

const polygonPayload = {
  features: [
    {
      type: 'Feature',
      geometry: {
        type: 'Polygon',
        coordinates: [
          [
            [36.8, -1.2],
            [36.9, -1.2],
            [36.9, -1.3],
            [36.8, -1.3],
            [36.8, -1.2]
          ]
        ]
      },
      properties: {
        farmer_name: 'Global Coffee Farmer #1',
        farmer_id: 'TEST_FARMER_100',
        plot_name: 'Nyeri Hill Farm Block B',
        area: 2.5,
        unit: 'HECTARES',
        commodity: 'Coffee'
      }
    }
  ]
};

async function ingestPolygons() {
  try {
    const job = await client.polygons.ingest(polygonPayload, {
      idempotencyKey: randomUUID()
    });
    console.log('Job accepted:', job.jobId);
    console.log('Estimated duration (s):', job.estimatedDurationSec);
  } catch (error) {
    console.error('Error:', error.message);
  }
}

ingestPolygons();
curl --request POST \
  --url https://live.agribackup.com/api/v1/enterprise/eudr/polygons \
  --header 'X-API-Key: sk_live_YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000' \
  --data '{
    "features": [
      {
        "type": "Feature",
        "geometry": {
          "type": "Polygon",
          "coordinates": [
            [
              [36.8, -1.2],
              [36.9, -1.2],
              [36.9, -1.3],
              [36.8, -1.3],
              [36.8, -1.2]
            ]
          ]
        },
        "properties": {
          "farmer_name": "Global Coffee Farmer #1",
          "farmer_id": "TEST_FARMER_100",
          "plot_name": "Nyeri Hill Farm Block B",
          "area": 2.5,
          "unit": "HECTARES",
          "commodity": "Coffee"
        }
      }
    ]
  }'
A successful response looks like this:
{
  "jobId": "job_998877",
  "status": "PENDING",
  "estimatedDurationSec": 3,
  "createdAt": "2026-06-15T12:00:00Z"
}
The Location response header also contains the job-status URL: /api/v1/enterprise/eudr/jobs/job_998877.
2

Poll the job status

Query GET /api/v1/enterprise/eudr/jobs/{jobId} to track processing phases. The job moves through the following phase values before finishing:
PhaseMeaning
IMPORTINGRaw GeoJSON is being parsed and stored
ANALYZINGCopernicus satellite imagery check in progress
ANCHORINGCompliance results being anchored on Hedera
GENERATING_CERTSCryptographic certificates being issued
COMPLETEDAll polygons processed; polygonIds are ready
FAILEDOne or more fatal errors; check errors[]
Poll at a reasonable interval (for example, every 5 seconds) until phase is COMPLETED or FAILED.
import agribackup
from agribackup.rest import ApiException
import time

configuration = agribackup.Configuration()
configuration.api_key['ApiKeyAuth'] = 'sk_live_YOUR_API_KEY'

with agribackup.ApiClient(configuration) as api_client:
    jobs_api = agribackup.EnterpriseJobsApi(api_client)
    job_id = "job_998877"

    while True:
        try:
            status = jobs_api.get_job_status(job_id=job_id)
            print(f"Phase: {status.phase} — processed {status.processed_records}/{status.total_records}")

            if status.phase in ("COMPLETED", "FAILED"):
                break

            time.sleep(5)
        except ApiException as e:
            print("Error polling job:", e)
            break

    if status.phase == "COMPLETED":
        print("Compliant polygons:", status.compliant_units)
        print("High-risk polygons:", status.high_risk_units)
    else:
        for error in status.errors:
            print(f"Error [{error.code}] on {error.field}: {error.reason}")
import { AgriBackupClient } from '@agribackup/sdk';

const client = new AgriBackupClient({ apiKey: 'sk_live_YOUR_API_KEY' });

async function pollJob(jobId) {
  while (true) {
    const status = await client.jobs.getStatus(jobId);
    console.log(`Phase: ${status.phase} — processed ${status.processedRecords}/${status.totalRecords}`);

    if (status.phase === 'COMPLETED' || status.phase === 'FAILED') {
      return status;
    }

    await new Promise(resolve => setTimeout(resolve, 5000));
  }
}

async function main() {
  const status = await pollJob('job_998877');

  if (status.phase === 'COMPLETED') {
    console.log('Compliant polygons:', status.compliantUnits);
    console.log('High-risk polygons:', status.highRiskUnits);
  } else {
    status.errors.forEach(err =>
      console.error(`Error [${err.code}] on ${err.field}: ${err.reason}`)
    );
  }
}

main();
curl --request GET \
  --url https://live.agribackup.com/api/v1/enterprise/eudr/jobs/job_998877 \
  --header 'X-API-Key: sk_live_YOUR_API_KEY'
3

Receive the webhook completion event

Instead of polling, configure a webhook subscription for polygon.verified (see Webhooks Setup). AgriBackup posts the following payload to your endpoint when the job finishes:
{
  "eventType": "polygon.verified",
  "eventId": "evt_9988776655",
  "timestamp": "2026-06-15T12:00:45Z",
  "attempt": 0,
  "data": {
    "jobId": "job_998877",
    "polygonsVerified": 150,
    "polygonsFailed": 3,
    "status": "COMPLETED",
    "polygonIds": ["poly_abc123", "poly_def456"]
  }
}
Store the polygonIds array — you will pass these IDs into the batch registration request.
4

Check individual polygon compliance status

Before linking a polygon to a batch, confirm its eudrStatus by calling GET /api/v1/enterprise/eudr/polygons/{polygonId}.
import agribackup
from agribackup.rest import ApiException

configuration = agribackup.Configuration()
configuration.api_key['ApiKeyAuth'] = 'sk_live_YOUR_API_KEY'

with agribackup.ApiClient(configuration) as api_client:
    try:
        polygons_api = agribackup.EnterprisePolygonsApi(api_client)
        polygon = polygons_api.get_polygon_status(polygon_id="poly_abc123")
        print("Status:", polygon.eudr_status)
        print("Area (ha):", polygon.area_hectares)
    except ApiException as e:
        print("Error:", e)
import { AgriBackupClient } from '@agribackup/sdk';

const client = new AgriBackupClient({ apiKey: 'sk_live_YOUR_API_KEY' });

async function checkPolygon(polygonId) {
  try {
    const polygon = await client.polygons.getStatus(polygonId);
    console.log('Status:', polygon.eudrStatus);
    console.log('Area (ha):', polygon.areaHectares);
  } catch (error) {
    console.error('Error:', error.message);
  }
}

checkPolygon('poly_abc123');
curl --request GET \
  --url https://live.agribackup.com/api/v1/enterprise/eudr/polygons/poly_abc123 \
  --header 'X-API-Key: sk_live_YOUR_API_KEY'
The response body uses the PolygonStatusResponse schema:
{
  "polygonId": "poly_abc123",
  "commodity": "Coffee",
  "eudrStatus": "COMPLIANT",
  "areaHectares": 2.5,
  "createdAt": "2026-06-15T12:00:00Z"
}

Polygon compliance statuses

eudrStatusMeaningNext step
PENDINGSatellite analysis not yet completeWait for the polygon.verified webhook
COMPLIANTNo deforestation signal detectedInclude polygonId in a batch
HIGH_RISKDeforestation signal detectedDo not include in a batch; investigate with your supplier
Only polygons with eudrStatus: COMPLIANT may be included in a batch registration. Attempting to include a PENDING or HIGH_RISK polygon returns a 400 Bad Request.
Use GET /api/v1/enterprise/eudr/polygons?status=COMPLIANT to list all verified compliant polygons for your organization, ready to reference in batch registration.