> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agribackup.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Generate and File a Due Diligence Statement to TRACES NT

> Upload land tenure documents, generate a TRACES NT-compliant DDS, submit it to the EU portal, retrieve the finalized XML payload, and revoke if needed.

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.

<Note>
  Before generating a DDS, you must store your TRACES NT credentials in AgriBackup's vault. See the [credentials ingestion endpoint](/api-reference/enterprise-credentials/ingest-traces-nt-credentials-into-vault) (`POST /api/v1/enterprise/eudr/credentials/traces`) to securely encrypt and delegate your TRACES NT token or password.
</Note>

## 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](/guides/webhooks-setup)).

## Step-by-step DDS generation

<Steps>
  <Step title="(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.

    <CodeGroup>
      ```python Python theme={null}
      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)
      ```

      ```javascript Node.js theme={null}
      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');
      ```

      ```bash cURL theme={null}
      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'
      ```
    </CodeGroup>

    The response:

    ```json theme={null}
    {
      "documentId": "doc_123456789abc",
      "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
      "referenceUrl": "https://live.agribackup.com/api/v1/enterprise/eudr/documents/doc_123456789abc/download",
      "status": "SECURED"
    }
    ```

    <Note>
      Accepted file types are PDF, JPG, and PNG. Files larger than the configured upload limit are rejected with `413 Payload Too Large`.
    </Note>
  </Step>

  <Step title="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.

    <CodeGroup>
      ```python Python theme={null}
      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)
      ```

      ```javascript Node.js theme={null}
      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'
      ]);
      ```

      ```bash cURL theme={null}
      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"
          ]
        }'
      ```
    </CodeGroup>

    Response (`202 Accepted`):

    ```json theme={null}
    {
      "jobId": "job_556677",
      "status": "PENDING",
      "estimatedDurationSec": 10,
      "createdAt": "2026-07-08T11:00:00Z"
    }
    ```
  </Step>

  <Step title="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.

    <CodeGroup>
      ```python Python theme={null}
      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)
      ```

      ```javascript Node.js theme={null}
      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');
      ```

      ```bash cURL theme={null}
      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'
      ```
    </CodeGroup>

    A successful submission response:

    ```json theme={null}
    {
      "success": true,
      "tracesReferenceNumber": "EUDR-2026-KE-00123",
      "verificationNumber": "VER-9988776655",
      "status": "SUBMITTED",
      "submittedAt": "2026-07-08T11:05:30Z",
      "hederaTransactionId": "0.0.12345@1234567890.123456789"
    }
    ```

    <Warning>
      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.
    </Warning>
  </Step>

  <Step title="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}`.

    <CodeGroup>
      ```python Python theme={null}
      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)
      ```

      ```javascript Node.js theme={null}
      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');
      ```

      ```bash cURL theme={null}
      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'
      ```
    </CodeGroup>

    The `DdsRetrievalResponse` schema:

    ```json theme={null}
    {
      "referenceId": "DDS-AB12CD34",
      "batchId": "uuid-1234",
      "tracesNtXml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>...",
      "hederaConsensusTimestamp": "0.0.12345@1234567890.123456789",
      "tracesSubmissionStatus": "SUBMITTED"
    }
    ```
  </Step>
</Steps>

## 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
```

<Warning>
  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.
</Warning>

<CodeGroup>
  ```python Python theme={null}
  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)
  ```

  ```javascript Node.js theme={null}
  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');
  ```

  ```bash cURL theme={null}
  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'
  ```
</CodeGroup>

<Tip>
  Use `GET /api/v1/enterprise/eudr/declarations/dds` with cursor-based pagination to list all generated statements for your organization and filter by `tracesSubmissionStatus`.
</Tip>
