Skip to main content
POST
/
api
/
v1
/
enterprise
/
eudr
/
shipments
/
link
Python
import agribackup
from agribackup.rest import ApiException

# Initialize Client
configuration = agribackup.Configuration(host="https://live.agribackup.com/api/v1")
configuration.api_key['ApiKeyAuth'] = 'sk_live_YOUR_API_KEY'

with agribackup.ApiClient(configuration) as api_client:
    try:
        api_instance = agribackup.EnterpriseLogisticsApi(api_client)
        response = api_instance.link_batch_to_shipment()
        print(response)
    except ApiException as e:
        print("Exception when calling API: %s\n" % e)
import { AgriBackupClient } from '@agribackup/sdk';

// Initialize Client
const client = new AgriBackupClient({
apiKey: 'sk_live_YOUR_API_KEY',
baseUrl: 'https://live.agribackup.com/api/v1'
});

async function execute() {
try {
const response = await client.logistics.linkBatchToShipment();
console.log(response);
} catch (error) {
console.error(error);
}
}
import com.agribackup.client.*;
import com.agribackup.client.api.*;
import com.agribackup.client.model.*;

public class Example {
public static void main(String[] args) {
ApiClient defaultClient = Configuration.getDefaultApiClient();
defaultClient.setBasePath("https://live.agribackup.com/api/v1");
defaultClient.setApiKey("sk_live_YOUR_API_KEY");

EnterpriseLogisticsApi apiInstance = new EnterpriseLogisticsApi(defaultClient);
try {
Object result = apiInstance.linkBatchToShipment();
System.out.println(result);
} catch (ApiException e) {
System.err.println("Exception when calling API: " + e.getResponseBody());
}
}
}
using System;
using System.Threading.Tasks;
using AgriBackup.SDK.Api;
using AgriBackup.SDK.Client;
using AgriBackup.SDK.Model;

class Program {
static async Task Main() {
Configuration config = new Configuration();
config.BasePath = "https://live.agribackup.com/api/v1";
config.AddApiKey("ApiKeyAuth", "sk_live_YOUR_API_KEY");

var apiInstance = new EnterpriseLogisticsApi(config);
try {
var result = await apiInstance.LinkBatchToShipmentAsync();
Console.WriteLine(result);
} catch (ApiException e) {
Console.WriteLine("Exception when calling API: " + e.Message);
}
}
}
curl --request POST \
--url https://live.agribackup.com/api/v1/enterprise/eudr/shipments/link \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--header 'X-API-Key: <api-key>' \
--data '
{
"batchId": "uuid-1234",
"shipmentId": "SHIP-OOCL-9988",
"billOfLading": "BOL-77665544",
"vesselName": "MSC Gulsun"
}
'
const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
'X-API-Key': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
batchId: 'uuid-1234',
shipmentId: 'SHIP-OOCL-9988',
billOfLading: 'BOL-77665544',
vesselName: 'MSC Gulsun'
})
};

fetch('https://live.agribackup.com/api/v1/enterprise/eudr/shipments/link', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));
<?php

$curl = curl_init();

curl_setopt_array($curl, [
CURLOPT_URL => "https://live.agribackup.com/api/v1/enterprise/eudr/shipments/link",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'batchId' => 'uuid-1234',
'shipmentId' => 'SHIP-OOCL-9988',
'billOfLading' => 'BOL-77665544',
'vesselName' => 'MSC Gulsun'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"Idempotency-Key: <idempotency-key>",
"X-API-Key: <api-key>"
],
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}
package main

import (
"fmt"
"strings"
"net/http"
"io"
)

func main() {

url := "https://live.agribackup.com/api/v1/enterprise/eudr/shipments/link"

payload := strings.NewReader("{\n \"batchId\": \"uuid-1234\",\n \"shipmentId\": \"SHIP-OOCL-9988\",\n \"billOfLading\": \"BOL-77665544\",\n \"vesselName\": \"MSC Gulsun\"\n}")

req, _ := http.NewRequest("POST", url, payload)

req.Header.Add("Idempotency-Key", "<idempotency-key>")
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")

res, _ := http.DefaultClient.Do(req)

defer res.Body.Close()
body, _ := io.ReadAll(res.Body)

fmt.Println(string(body))

}
require 'uri'
require 'net/http'

