AgriBackup SDKs & Quickstart
AgriBackup provides several SDKs and libraries to help you integrate with AgriBackup's APIs across different platforms and languages. Whether you're building a server-side application, a web frontend, or a mobile app, you can use our official libraries to securely interact with AgriBackup, reduce boilerplate code, and access the latest features.
End-to-End Enterprise Quickstart
- Python
- Node.js
- Java
- C# (.NET)
Install the package via pip:
pip install agribackup
Usage
The package needs to be configured with your account's API key, which you can get at https://agribackup.com. The SDK automatically routes your requests to the correct environment (Sandbox or Production) based on your key's prefix.
With this decoupled architecture, the SDK cleanly separates the environment setup (Webhooks) from the actual physical compliance flow.
Phase 1: Pre-assessment (The Sandbox Check)
Action: client.risk_management.assess_coordinate_risk(...)
Purpose: A rapid, synchronous Boolean check to verify if a coordinate is in a deforested zone before you spend capital or compute on heavy satellite ingestion.
from agribackup.client import AgriBackupClient
from agribackup.models import CoordinateRiskRequest
from agribackup.exceptions import ApiException
def assess_coordinate_risk():
client = AgriBackupClient(api_key="sk_test_YOUR_API_KEY")
risk_check = client.risk_management.assess_coordinate_risk(
coordinate_risk_request=CoordinateRiskRequest(latitude=-1.246807, longitude=36.743217)
)
if risk_check.deforestation_detected:
print("Deforestation detected. Cannot proceed.")
else:
print(f"Coordinate is safe. Risk level: {risk_check.country_risk_level}")
print(risk_check)
if __name__ == "__main__":
assess_coordinate_risk()
Phase 2: Event-Driven Infrastructure (One-Time Setup)
Action: client.webhooks.register_webhook(...)
Purpose: Establishes the enterprise routing for asynchronous fulfillment. You register your ERP endpoint to listen for polygon.verified, batch.risk_assessed, shipment.linked, and dds.submitted.
## Register your webhook endpoint once during system startup
from agribackup.models import WebhookRegistrationRequest
request = WebhookRegistrationRequest(
target_url="https://your-erp.internal.co/api/webhooks/agribackup",
event_types=["batch.risk_assessed", "polygon.verified", "shipment.linked", "dds.generated", "dds.submitted"]
)
registration = client.webhooks.register_webhook(request)
print(f"Webhook Secret (Save securely!): {registration.signing_secret}")
Webhook Event Payloads
Every webhook shares a common envelope (eventType, eventId, timestamp, attempt, nextRetry, data). Below are the schemas for the inner data object for each event:
polygon.verified:{ jobId, polygonsVerified, polygonsFailed, status, polygonIds }batch.risk_assessed:{ batchId, batchCode, workflowId, riskScore, classification, status }shipment.linked:{ batchId, shipmentReference, transactionHash }dds.generated:{ jobId, batchId, ddsReference, status, error }dds.submitted:{ batchId, ddsReference, status }dds.validated:{ batchId, ddsReference, validationTimestamp, status }dds.rejected:{ batchId, ddsReference, rejectionReason, status }job.failed:{ jobId, jobType, errorCode, errorMessage }report.ready:{ reportId, reportType, downloadUrl }
Verifying Incoming Webhooks
Use your signing_secret to cryptographically verify that incoming webhooks originated from AgriBackup:
import hmac
import hashlib
def verify_webhook(signature_header: str, raw_body_string: str, secret: str) -> bool:
expected_hash = hmac.new(
secret.encode('utf-8'),
raw_body_string.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected_hash, signature_header)
Alternative: Manual Polling If you prefer not to use webhooks, or need to manually verify a job's status, you can retrieve the job state at any time:
job_response = client.jobs.get_job_status("job-1234-uuid")
phase = job_response.phase
if phase == "COMPLETED":
print(f"Job completed! Compliant units: {job_response.compliant_units}")
elif phase == "FAILED":
print(f"Job failed. Errors: {job_response.errors}")
else:
print(f"Job is still processing. Current phase: {phase}")
Phase 3: The Complete EUDR Execution Lifecycle
This is the core operational loop where the decoupling shines.
- Ingest Polygons: Call
client.polygons.ingest_polygons(...). (Async: wait forpolygon.verifiedwebhook). - Register Batch: Call
client.batches.register_batch(...)using the verified polygon IDs. (Sync: returns batchId instantly). - Attach Documentation: Call
client.documents.upload(...)or equivalent to bind legal EUDR documents to the batchId. - Assess Batch Risk: Call
client.batches.assess_risk(...). (Async: wait forbatch.risk_assessedwebhook). - Link Logistics: Call
client.logistics.link_batch_to_shipment(...). (Async: wait forshipment.linkedwebhook). - Generate Declaration: Call
client.declarations.generate_dds(...). (Async: wait fordds.generatedwebhook). - Submit Declaration: Call
client.declarations.submit_dds(...). (Async: wait for TRACES NTdds.validatedwebhook).
This SDK structure gives you absolute deterministic control over the state machine of your agricultural supply chain.
Complete Lifecycle Implementation Example
from fastapi import FastAPI, Request, BackgroundTasks
from agribackup.client import AgriBackupClient
from agribackup.models import (
CoordinateRiskRequest, PolygonIngestionRequest, BatchRegistrationRequest,
ShipmentLinkRequest, DdsGenerationRequest, GeoJsonFeature, GeoJsonGeometry, FeatureProperties
)
import uvicorn
app = FastAPI()
client = AgriBackupClient(api_key="sk_test_YOUR_API_KEY")
## ==========================================
## Webhook Listener (Handling Async Events)
## ==========================================
@app.post("/api/webhooks/agribackup")
async def handle_webhook(request: Request, background_tasks: BackgroundTasks):
event = await request.json()
event_type = event.get("eventType")
data = event.get("data", {})
if event_type == "polygon.verified":
background_tasks.add_task(handle_polygon_verified, data)
elif event_type == "batch.risk_assessed":
background_tasks.add_task(handle_batch_risk_assessed, data)
elif event_type == "shipment.linked":
background_tasks.add_task(handle_shipment_linked, data)
elif event_type == "dds.generated":
background_tasks.add_task(handle_dds_generated, data)
elif event_type == "dds.submitted":
print(f"DDS Successfully Filed! Reference: {data.get('ddsReference')}")
return {"status": "OK"}
## ==========================================
## The State Machine Workflow
## ==========================================
## Step 1: Ingest Polygons (Triggered manually or via ERP)
def start_compliance_flow():
polygon_job = client.polygons.ingest_polygons(
polygon_ingestion_request=PolygonIngestionRequest(
features=[
GeoJsonFeature(
type="Feature",
geometry=GeoJsonGeometry(type="Polygon", coordinates=[[[36.8, -1.2], [36.9, -1.2], [36.9, -1.3], [36.8, -1.3], [36.8, -1.2]]]),
properties=FeatureProperties(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")
)
]
)
)
print(f"Polygon ingestion started. Job ID: {polygon_job.job_id}")
## Step 2 & 3: Register Batch & Attach Documents (Fired by polygon.verified webhook)
def handle_polygon_verified(data):
if data.get("status") != "COMPLETED":
return
batch = client.batches.register_batch(
batch_registration_request=BatchRegistrationRequest(
commodity="Coffee", country_code="KEN", hs_code="0901", quantity_kg=1500.0,
polygon_ids=data.get("polygonIds", []), vendor_ids=["ERP-VEND-991"]
)
)
# Step 4: Assess Batch Risk
client.batches.assess_risk(batch_id=batch.batch_id)
print(f"Batch risk assessment started for {batch.batch_id}")
## Step 5: Link Logistics (Fired by batch.risk_assessed webhook)
def handle_batch_risk_assessed(data):
if data.get("status") != "COMPLETED" or data.get("classification") == "HIGH_RISK":
return
client.logistics.link_batch_to_shipment(
shipment_link_request=ShipmentLinkRequest(
batch_id=data.get("batchId"), shipment_id="SHP-123", bill_of_lading="BOL-99281744", vessel_name="Evergreen"
)
)
print(f"Logistics linked for batch {data.get('batchId')}")
## Step 6: Generate Declaration (Fired by shipment.linked webhook)
def handle_shipment_linked(data):
# Note: shipment.linked webhook firing indicates success. No status check required.
dds = client.declarations.generate_dds(
dds_generation_request=DdsGenerationRequest(
batch_id=data.get("batchId"), legal_document_hashes=["e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"]
)
)
print(f"DDS generation started for batch {data.get('batchId')}. Job ID: {dds.job_id}")
# Awaits 'dds.generated' webhook...
## Step 7: Submit Declaration (Fired by dds.generated webhook)
def handle_dds_generated(data):
if data.get("status") != "COMPLETED":
return
dds_ref = data.get("ddsReference")
client.declarations.submit_dds(reference_id=dds_ref)
print(f"DDS submitted to TRACES. Awaiting validation for {dds_ref}...")
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
Cryptographic Evidence
AgriBackup anchors all critical compliance states to the Hedera Hashgraph DLT. You can retrieve immutable, cryptographically verifiable proofs of your compliance events at any time.
evidence = client.evidence.get_batch_ledger_evidence("batch-123")
print(evidence)
Interpreting the Evidence
The returned evidence object contains a chronological history of the entity's lifecycle anchored on-chain. It includes an array of state_proofs, where each proof represents a distinct compliance event (like CREATED or RISK_ASSESSED).
Key fields inside each state proof include:
hedera_transaction_id: The exact identifier on the Hedera Consensus Service. You can search this ID on any public Hedera explorer (e.g., Hashscan) to independently verify the transaction.consensus_timestamp: The decentralized, network-agreed time the event was permanently recorded.operation_type: The specific state transition that occurred.merkle_proof: The cryptographic data required to perform offline verification, ensuring the state has not been tampered with since anchoring.
Archiving Reports
AgriBackup supports asynchronous generation of bulk compliance archives. This compiles all XML payloads, Hedera state proofs, and legal document hashes into a single cryptographic ZIP artifact.
- Request the Archive: Call
client.archival.trigger_archive_reportwith a date range. - Await the Webhook: Wait for the
report.readywebhook. - Download: Use the provided URL or SDK method to securely retrieve the artifact.
from datetime import date
from agribackup.models import ArchiveReportRequest
## Step 1: Request Archive
report_req = client.archival.trigger_archive_report(
archive_report_request=ArchiveReportRequest(
start_date=date(2026, 1, 1),
end_date=date(2026, 3, 31)
)
)
print(f"Report job started: {report_req.report_id}")
## Step 2: Handle Webhook (fired by report.ready)
def handle_report_ready(data):
if data.get("reportType") != "COMPLIANCE_ARCHIVE":
return
print(f"Report is ready to download at: {data.get('downloadUrl')}")
# Optionally fetch it directly using the SDK:
# zip_buffer = client.archival.download_archive_report(report_id=data.get("reportId"))
Advanced Enterprise Configuration
Overriding Network Routing & Proxies
The client can be initialized with several options to bypass default network behaviors. This is primarily used by enterprise architectures operating behind zero-trust firewalls or corporate VPC proxies.
client = AgriBackupClient(
api_key="sk_live_...",
base_url="https://custom-proxy.internal.co" # Overrides automated prefix routing
)
Manual Idempotency Control
AgriBackup strictly guarantees safety during distributed failures via Idempotency-Key tracking. The backend will automatically generate this key if absent, so standard integrations can safely ignore this parameter.
If your Tier-1 enterprise architecture strictly requires passing your own internal ERP database transaction IDs as idempotency keys, you can inject them securely using the client's default HTTP headers:
## Set the Idempotency-Key globally for the transaction
client.api_client.default_headers["Idempotency-Key"] = "erp-tx-10928-abc"
polygon_job = client.polygons.ingest_polygons(
polygon_ingestion_request=PolygonIngestionRequest(features=[...])
)
Install the package with:
npm install agribackup
Usage
The package needs to be configured with your account's API key, which you can get at https://agribackup.com. The SDK automatically routes your requests to the correct environment (Sandbox or Production) based on your key's prefix.
With this decoupled architecture, the SDK cleanly separates the environment setup (Webhooks) from the actual physical compliance flow.
Phase 1: Pre-assessment (The Sandbox Check)
Action: client.riskManagement.assessCoordinateRisk(...)
Purpose: A rapid, synchronous Boolean check to verify if a coordinate is in a deforested zone before you spend capital or compute on heavy satellite ingestion.
import { AgriBackupClient } from 'agribackup';
async function assessCoordinateRisk() {
const client = new AgriBackupClient('sk_test_YOUR_API_KEY');
const riskCheck = await client.riskManagement.assessCoordinateRisk({
latitude: -1.246807,
longitude: 36.743217
});
if (riskCheck.data.deforestationDetected) {
console.error("Deforestation detected. Cannot proceed.");
} else {
console.log("Coordinate is safe. Risk level:", riskCheck.data.countryRiskLevel);
}
console.log(riskCheck.data);
}
assessCoordinateRisk();
Phase 2: Event-Driven Infrastructure (One-Time Setup)
Action: client.webhooks.registerWebhook(...)
Purpose: Establishes the enterprise routing for asynchronous fulfillment. You register your ERP endpoint to listen for polygon.verified, batch.risk_assessed, shipment.linked, and dds.submitted.
// Register your webhook endpoint once during system startup
const registration = await client.webhooks.registerWebhook({
targetUrl: "https://your-erp.internal.co/api/webhooks/agribackup",
eventTypes: ["batch.risk_assessed", "polygon.verified", "shipment.linked", "dds.generated", "dds.submitted"]
});
console.log(`Webhook Secret (Save securely!): ${registration.data.signingSecret}`);
Webhook Event Payloads
Every webhook shares a common envelope (eventType, eventId, timestamp, attempt, nextRetry, data). Below are the schemas for the inner data object for each event:
polygon.verified:{ jobId, polygonsVerified, polygonsFailed, status, polygonIds }batch.risk_assessed:{ batchId, batchCode, workflowId, riskScore, classification, status }shipment.linked:{ batchId, shipmentReference, transactionHash }dds.generated:{ jobId, batchId, ddsReference, status, error }dds.submitted:{ batchId, ddsReference, status }dds.validated:{ batchId, ddsReference, validationTimestamp, status }dds.rejected:{ batchId, ddsReference, rejectionReason, status }job.failed:{ jobId, jobType, errorCode, errorMessage }report.ready:{ reportId, reportType, downloadUrl }
Verifying Incoming Webhooks
Use your signingSecret to cryptographically verify that incoming webhooks originated from AgriBackup:
const crypto = require('crypto');
function verifyWebhook(signatureHeader, rawBodyString, secret) {
const hash = crypto.createHmac('sha256', secret).update(rawBodyString).digest('hex');
return crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(signatureHeader));
}
Alternative: Manual Polling If you prefer not to use webhooks, or need to manually verify a job's status, you can retrieve the job state at any time:
const jobResponse = await client.jobs.getJobStatus("job-1234-uuid");
const phase = jobResponse.data.phase;
if (phase === "COMPLETED") {
console.log(`Job completed! Compliant units: ${jobResponse.data.compliantUnits}`);
} else if (phase === "FAILED") {
console.error(`Job failed. Errors:`, jobResponse.data.errors);
} else {
console.log(`Job is still processing. Current phase: ${phase}`);
}
Phase 3: The Complete EUDR Execution Lifecycle
This is the core operational loop where the decoupling shines.
- Ingest Polygons: Call
client.polygons.ingestPolygons(...). (Async: wait forpolygon.verifiedwebhook). - Register Batch: Call
client.batches.registerBatch(...)using the verified polygon IDs. (Sync: returns batchId instantly). - Attach Documentation: Call
client.documents.upload(...)or equivalent to bind legal EUDR documents to the batchId. - Assess Batch Risk: Call
client.batches.assessRisk(...). (Async: wait forbatch.risk_assessedwebhook). - Link Logistics: Call
client.logistics.linkBatchToShipment(...). (Async: wait forshipment.linkedwebhook). - Generate Declaration: Call
client.declarations.generateDds(...). (Async: wait fordds.generatedwebhook). - Submit Declaration: Call
client.declarations.submitDds(...). (Async: wait for TRACES NTdds.validatedwebhook).
This SDK structure gives you absolute deterministic control over the state machine of your agricultural supply chain.
Complete Lifecycle Implementation Example
// Initialize the Client
const client = new AgriBackupClient('sk_test_YOUR_API_KEY');
const express = require('express');
const app = express();
app.use(express.json());
// ==========================================
// Webhook Listener (Handling Async Events)
// ==========================================
app.post('/api/webhooks/agribackup', async (req, res) => {
const event = req.body;
// 1. Acknowledge receipt immediately to prevent retries
res.status(200).send("OK");
try {
// 2. Route the webhook event
switch (event.eventType) {
case 'polygon.verified':
await handlePolygonVerified(event.data);
break;
case 'batch.risk_assessed':
await handleBatchRiskAssessed(event.data);
break;
case 'shipment.linked':
await handleShipmentLinked(event.data);
break;
case 'dds.generated':
await handleDdsGenerated(event.data);
break;
case 'dds.submitted':
console.log(`DDS Successfully Filed! Reference: ${event.data.ddsReference}`);
break;
default:
console.log(`Unhandled event type: ${event.eventType}`);
}
} catch (error) {
console.error(`Failed to process webhook ${event.eventType}:`, error);
}
});
// ==========================================
// The State Machine Workflow
// ==========================================
// Step 1: Ingest Polygons (Triggered manually or via ERP)
async function startComplianceFlow() {
const polygonJob = await client.polygons.ingestPolygons({
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"
}
}]
});
console.log(`Polygon ingestion started. Job ID: ${polygonJob.data.jobId}`);
// Awaits 'polygon.verified' webhook...
}
// Step 2 & 3: Register Batch & Attach Documents (Fired by polygon.verified webhook)
async function handlePolygonVerified(data) {
if (data.status !== "COMPLETED") return;
// Register Batch synchronously
const batch = await client.batches.registerBatch({
commodity: "Coffee",
countryCode: "KEN",
hsCode: "0901",
quantityKg: 1500.0,
polygonIds: data.polygonIds,
vendorIds: ["ERP-VEND-991"]
});
console.log(`Batch registered. ID: ${batch.data.batchId}`);
// Step 4: Assess Batch Risk
await client.batches.assessRisk(batch.data.batchId);
console.log(`Batch risk assessment started for ${batch.data.batchId}`);
// Awaits 'batch.risk_assessed' webhook...
}
// Step 5: Link Logistics (Fired by batch.risk_assessed webhook)
async function handleBatchRiskAssessed(data) {
if (data.status !== "COMPLETED" || data.classification === "HIGH_RISK") {
console.error("Batch is high risk or failed assessment.");
return;
}
await client.logistics.linkBatchToShipment({
batchId: data.batchId,
shipmentId: "SHP-123",
billOfLading: "BOL-99281744",
vesselName: "Evergreen"
});
console.log(`Logistics linked for batch ${data.batchId}`);
// Awaits 'shipment.linked' webhook...
}
// Step 6: Generate Declaration (Fired by shipment.linked webhook)
async function handleShipmentLinked(data) {
// Note: shipment.linked webhook firing indicates success. No status check required.
const dds = await client.declarations.generateDds({
batchId: data.batchId,
legalDocumentHashes: ["e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"]
});
console.log(`DDS generation started for batch ${data.batchId}. Job ID: ${dds.data.jobId}`);
// Awaits 'dds.generated' webhook...
}
// Step 7: Submit Declaration (Fired by dds.generated webhook)
async function handleDdsGenerated(data) {
if (data.status !== "COMPLETED") return;
await client.declarations.submitDds(data.ddsReference);
console.log(`DDS submitted to TRACES. Awaiting validation for ${data.ddsReference}...`);
// Awaits 'dds.submitted' / 'dds.validated' webhooks...
}
app.listen(3000, () => console.log('AgriBackup Integration Server running on port 3000'));
Cryptographic Evidence
AgriBackup anchors all critical compliance states to the Hedera Hashgraph DLT. You can retrieve immutable, cryptographically verifiable proofs of your compliance events at any time.
const evidence = await client.evidence.getBatchLedgerEvidence("batch-123");
console.log(evidence.data);
Interpreting the Evidence
The returned evidence object contains a chronological history of the entity's lifecycle anchored on-chain. It includes an array of stateProofs, where each proof represents a distinct compliance event (like CREATED or RISK_ASSESSED).
Key fields inside each state proof include:
hederaTransactionId: The exact identifier on the Hedera Consensus Service. You can search this ID on any public Hedera explorer (e.g., Hashscan) to independently verify the transaction.consensusTimestamp: The decentralized, network-agreed time the event was permanently recorded.operationType: The specific state transition that occurred.merkleProof: The cryptographic data required to perform offline verification, ensuring the state has not been tampered with since anchoring.
Archiving Reports
AgriBackup supports asynchronous generation of bulk compliance archives. This compiles all XML payloads, Hedera state proofs, and legal document hashes into a single cryptographic ZIP artifact.
- Request the Archive: Call
client.archival.triggerArchiveReportwith a date range. - Await the Webhook: Wait for the
report.readywebhook. - Download: Use the provided URL or SDK method to securely retrieve the artifact.
// Step 1: Request Archive
const reportReq = await client.archival.triggerArchiveReport({
startDate: "2026-01-01",
endDate: "2026-03-31"
});
console.log(`Report job started: ${reportReq.data.reportId}`);
// Step 2: Handle Webhook
async function handleReportReady(data) {
if (data.reportType !== "COMPLIANCE_ARCHIVE") return;
console.log(`Report is ready to download at: ${data.downloadUrl}`);
// Optionally fetch it directly using the SDK:
// const zipBuffer = await client.archival.downloadArchiveReport(data.reportId);
}
Advanced Enterprise Configuration
Overriding Network Routing & Proxies
The client can be initialized with several options to bypass default network behaviors. This is primarily used by enterprise architectures operating behind zero-trust firewalls or corporate VPC proxies.
const client = new AgriBackupClient(
"sk_live_...",
"https://custom-proxy.internal.co" // Overrides automated prefix routing
);
Manual Idempotency Control
AgriBackup strictly guarantees safety during distributed failures via Idempotency-Key tracking. The backend will automatically generate this key if absent, so standard integrations can safely ignore this parameter as it is automatically abstracted.
If your Tier-1 enterprise architecture strictly requires passing your own internal ERP database transaction IDs as idempotency keys, you can inject them securely using the client's default HTTP headers:
// Set the Idempotency-Key globally for the transaction
client.axios.defaults.headers.common['Idempotency-Key'] = "erp-tx-10928-abc";
await client.polygons.ingestPolygons({ features: [...] });
Add the dependency to your pom.xml:
<dependency>
<groupId>com.agribackup</groupId>
<artifactId>agribackup</artifactId>
</dependency>
Usage
The package needs to be configured with your account's API key, which you can get at https://agribackup.com. The SDK automatically routes your requests to the correct environment (Sandbox or Production) based on your key's prefix.
With this decoupled architecture, the SDK cleanly separates the environment setup (Webhooks) from the actual physical compliance flow.
Phase 1: Pre-assessment (The Sandbox Check)
Action: client.riskManagement().assessCoordinateRisk(...)
Purpose: A rapid, synchronous Boolean check to verify if a coordinate is in a deforested zone before you spend capital or compute on heavy satellite ingestion.
import com.agribackup.AgriBackupClient;
import com.agribackup.sdk.model.CoordinateRiskRequest;
import com.agribackup.sdk.model.CoordinateRiskAssessmentResponse;
public class Phase1Example {
public static void main(String[] args) {
AgriBackupClient client = new AgriBackupClient("sk_test_YOUR_API_KEY");
CoordinateRiskRequest request = new CoordinateRiskRequest().latitude(-1.246807).longitude(36.743217);
CoordinateRiskAssessmentResponse riskCheck = client.riskManagement().assessCoordinateRisk(request);
if (Boolean.TRUE.equals(riskCheck.getDeforestationDetected())) {
System.out.println("Deforestation detected. Cannot proceed.");
} else {
System.out.println("Coordinate is safe. Risk level: " + riskCheck.getCountryRiskLevel());
}
System.out.println(riskCheck);
}
}
Phase 2: Event-Driven Infrastructure (One-Time Setup)
Action: client.webhooks().registerWebhook(...)
Purpose: Establishes the enterprise routing for asynchronous fulfillment. You register your ERP endpoint to listen for polygon.verified, batch.risk_assessed, shipment.linked, and dds.submitted.
import org.openapitools.client.model.WebhookRegistrationRequest;
import org.openapitools.client.model.WebhookRegistrationResponse;
import java.util.Arrays;
// Register your webhook endpoint once during system startup
WebhookRegistrationRequest request = new WebhookRegistrationRequest()
.targetUrl("https://your-erp.internal.co/api/webhooks/agribackup")
.eventTypes(Arrays.asList("batch.risk_assessed", "polygon.verified", "shipment.linked", "dds.generated", "dds.submitted"));
WebhookRegistrationResponse response = client.webhooks().registerWebhook(request);
System.out.println("Webhook Secret (Save securely!): " + response.getSigningSecret());
Webhook Event Payloads
Every webhook shares a common envelope (eventType, eventId, timestamp, attempt, nextRetry, data). Below are the schemas for the inner data object for each event:
polygon.verified:{ jobId, polygonsVerified, polygonsFailed, status, polygonIds }batch.risk_assessed:{ batchId, batchCode, workflowId, riskScore, classification, status }shipment.linked:{ batchId, shipmentReference, transactionHash }dds.generated:{ jobId, batchId, ddsReference, status, error }dds.submitted:{ batchId, ddsReference, status }dds.validated:{ batchId, ddsReference, validationTimestamp, status }dds.rejected:{ batchId, ddsReference, rejectionReason, status }job.failed:{ jobId, jobType, errorCode, errorMessage }report.ready:{ reportId, reportType, downloadUrl }
Verifying Incoming Webhooks
Use your signingSecret to cryptographically verify that incoming webhooks originated from AgriBackup:
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
public boolean verifyWebhook(String signatureHeader, String rawBodyString, String secret) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
mac.init(secretKeySpec);
byte[] hash = mac.doFinal(rawBodyString.getBytes(StandardCharsets.UTF_8));
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if(hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return MessageDigest.isEqual(hexString.toString().getBytes(), signatureHeader.getBytes());
}
Alternative: Manual Polling If you prefer not to use webhooks, or need to manually verify a job's status, you can retrieve the job state at any time:
import org.openapitools.client.model.EnterpriseJobStatusResponse;
EnterpriseJobStatusResponse jobResponse = client.jobs().getJobStatus("job-1234-uuid");
String phase = jobResponse.getPhase();
if ("COMPLETED".equals(phase)) {
System.out.println("Job completed! Compliant units: " + jobResponse.getCompliantUnits());
} else if ("FAILED".equals(phase)) {
System.err.println("Job failed. Errors: " + jobResponse.getErrors());
} else {
System.out.println("Job is still processing. Current phase: " + phase);
}
Phase 3: The Complete EUDR Execution Lifecycle
This is the core operational loop where the decoupling shines.
- Ingest Polygons: Call
client.polygons().ingestPolygons(...). (Async: wait forpolygon.verifiedwebhook). - Register Batch: Call
client.batches().registerBatch(...)using the verified polygon IDs. (Sync: returns batchId instantly). - Attach Documentation: Call
client.documents().upload(...)or equivalent to bind legal EUDR documents to the batchId. - Assess Batch Risk: Call
client.batches().assessRisk(...). (Async: wait forbatch.risk_assessedwebhook). - Link Logistics: Call
client.logistics().linkBatchToShipment(...). (Async: wait forshipment.linkedwebhook). - Generate Declaration: Call
client.declarations().generateDds(...). (Async: wait fordds.generatedwebhook). - Submit Declaration: Call
client.declarations().submitDds(...). (Async: wait for TRACES NTdds.validatedwebhook).
This SDK structure gives you absolute deterministic control over the state machine of your agricultural supply chain.
Complete Lifecycle Implementation Example
import com.agribackup.AgriBackupClient;
import com.agribackup.sdk.model.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import java.util.Arrays;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class WebhookController {
private final AgriBackupClient client = new AgriBackupClient("sk_test_YOUR_API_KEY");
// ==========================================
// Webhook Listener (Handling Async Events)
// ==========================================
@PostMapping("/webhooks/agribackup")
public ResponseEntity<String> handleWebhook(@RequestBody Map<String, Object> payload) {
String eventType = (String) payload.get("eventType");
Map<String, Object> data = (Map<String, Object>) payload.get("data");
// Process asynchronously in a real application
new Thread(() -> processEvent(eventType, data)).start();
// 1. Acknowledge receipt immediately
return ResponseEntity.ok("OK");
}
// ==========================================
// The State Machine Workflow
// ==========================================
private void processEvent(String eventType, Map<String, Object> data) {
try {
switch (eventType) {
case "polygon.verified":
handlePolygonVerified(data);
break;
case "batch.risk_assessed":
handleBatchRiskAssessed(data);
break;
case "shipment.linked":
handleShipmentLinked(data);
break;
case "dds.generated":
handleDdsGenerated(data);
break;
case "dds.submitted":
System.out.println("DDS Successfully Filed! Reference: " + data.get("ddsReference"));
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
// Step 1: Ingest Polygons (Triggered manually or via ERP)
public void startComplianceFlow() {
GeoJsonFeature feature = new GeoJsonFeature().type("Feature")
.geometry(new GeoJsonGeometry().type(GeoJsonGeometry.TypeEnum.POLYGON).addCoordinatesItem(
Arrays.asList(Arrays.asList(36.8, -1.2), Arrays.asList(36.9, -1.2), Arrays.asList(36.9, -1.3), Arrays.asList(36.8, -1.3), Arrays.asList(36.8, -1.2))
)).properties(new FeatureProperties().farmerName("Global Coffee Farmer #1").farmerId("TEST_100").plotName("Nyeri Hill Farm Block B").area(new java.math.BigDecimal("2.5")).commodity("Coffee"));
JobAcceptedResponse response = client.polygons().ingestPolygons(new PolygonIngestionRequest().addFeaturesItem(feature));
System.out.println("Polygon ingestion started. Job ID: " + response.getJobId());
}
// Step 2 & 3: Register Batch & Attach Documents
private void handlePolygonVerified(Map<String, Object> data) throws Exception {
if (!"COMPLETED".equals(data.get("status"))) return;
BatchRegistrationRequest batchRequest = new BatchRegistrationRequest()
.commodity("Coffee").countryCode("KEN").hsCode("0901").quantityKg(new java.math.BigDecimal("1500.0"))
.polygonIds((java.util.List<String>) data.get("polygonIds"))
.addVendorIdsItem("ERP-VEND-991");
BatchRegistrationResponse batch = client.batches().registerBatch(batchRequest);
// Step 4: Assess Batch Risk
client.batches().assessRisk(batch.getBatchId());
System.out.println("Batch risk assessment started for " + batch.getBatchId());
}
// Step 5: Link Logistics
private void handleBatchRiskAssessed(Map<String, Object> data) throws Exception {
if (!"COMPLETED".equals(data.get("status")) || "HIGH_RISK".equals(data.get("classification"))) return;
ShipmentLinkRequest linkReq = new ShipmentLinkRequest()
.batchId((String) data.get("batchId")).shipmentId("SHP-123").billOfLading("BOL-99281744").vesselName("Evergreen");
client.logistics().linkBatchToShipment(linkReq);
}
// Step 6: Generate Declaration
private void handleShipmentLinked(Map<String, Object> data) throws Exception {
// Note: shipment.linked webhook firing indicates success. No status check required.
DdsGenerationRequest ddsReq = new DdsGenerationRequest()
.batchId((String) data.get("batchId")).addLegalDocumentHashesItem("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
JobAcceptedResponse dds = client.declarations().generateDds(ddsReq);
System.out.println("DDS generation started for batch " + data.get("batchId") + ". Job ID: " + dds.getJobId());
}
// Step 7: Submit Declaration
private void handleDdsGenerated(Map<String, Object> data) throws Exception {
if (!"COMPLETED".equals(data.get("status"))) return;
String ddsRef = (String) data.get("ddsReference");
client.declarations().submitDds(ddsRef);
System.out.println("DDS submitted to TRACES. Awaiting validation for " + ddsRef + "...");
}
}
Cryptographic Evidence
AgriBackup anchors all critical compliance states to the Hedera Hashgraph DLT. You can retrieve immutable, cryptographically verifiable proofs of your compliance events at any time.
import com.agribackup.sdk.model.LedgerEvidenceResponse;
LedgerEvidenceResponse evidence = client.evidence().getBatchLedgerEvidence("batch-123");
System.out.println(evidence);
Interpreting the Evidence
The returned evidence object contains a chronological history of the entity's lifecycle anchored on-chain. It includes a list of stateProofs, where each proof represents a distinct compliance event (like CREATED or RISK_ASSESSED).
Key fields inside each state proof include:
hederaTransactionId: The exact identifier on the Hedera Consensus Service. You can search this ID on any public Hedera explorer (e.g., Hashscan) to independently verify the transaction.consensusTimestamp: The decentralized, network-agreed time the event was permanently recorded.operationType: The specific state transition that occurred.merkleProof: The cryptographic data required to perform offline verification, ensuring the state has not been tampered with since anchoring.
Archiving Reports
AgriBackup supports asynchronous generation of bulk compliance archives. This compiles all XML payloads, Hedera state proofs, and legal document hashes into a single cryptographic ZIP artifact.
- Request the Archive: Call
client.archival().triggerArchiveReportwith a date range. - Await the Webhook: Wait for the
report.readywebhook. - Download: Use the provided URL or SDK method to securely retrieve the artifact.
import java.time.LocalDate;
import com.agribackup.model.ArchiveReportRequest;
import com.agribackup.model.ArchiveReportResponse;
// Step 1: Request Archive
ArchiveReportRequest req = new ArchiveReportRequest()
.startDate(LocalDate.parse("2026-01-01"))
.endDate(LocalDate.parse("2026-03-31"));
ArchiveReportResponse response = client.archival().triggerArchiveReport(req);
System.out.println("Report job started: " + response.getReportId());
// Step 2: Handle Webhook (fired by report.ready)
public void handleReportReady(Map<String, Object> data) {
if (!"COMPLIANCE_ARCHIVE".equals(data.get("reportType"))) return;
System.out.println("Report is ready to download at: " + data.get("downloadUrl"));
// Optionally fetch it directly using the SDK:
// File zipFile = client.archival().downloadArchiveReport((String) data.get("reportId"));
}
Advanced Enterprise Configuration
Overriding Network Routing & Proxies
The client can be initialized with several options to bypass default network behaviors. This is primarily used by enterprise architectures operating behind zero-trust firewalls or corporate VPC proxies.
AgriBackupClient client = new AgriBackupClient(
"sk_live_...",
"https://custom-proxy.internal.co" // Overrides automated prefix routing
);
Manual Idempotency Control
AgriBackup strictly guarantees safety during distributed failures via Idempotency-Key tracking. The backend will automatically generate this key if absent, so standard integrations can safely ignore this parameter.
If your Tier-1 enterprise architecture strictly requires passing your own internal ERP database transaction IDs as idempotency keys, you can inject them securely using the client's default HTTP headers:
// Set the Idempotency-Key globally for the transaction
client.getApiClient().addDefaultHeader("Idempotency-Key", "erp-tx-10928-abc");
PolygonIngestionRequest polygonRequest = new PolygonIngestionRequest().addFeaturesItem(...);
client.polygons().ingestPolygons(polygonRequest);
Install the package via NuGet:
dotnet add package AgriBackup
Usage
The package needs to be configured with your account's API key, which you can get at https://agribackup.com. The SDK automatically routes your requests to the correct environment (Sandbox or Production) based on your key's prefix.
With this decoupled architecture, the SDK cleanly separates the environment setup (Webhooks) from the actual physical compliance flow.
Phase 1: Pre-assessment (The Sandbox Check)
Action: client.RiskManagement.AssessCoordinateRiskAsync(...)
Purpose: A rapid, synchronous Boolean check to verify if a coordinate is in a deforested zone before you spend capital or compute on heavy satellite ingestion.
using System;
using System.Threading.Tasks;
using AgriBackup.Api;
using AgriBackup.Client;
using AgriBackup.Model;
using Newtonsoft.Json;
namespace AgriBackupExample
{
class Program
{
static async Task Main(string[] args)
{
var client = new AgriBackupClient("sk_test_YOUR_API_KEY");
var request = new CoordinateRiskRequest(latitude: -1.246807, longitude: 36.743217);
var riskCheck = await client.RiskManagement.AssessCoordinateRiskAsync(request);
if (riskCheck.DeforestationDetected == true) {
Console.WriteLine("Deforestation detected. Cannot proceed.");
} else {
Console.WriteLine($"Coordinate is safe. Risk level: {riskCheck.CountryRiskLevel}");
}
Console.WriteLine(JsonConvert.SerializeObject(riskCheck));
}
}
}
Phase 2: Event-Driven Infrastructure (One-Time Setup)
Action: client.Webhooks.RegisterWebhookAsync(...)
Purpose: Establishes the enterprise routing for asynchronous fulfillment. You register your ERP endpoint to listen for polygon.verified, batch.risk_assessed, shipment.linked, and dds.submitted.
using AgriBackup.Model;
using System.Collections.Generic;
// Register your webhook endpoint once during system startup
var request = new WebhookRegistrationRequest(
targetUrl: "https://your-erp.internal.co/api/webhooks/agribackup",
eventTypes: new List<string> { "batch.risk_assessed", "polygon.verified", "shipment.linked", "dds.generated", "dds.submitted" }
);
var response = await client.Webhooks.RegisterWebhookAsync(request);
Console.WriteLine($"Webhook Secret (Save securely!): {response.SigningSecret}");
Webhook Event Payloads
Every webhook shares a common envelope (eventType, eventId, timestamp, attempt, nextRetry, data). Below are the schemas for the inner data object for each event:
polygon.verified:{ jobId, polygonsVerified, polygonsFailed, status, polygonIds }batch.risk_assessed:{ batchId, batchCode, workflowId, riskScore, classification, status }shipment.linked:{ batchId, shipmentReference, transactionHash }dds.generated:{ jobId, batchId, ddsReference, status, error }dds.submitted:{ batchId, ddsReference, status }dds.validated:{ batchId, ddsReference, validationTimestamp, status }dds.rejected:{ batchId, ddsReference, rejectionReason, status }job.failed:{ jobId, jobType, errorCode, errorMessage }report.ready:{ reportId, reportType, downloadUrl }
Verifying Incoming Webhooks
Use your SigningSecret to cryptographically verify that incoming webhooks originated from AgriBackup:
using System.Security.Cryptography;
using System.Text;
public bool VerifyWebhook(string signatureHeader, string rawBodyString, string secret)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(rawBodyString));
var hashString = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(hashString),
Encoding.UTF8.GetBytes(signatureHeader)
);
}
Alternative: Manual Polling If you prefer not to use webhooks, or need to manually verify a job's status, you can retrieve the job state at any time:
var jobResponse = await client.Jobs.GetJobStatusAsync("job-1234-uuid");
var phase = jobResponse.Phase;
if (phase == "COMPLETED") {
Console.WriteLine($"Job completed! Compliant units: {jobResponse.CompliantUnits}");
} else if (phase == "FAILED") {
Console.WriteLine($"Job failed. Errors: {string.Join(", ", jobResponse.Errors)}");
} else {
Console.WriteLine($"Job is still processing. Current phase: {phase}");
}
Phase 3: The Complete EUDR Execution Lifecycle
This is the core operational loop where the decoupling shines.
- Ingest Polygons: Call
client.Polygons.IngestPolygonsAsync(...). (Async: wait forpolygon.verifiedwebhook). - Register Batch: Call
client.Batches.RegisterBatchAsync(...)using the verified polygon IDs. (Sync: returns batchId instantly). - Attach Documentation: Call
client.Documents.UploadAsync(...)or equivalent to bind legal EUDR documents to the batchId. - Assess Batch Risk: Call
client.Batches.AssessRiskAsync(...). (Async: wait forbatch.risk_assessedwebhook). - Link Logistics: Call
client.Logistics.LinkBatchToShipmentAsync(...). (Async: wait forshipment.linkedwebhook). - Generate Declaration: Call
client.Declarations.GenerateDdsAsync(...). (Async: wait fordds.generatedwebhook). - Submit Declaration: Call
client.Declarations.SubmitDdsAsync(...). (Async: wait for TRACES NTdds.validatedwebhook).
This SDK structure gives you absolute deterministic control over the state machine of your agricultural supply chain.
Complete Lifecycle Implementation Example
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using AgriBackup.Api;
using AgriBackup.Client;
using AgriBackup.Model;
using Newtonsoft.Json;
[ApiController]
[Route("api")]
public class WebhookController : ControllerBase
{
private readonly AgriBackupClient _client = new AgriBackupClient("sk_test_YOUR_API_KEY");
// ==========================================
// Webhook Listener (Handling Async Events)
// ==========================================
[HttpPost("webhooks/agribackup")]
public IActionResult HandleWebhook([FromBody] dynamic payload)
{
string eventType = payload.eventType;
var data = payload.data;
// Process asynchronously
Task.Run(() => ProcessEvent(eventType, data));
// 1. Acknowledge receipt immediately
return Ok("OK");
}
// ==========================================
// The State Machine Workflow
// ==========================================
private async Task ProcessEvent(string eventType, dynamic data)
{
try
{
switch (eventType)
{
case "polygon.verified": await HandlePolygonVerified(data); break;
case "batch.risk_assessed": await HandleBatchRiskAssessed(data); break;
case "shipment.linked": await HandleShipmentLinked(data); break;
case "dds.generated": await HandleDdsGenerated(data); break;
case "dds.submitted": Console.WriteLine($"DDS Successfully Filed! Ref: {data.ddsReference}"); break;
}
}
catch (Exception ex) { Console.WriteLine($"Error processing webhook: {ex.Message}"); }
}
// Step 1: Ingest Polygons (Triggered manually or via ERP)
public async Task StartComplianceFlow()
{
var request = new PolygonIngestionRequest(
features: new List<GeoJsonFeature> {
new GeoJsonFeature(
type: "Feature",
geometry: new GeoJsonGeometry(type: GeoJsonGeometry.TypeEnum.Polygon, coordinates: new List<List<List<double>>> { /* ... */ }),
properties: new FeatureProperties(farmerName: "Global Coffee Farmer #1", farmerId: "TEST_100", plotName: "Nyeri Hill Farm Block B", area: 2.5m, commodity: "Coffee")
)
}
);
var response = await _client.Polygons.IngestPolygonsAsync(request);
Console.WriteLine($"Polygon ingestion started. Job ID: {response.JobId}");
}
// Step 2 & 3: Register Batch & Attach Documents
private async Task HandlePolygonVerified(dynamic data)
{
if (data.status != "COMPLETED") return;
var batchRequest = new BatchRegistrationRequest(
commodity: "Coffee", countryCode: "KEN", hsCode: "0901", quantityKg: 1500.0m,
polygonIds: data.polygonIds.ToObject<List<string>>(), vendorIds: new List<string> { "ERP-VEND-991" }
);
var batch = await _client.Batches.RegisterBatchAsync(batchRequest);
// Step 4: Assess Batch Risk
await _client.Batches.AssessRiskAsync((string)batch.BatchId);
Console.WriteLine($"Batch risk assessment started for {batch.BatchId}");
}
// Step 5: Link Logistics
private async Task HandleBatchRiskAssessed(dynamic data)
{
if (data.status != "COMPLETED" || data.classification == "HIGH_RISK") return;
var linkReq = new ShipmentLinkRequest(
batchId: (string)data.batchId, shipmentId: "SHP-123", billOfLading: "BOL-99281744", vesselName: "Evergreen"
);
await _client.Logistics.LinkBatchToShipmentAsync(linkReq);
}
// Step 6: Generate Declaration
private async Task HandleShipmentLinked(dynamic data)
{
// Note: shipment.linked webhook firing indicates success. No status check required.
var ddsReq = new DdsGenerationRequest(
batchId: (string)data.batchId, legalDocumentHashes: new List<string> { "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }
);
var dds = await _client.Declarations.GenerateDdsAsync(ddsReq);
Console.WriteLine($"DDS generation started for batch {data.batchId}. Job ID: {dds.JobId}");
}
// Step 7: Submit Declaration
private async Task HandleDdsGenerated(dynamic data)
{
if (data.status != "COMPLETED") return;
string ddsRef = (string)data.ddsReference;
await _client.Declarations.SubmitDdsAsync(ddsRef);
Console.WriteLine($"DDS submitted to TRACES. Awaiting validation for {ddsRef}...");
}
}
Cryptographic Evidence
AgriBackup anchors all critical compliance states to the Hedera Hashgraph DLT. You can retrieve immutable, cryptographically verifiable proofs of your compliance events at any time.
using Newtonsoft.Json;
var evidence = await client.Evidence.GetBatchLedgerEvidenceAsync("batch-123");
Console.WriteLine(JsonConvert.SerializeObject(evidence, Formatting.Indented));
Interpreting the Evidence
The returned evidence object contains a chronological history of the entity's lifecycle anchored on-chain. It includes a list of stateProofs, where each proof represents a distinct compliance event (like CREATED or RISK_ASSESSED).
Key fields inside each state proof include:
hederaTransactionId: The exact identifier on the Hedera Consensus Service. You can search this ID on any public Hedera explorer (e.g., Hashscan) to independently verify the transaction.consensusTimestamp: The decentralized, network-agreed time the event was permanently recorded.operationType: The specific state transition that occurred.merkleProof: The cryptographic data required to perform offline verification, ensuring the state has not been tampered with since anchoring.
Archiving Reports
AgriBackup supports asynchronous generation of bulk compliance archives. This compiles all XML payloads, Hedera state proofs, and legal document hashes into a single cryptographic ZIP artifact.
- Request the Archive: Call
client.Archival.TriggerArchiveReportAsyncwith a date range. - Await the Webhook: Wait for the
report.readywebhook. - Download: Use the provided URL or SDK method to securely retrieve the artifact.
using System;
// Step 1: Request Archive
var req = new ArchiveReportRequest(
startDate: DateTime.Parse("2026-01-01"),
endDate: DateTime.Parse("2026-03-31")
);
var response = await client.Archival.TriggerArchiveReportAsync(req);
Console.WriteLine($"Report job started: {response.ReportId}");
// Step 2: Handle Webhook (fired by report.ready)
public void HandleReportReady(dynamic data)
{
if (data.reportType != "COMPLIANCE_ARCHIVE") return;
Console.WriteLine($"Report is ready to download at: {data.downloadUrl}");
// Optionally fetch it directly using the SDK:
// var zipStream = await client.Archival.DownloadArchiveReportAsync((string)data.reportId);
}
Advanced Enterprise Configuration
Overriding Network Routing & Proxies
The client can be initialized with several options to bypass default network behaviors. This is primarily used by enterprise architectures operating behind zero-trust firewalls or corporate VPC proxies.
var client = new AgriBackupClient(
"sk_live_...",
"https://custom-proxy.internal.co" // Overrides automated prefix routing
);
Manual Idempotency Control
AgriBackup strictly guarantees safety during distributed failures via Idempotency-Key tracking. The backend will automatically generate this key if absent, so standard integrations can safely ignore this parameter.
If your Tier-1 enterprise architecture strictly requires passing your own internal ERP database transaction IDs as idempotency keys, you can inject them securely using the client's default HTTP headers:
// Set the Idempotency-Key globally for the transaction
client.Configuration.DefaultHeaders.Add("Idempotency-Key", "erp-tx-10928-abc");
var polygonRequest = new PolygonIngestionRequest { Features = new List<GeoJsonFeature> { ... } };
await client.Polygons.IngestPolygonsAsync(polygonRequest);
Enterprise Routing & Dedicated Proxies
For Tier-1 institutions running behind zero-trust firewalls, dedicated VPC proxies, or specialized EU data localization clusters, relying on the automatic Prefix Inference may be blocked by network policy.
In these environments, you can override the routing defaults by explicitly passing a base_url or host string to the client instance.
Crucial Path Stripping: When providing a custom proxy URL, provide strictly the root domain (e.g., https://custom-proxy.internal.co). The SDK automatically manages and strips the legacy /api/v1 namespace internally.
from agribackup.client import AgriBackupClient
# Bypassing Prefix Inference to route through an internal corporate proxy
client = AgriBackupClient(
api_key="sk_live_YOUR_API_KEY",
base_url="https://custom-proxy.internal.co"
)