A Due Diligence Statement (DDS) is the EU-mandated declaration that your commodity complies with the EUDR. AgriBackup automates the full DDS lifecycle: it compiles your batch data and land tenure evidence into a TRACES NT V3–compliant XML payload, anchors the document hashes on Hedera for tamper-proof provenance, and submits the statement directly to TRACES NT using your delegated credentials. You then retrieve the finalized XML for your own records or for downstream customs integrations.
Before generating a DDS, you must store your TRACES NT credentials in AgriBackup’s vault. See the credentials ingestion endpoint (POST /api/v1/enterprise/eudr/credentials/traces) to securely encrypt and delegate your TRACES NT token or password.
Prerequisites
- A batch with
consolidatedRiskState: COMPLIANT linked to a shipment.
- TRACES NT credentials ingested via
POST /api/v1/enterprise/eudr/credentials/traces (credentials are encrypted and stored securely).
- (Optional) Land tenure documents — PDF, JPG, or PNG files — for inclusion in the DDS.
- A
dds.submitted webhook subscription to receive the filing confirmation (see Webhooks Setup).
Step-by-step DDS generation
(Optional) Upload land tenure documents
If you need to attach legal land tenure proof — such as title deeds, community forest agreements, or government-issued land certificates — upload each file via POST /api/v1/enterprise/eudr/declarations/documents/upload. The endpoint returns a SHA-256 hash for each file; you pass these hashes into the DDS generation request.import agribackup
from agribackup.rest import ApiException
import uuid
configuration = agribackup.Configuration()
configuration.api_key['ApiKeyAuth'] = 'sk_live_YOUR_API_KEY'
with agribackup.ApiClient(configuration) as api_client:
try:
declarations_api = agribackup.EnterpriseDeclarationsApi(api_client)
with open("land_tenure_certificate.pdf", "rb") as f:
doc = declarations_api.upload_document(
idempotency_key=str(uuid.uuid4()),
file=f
)
print("Document ID:", doc.document_id)
print("SHA-256 hash:", doc.hash)
print("Status:", doc.status) # "SECURED"
except ApiException as e:
print("Error:", e)
import { AgriBackupClient } from '@agribackup/sdk';
import { randomUUID } from 'crypto';
import { createReadStream } from 'fs';
const client = new AgriBackupClient({ apiKey: 'sk_live_YOUR_API_KEY' });
async function uploadDocument(filePath) {
try {
const fileStream = createReadStream(filePath);
const doc = await client.declarations.uploadDocument(fileStream, {
idempotencyKey: randomUUID()
});
console.log('Document ID:', doc.documentId);
console.log('SHA-256 hash:', doc.hash);
console.log('Status:', doc.status); // "SECURED"
return doc.hash;
} catch (error) {
console.error('Error:', error.message);
}
}
uploadDocument('./land_tenure_certificate.pdf');
curl --request POST \
--url https://live.agribackup.com/api/v1/enterprise/eudr/declarations/documents/upload \
--header 'X-API-Key: sk_live_YOUR_API_KEY' \
--header 'Idempotency-Key: 550e8400-e29b-41d4-a716-446655440003' \
--form 'file=@land_tenure_certificate.pdf'
The response:{
"documentId": "doc_123456789abc",
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"referenceUrl": "https://live.agribackup.com/api/v1/enterprise/eudr/documents/doc_123456789abc/download",
"status": "SECURED"
}
Accepted file types are PDF, JPG, and PNG. Files larger than the configured upload limit are rejected with 413 Payload Too Large.
Generate the Due Diligence Statement
Send POST /api/v1/enterprise/eudr/declarations/dds with your batchId and an array of SHA-256 document hashes from Step 1. If you have no land tenure documents to attach, pass an empty legalDocumentHashes array.The endpoint returns 202 Accepted and a jobId. AgriBackup assembles the TRACES NT V3 XML, anchors the document hashes on Hedera, and stores the resulting payload for retrieval.import agribackup
from agribackup.rest import ApiException
import uuid
configuration = agribackup.Configuration()
configuration.api_key['ApiKeyAuth'] = 'sk_live_YOUR_API_KEY'
with agribackup.ApiClient(configuration) as api_client:
try:
declarations_api = agribackup.EnterpriseDeclarationsApi(api_client)
job = declarations_api.generate_dds(
idempotency_key=str(uuid.uuid4()),
dds_generation_request={
"batchId": "uuid-1234",
"legalDocumentHashes": [
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
]
}
)
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' });
async function generateDds(batchId, documentHashes) {
try {
const job = await client.declarations.generateDds(
{
batchId,
legalDocumentHashes: documentHashes
},
{ idempotencyKey: randomUUID() }
);
console.log('Job accepted:', job.jobId);
console.log('Estimated duration (s):', job.estimatedDurationSec);
return job;
} catch (error) {
console.error('Error:', error.message);
}
}
generateDds('uuid-1234', [
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
]);
curl --request POST \
--url https://live.agribackup.com/api/v1/enterprise/eudr/declarations/dds \
--header 'X-API-Key: sk_live_YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: 550e8400-e29b-41d4-a716-446655440004' \
--data '{
"batchId": "uuid-1234",
"legalDocumentHashes": [
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
]
}'
Response (202 Accepted):{
"jobId": "job_556677",
"status": "PENDING",
"estimatedDurationSec": 10,
"createdAt": "2026-07-08T11:00:00Z"
}
Submit the DDS to TRACES NT
Once DDS generation completes (the dds.submitted webhook fires, or you poll the job to COMPLETED), retrieve the referenceId and submit to TRACES NT:POST /api/v1/enterprise/eudr/declarations/dds/{referenceId}/submit
AgriBackup authenticates to TRACES NT using the credentials you stored in Step 0 and files the XML payload on your behalf.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:
declarations_api = agribackup.EnterpriseDeclarationsApi(api_client)
result = declarations_api.submit_dds(reference_id="DDS-AB12CD34")
print("Success:", result.success)
print("TRACES reference number:", result.traces_reference_number)
print("Verification number:", result.verification_number)
print("Hedera TX:", result.hedera_transaction_id)
except ApiException as e:
print("Error:", e)
import { AgriBackupClient } from '@agribackup/sdk';
const client = new AgriBackupClient({ apiKey: 'sk_live_YOUR_API_KEY' });
async function submitDds(referenceId) {
try {
const result = await client.declarations.submit(referenceId);
console.log('Success:', result.success);
console.log('TRACES reference number:', result.tracesReferenceNumber);
console.log('Verification number:', result.verificationNumber);
console.log('Hedera TX:', result.hederaTransactionId);
} catch (error) {
console.error('Error:', error.message);
}
}
submitDds('DDS-AB12CD34');
curl --request POST \
--url https://live.agribackup.com/api/v1/enterprise/eudr/declarations/dds/DDS-AB12CD34/submit \
--header 'X-API-Key: sk_live_YOUR_API_KEY'
A successful submission response:{
"success": true,
"tracesReferenceNumber": "EUDR-2026-KE-00123",
"verificationNumber": "VER-9988776655",
"status": "SUBMITTED",
"submittedAt": "2026-07-08T11:05:30Z",
"hederaTransactionId": "0.0.12345@1234567890.123456789"
}
If your TRACES NT credentials have expired or were revoked from the vault, this call returns 403 Forbidden. Re-ingest your credentials via POST /api/v1/enterprise/eudr/credentials/traces before retrying.
Retrieve the finalized DDS XML payload
At any point after generation, fetch the complete TRACES NT V3 XML and Hedera consensus timestamp using GET /api/v1/enterprise/eudr/declarations/dds/{referenceId}.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:
declarations_api = agribackup.EnterpriseDeclarationsApi(api_client)
dds = declarations_api.get_dds_payload(reference_id="DDS-AB12CD34")
print("Reference ID:", dds.reference_id)
print("Submission status:", dds.traces_submission_status)
print("Hedera consensus:", dds.hedera_consensus_timestamp)
# Write the XML to disk for archival
with open("dds_DDS-AB12CD34.xml", "w") as f:
f.write(dds.traces_nt_xml)
except ApiException as e:
print("Error:", e)
import { AgriBackupClient } from '@agribackup/sdk';
import { writeFileSync } from 'fs';
const client = new AgriBackupClient({ apiKey: 'sk_live_YOUR_API_KEY' });
async function getDdsPayload(referenceId) {
try {
const dds = await client.declarations.getPayload(referenceId);
console.log('Submission status:', dds.tracesSubmissionStatus);
console.log('Hedera consensus:', dds.hederaConsensusTimestamp);
writeFileSync(`dds_${referenceId}.xml`, dds.tracesNtXml);
console.log('XML written to disk.');
} catch (error) {
console.error('Error:', error.message);
}
}
getDdsPayload('DDS-AB12CD34');
curl --request GET \
--url https://live.agribackup.com/api/v1/enterprise/eudr/declarations/dds/DDS-AB12CD34 \
--header 'X-API-Key: sk_live_YOUR_API_KEY'
The DdsRetrievalResponse schema:{
"referenceId": "DDS-AB12CD34",
"batchId": "uuid-1234",
"tracesNtXml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>...",
"hederaConsensusTimestamp": "0.0.12345@1234567890.123456789",
"tracesSubmissionStatus": "SUBMITTED"
}
DDS submission statuses
tracesSubmissionStatus | Meaning |
|---|
PENDING | DDS job accepted but XML not yet assembled |
GENERATED | XML built and Hedera-anchored; ready for submission |
SUBMITTED | Filed to TRACES NT; tracesReferenceNumber is available |
Revoke a submitted DDS
If you need to cancel a filed statement — for example, due to a supplier change or a shipment cancellation — send:
POST /api/v1/enterprise/eudr/declarations/dds/{referenceId}/revoke
Revocation is irreversible. A revoked DDS cannot be reinstated; you must generate and submit a new one. The TRACES NT reference number associated with the revoked statement is permanently invalidated.
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:
declarations_api = agribackup.EnterpriseDeclarationsApi(api_client)
result = declarations_api.revoke_dds(reference_id="DDS-AB12CD34")
print("Revoked:", result.success)
print("TRACES reference:", result.traces_reference_number)
print("Revoked at:", result.revoked_at)
print("Hedera TX:", result.hedera_transaction_id)
except ApiException as e:
print("Error:", e)
import { AgriBackupClient } from '@agribackup/sdk';
const client = new AgriBackupClient({ apiKey: 'sk_live_YOUR_API_KEY' });
async function revokeDds(referenceId) {
try {
const result = await client.declarations.revoke(referenceId);
console.log('Revoked:', result.success);
console.log('TRACES reference:', result.tracesReferenceNumber);
console.log('Revoked at:', result.revokedAt);
console.log('Hedera TX:', result.hederaTransactionId);
} catch (error) {
console.error('Error:', error.message);
}
}
revokeDds('DDS-AB12CD34');
curl --request POST \
--url https://live.agribackup.com/api/v1/enterprise/eudr/declarations/dds/DDS-AB12CD34/revoke \
--header 'X-API-Key: sk_live_YOUR_API_KEY'
Use GET /api/v1/enterprise/eudr/declarations/dds with cursor-based pagination to list all generated statements for your organization and filter by tracesSubmissionStatus.