url = URI("https://live.agribackup.com/api/v1/enterprise/eudr/shipments/link")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Idempotency-Key"] = '<idempotency-key>'
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"batchId\": \"uuid-1234\",\n \"shipmentId\": \"SHIP-OOCL-9988\",\n \"billOfLading\": \"BOL-77665544\",\n \"vesselName\": \"MSC Gulsun\"\n}"

response = http.request(request)
puts response.read_body
{
  "jobId": "job_998877",
  "status": "PENDING",
  "estimatedDurationSec": 3,
  "createdAt": "2026-06-15T12:00:00Z"
}
{
"code": "VALIDATION_FAILED",
"message": "Invalid polygon coordinates",
"details": [
{
"code": "INVALID_COORDINATE_CLOSURE",
"field": "features[0].properties.area",
"issue": "Area must be specified in hectares and cannot be negative"
}
]
}
{
"code": "VALIDATION_FAILED",
"message": "Invalid polygon coordinates",
"details": [
{
"code": "INVALID_COORDINATE_CLOSURE",
"field": "features[0].properties.area",
"issue": "Area must be specified in hectares and cannot be negative"
}
]
}
{
"code": "VALIDATION_FAILED",
"message": "Invalid polygon coordinates",
"details": [
{
"code": "INVALID_COORDINATE_CLOSURE",
"field": "features[0].properties.area",
"issue": "Area must be specified in hectares and cannot be negative"
}
]
}
{
"code": "VALIDATION_FAILED",
"message": "Invalid polygon coordinates",
"details": [
{
"code": "INVALID_COORDINATE_CLOSURE",
"field": "features[0].properties.area",
"issue": "Area must be specified in hectares and cannot be negative"
}
]
}
{
"code": "VALIDATION_FAILED",
"message": "Invalid polygon coordinates",
"details": [
{
"code": "INVALID_COORDINATE_CLOSURE",
"field": "features[0].properties.area",
"issue": "Area must be specified in hectares and cannot be negative"
}
]
}
{
"code": "VALIDATION_FAILED",
"message": "Invalid polygon coordinates",
"details": [
{
"code": "INVALID_COORDINATE_CLOSURE",
"field": "features[0].properties.area",
"issue": "Area must be specified in hectares and cannot be negative"
}
]
}
Invokes the AgriBackup Hedera smart contract to cryptographically lock a verified, compliant EUDR batch to a logistics shipment reference. This creates an immutable on-chain record binding the batch’s supply-chain provenance to a specific Bill of Lading or shipment tracking ID. Because Hedera consensus takes time, this endpoint returns 202 Accepted immediately with a jobId. Poll GET /api/v1/enterprise/eudr/jobs/{jobId} or listen for the shipment.linked webhook to confirm that the smart contract lock has been executed.
Only batches with a consolidatedRiskState of COMPLIANT or LOW may be linked to a shipment. Attempting to link a HIGH_RISK batch returns 409 Conflict.

Request Headers

HeaderRequiredDescription
X-API-KeyYour live API key, e.g. sk_live_....
Idempotency-KeyA UUID v4 string. Replaying the same key within 24 hours returns the original response without re-invoking the smart contract.

Request Body

FieldTypeRequiredDescription
batchIdstringThe UUID of the EUDR batch to link.
shipmentIdstringThe shipment reference or logistics tracking ID (e.g. SHIP-OOCL-9988).
billOfLadingstringOptional Bill of Lading document number (e.g. BOL-77665544).
vesselNamestringOptional shipping vessel name (e.g. MSC Gulsun).
{
  "batchId": "uuid-1234",
  "shipmentId": "SHIP-OOCL-9988",
  "billOfLading": "BOL-77665544",
  "vesselName": "MSC Gulsun"
}

Response — 202 Accepted

FieldTypeDescription
jobIdstringUse this ID to poll job status via GET /api/v1/enterprise/eudr/jobs/{jobId}.
statusstringAlways PENDING on acceptance.
estimatedDurationSecintegerEstimated seconds until Hedera consensus completes.
createdAtstring (ISO-8601)Timestamp when the job was accepted.
The response also includes a Location header pointing to the job status endpoint.
{
  "jobId": "job_998877",
  "status": "PENDING",
  "estimatedDurationSec": 3,
  "createdAt": "2026-06-15T12:00:00Z"
}

Error Responses

StatusMeaning
400Invalid batchId, invalid shipmentId, or malformed request body.
401Missing or invalid X-API-Key.
403Your API key does not have permission to link this batch.
409The batch is already linked to a shipment, or a double-spend attempt was detected on the Hedera ledger.
423The batch is currently being processed by another thread. Wait briefly and retry.
429Rate limit exceeded.

Async Completion — shipment.linked Webhook

When the Hedera smart contract lock is confirmed, AgriBackup fires a shipment.linked event to your registered webhook URL. The payload is signed with X-AgriBackup-Signature (HMAC-SHA256). After receiving this event, the batch is cryptographically immutable and ready for Due Diligence Statement generation.

Code Examples

import agribackup
from agribackup.rest import ApiException
import uuid

configuration = agribackup.Configuration(host="https://live.agribackup.com/api/v1")
configuration.api_key['ApiKeyAuth'] = 'sk_live_YOUR_API_KEY'

with agribackup.ApiClient(configuration) as api_client:
    try:
        api_instance = agribackup.EnterpriseLogisticsApi(api_client)

        request = agribackup.ShipmentLinkRequest(
            batch_id='uuid-1234',
            shipment_id='SHIP-OOCL-9988',
            bill_of_lading='BOL-77665544',
            vessel_name='MSC Gulsun'
        )

        response = api_instance.link_batch_to_shipment(
            idempotency_key=str(uuid.uuid4()),
            shipment_link_request=request
        )
        print("Job accepted:", response.job_id)
    except ApiException as e:
        print("Error:", e)
import { AgriBackupClient } from '@agribackup/sdk';
import { v4 as uuidv4 } from 'uuid';

const client = new AgriBackupClient({
  apiKey: 'sk_live_YOUR_API_KEY',
  baseUrl: 'https://live.agribackup.com/api/v1'
});

async function linkShipment() {
  try {
    const response = await client.logistics.linkBatchToShipment({
      idempotencyKey: uuidv4(),
      batchId: 'uuid-1234',
      shipmentId: 'SHIP-OOCL-9988',
      billOfLading: 'BOL-77665544',
      vesselName: 'MSC Gulsun'
    });
    console.log('Job accepted:', response.jobId);
  } catch (error) {
    console.error('Error:', error.message);
  }
}
curl -X POST https://live.agribackup.com/api/v1/enterprise/eudr/shipments/link \
  -H "X-API-Key: sk_live_YOUR_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "batchId": "uuid-1234",
    "shipmentId": "SHIP-OOCL-9988",
    "billOfLading": "BOL-77665544",
    "vesselName": "MSC Gulsun"
  }'

Authorizations

X-API-Key
string
header
required

Enterprise API Key provided via the AgriBackup developer console.

Headers

Idempotency-Key
string<uuid>
required

Unique idempotency key (UUID) to prevent duplicate operations. Safely stored in Redis with a strict 24-hour TTL to prevent memory bloat during saturation attacks.

Body

application/json

Payload to link a batch to a shipment via Hedera Smart Contract

batchId
string
required

The EUDR batch ID

Example:

"uuid-1234"

shipmentId
string
required

The shipment reference or logistics tracking ID

Example:

"SHIP-OOCL-9988"

billOfLading
string | null

Optional Bill of Lading document number

Example:

"BOL-77665544"

vesselName
string | null

Optional Shipping Vessel Name

Example:

"MSC Gulsun"

Response

Smart contract lock initiated asynchronously

Response payload for asynchronously accepted jobs

jobId
string
required

The unique asynchronous tracking job ID

Example:

"job_998877"

status
enum<string>
required

Current execution status invariant

Available options:
PENDING
Example:

"PENDING"

estimatedDurationSec
integer<int32>
required

Estimated duration in seconds before consensus or processing lock release

Example:

3

createdAt
string<date-time>
required

Timestamp when the job was accepted

Example:

"2026-06-15T12:00:00Z"