Skip to main content

EUDR Public API → Simplify EUDR Compliance

Prewave simplifies EUDR compliance through a streamlined, end-to-end approach

Sabine avatar
Written by Sabine
Updated yesterday

Our API supports your journey to compliance in five key stages - Implementation, Supplier Engagement, Risk Assessment, Risk Mitigation, and Due Diligence & Record Keeping

Built around key EUDR requirements, Prewave APIs help you manage the essential steps efficiently. Once you've identified the relevant products under the EUDR framework, it’s important to ensure that their associated suppliers are already available in the Prewave system before uploading your product list. Our APIs are designed to guide you through this process smoothly, covering all core compliance scenarios.

Getting started and setting up your API

To get started with the API, you’ll first need an API token to access it. Please reach out to your dedicated Customer Success Manager, who will provide you with the token.

EUDR Endpoints

Below, we've listed all the EUDR-specific endpoints available in the API, which allows you to manage and retrieve EUDR information. For more detailed information you can access our API documentation.

Here are some API tutorials to help you explore and test the different functionalities available.

Products

Inbound Products

To enable product onboarding via the API, suppliers must first be set up in the system. The product reference serves as a unique identifier to link the products in your ERP system with those in Prewave.

If you wish to update product information, such as the annual quantity, you can do so by modifying the inbound product data.

Create Inbound Product

Product Master Data

# Data needed to create a product, available in customer ERP system

productName = "A Test Integration Product"

productReference = "TIP111222"

hsCode = "1801"

annualQuantity = 1000

annualSpend = 20000

supplierName = "Fantasy Department For Testing 5"

The commodity ID as well as the Supplier Reference Number are necessary to create a product.

# Get internal supplier ID

url = f"{base_url}/public/v2/eudr/suppliers?query={supplierName}"

response = requests.request("GET", url, headers=headers)

supplierId = response.json()[0]['id']

print(supplierId)

# Alternatively supplier name to supplier ID mapping stored in customer ERP

suppliers = {"Fantasy Department For Testing 5":13756990}

# HS codes to internal commodity ID mapping

url = f"{base_url}/public/v2/eudr/hscodes"

response = requests.request("GET", url, headers=headers)

data = response.json()

code_to_commodity = {item['code']: item['commodityId'] for item in data}

# Create one inbound product

url = f"{base_url}/public/v2/eudr/products/inbound"

commodityId = code_to_commodity[hsCode]

payload = json.dumps({

"name": productName,

"scientificName": "",

"reference": productReference,

"commodityId": commodityId,

"hsCode": hsCode,

"annualSpend": annualSpend,

"annualQuantity": annualQuantity,

"unit": "kg",

"supplierId": supplierId

})

response = requests.request("POST", url, headers=headers, data=payload)

productId = int(response.text)

print(f"New product created, ID: {productId}")

Update Inbound Product

Set a new value to update a quantity of an inbound product

newAnnualQuantity = 5500

Ready to send the update product request

# Update one inbound product

url = f"{base_url}/public/v2/eudr/products/inbound/{productId}"

payload = json.dumps({

"name": productName,

"scientificName": "",

"reference": newProductReference,

"commodityId": commodityId,

"hsCode": hsCode,

"annualSpend": 1000,

"annualQuantity": newAnnualQuantity,

"unit": "kg",

"supplierId": supplierId

})

response = requests.request("PUT", url, headers=headers, data=payload)

print(response)

Get Product

To check the current status of origin requests and verify whether the conditions for DDS are met, you can use this endpoint. Additionally, it provides access to the deforestation check results, the Supplier Maturity Assessment outcomes, and the legality assessment score.

url = f"{base_url}/public/v2/eudr/products/inbound?scope=IDS&ids={productId}&includeOrigins=false"

response = requests.request("GET", url, headers=headers)

print(json.dumps(response.json()['content'], indent=4))

Outbound Products

To create a new outbound product, you’ll need to provide the supplier reference ID and the commodity ID. Additionally, outbound products must be linked to the relevant inbound products to ensure compliance.

Outbound Products

Product Master Data

# Data needed to create a product, available in customer ERP system

productName = "A Integration Outbound Product"

productReference = "TIOP333444"

hsCode = "1806"

annualQuantity = 6000

annualRevenue = 45000

Supplier ID and Commodity ID are needed to create a product

# HS codes to internal commodity ID mapping

url = f"{base_url}/public/v2/eudr/hscodes"

response = requests.request("GET", url, headers=headers)

data = response.json()

code_to_commodity = {item['code']: item['commodityId'] for item in data}

Create a new Outbound Product

url = f"{base_url}/public/v2/eudr/products/outbound"

commodityId = code_to_commodity[hsCode]

payload = json.dumps({

"name": productName,

"scientificName": "",

"reference": productReference,

"commodityId": commodityId,

"hsCode": hsCode,

"annualRevenue": annualRevenue,

"annualQuantity": annualQuantity,

"unit": "kg"

})

response = requests.request("POST", url, headers=headers, data=payload)

productId = int(response.text)

print(f"New outbound product created, ID: {productId}")

List of Inbound Product References linked to the Outbound Product

inboundProductReferences = ["050405", "515566", "524962"]

Filter by product reference to get internal inbound product ids using query parameter

inboundProductIds =[]

for inboundProductRef in inboundProductReferences:

url = f"{base_url}/public/v2/eudr/products/inbound?q={inboundProductRef}&includeOrigins=false&page=0&size=10"

response = requests.request("GET", url, headers=headers)

for product in response.json()['content']:

inboundProductIds.append(product['id'])

print(inboundProductIds)

Link inbound products to outbound product

url = f"{base_url}/public/v2/eudr/products/outbound/{productId}/inbound"

payload = json.dumps({

"inboundProductIds": inboundProductIds

})

response = requests.request("POST", url, headers=headers, data=payload)

print(response)

Unlink inbound from outbound

unlinkProductId = inboundProductIds[0]

url = f"{base_url}/public/v2/eudr/products/outbound/{productId}/inbound/{unlinkProductId}"

payload = json.dumps({

"inboundProductIds": inboundProductIds

})

response = requests.request("DELETE", url, headers=headers, data=payload)

print(response)

Filter Products

You can filter products based on various criteria, such as DDS status or inbound products with non-negligible deforestation check results. It is useful to create certain reportings.

Examples

# Filter inbound products with non-negligible deforestation checks and get their origins

filter = "dfs=NonNegligible"

url = f"{base_url}/public/v2/eudr/products/inbound?{filter}&includeOrigins=false&page=0&size=10"

response = requests.request("GET", url, headers=headers)

print(json.dumps(response.json()['content'], indent=4))

# Filter products with blocked DDS

filter = "dd=Blocked"

url = f"{base_url}/public/v2/eudr/products/inbound?{filter}&includeOrigins=false&page=0&size=10"

response = requests.request("GET", url, headers=headers)

print(json.dumps(response.json()['content'], indent=4))

# Filter by product name, supplier or product reference using query parameter

filter = "q=Cocoa"

url = f"{base_url}/public/v2/eudr/products/inbound?{filter}&includeOrigins=false&page=0&size=10"

response = requests.request("GET", url, headers=headers)

print(json.dumps(response.json()['content'], indent=4))

Get All Product Data

To retrieve all product data, you can use this endpoint. Please note that the response is paginated and may take several minutes to load, depending on the total number of products.

Get All Product Data

Get all products, filter our origins for faster response.

urls = [

f"{base_url}/public/v2/eudr/products/inbound?filter=scope%3DNAME&includeOrigins=false",

f"{base_url}/public/v2/eudr/products/outbound?filter=scope%3DNAME&includeOrigins=false"

]

data = []

for url in urls:

response = requests.get(url, headers=headers)

for page in range(response.json()["totalPages"]+1):

response = requests.get(url+f"&page={page}", headers=headers)

# Check if the response is successful

if response.status_code == 200:

json_data = response.json()["content"]

# Ensure that the response is a list

if isinstance(json_data, list):

data.extend(json_data)

else:

print(f"Unexpected data format from {url}: Expected a list.")

else:

print(f"Failed to fetch data from {url}. Status code: {response.status_code}")

# 'data' as a flat list containing all JSON objects from both responses

print(f"{len(data)} products")

Origins

To send out product origin requests, connection contacts must first be added. Initially, these contacts can be uploaded using an Excel file. Later, these can be managed and added through the Site Upsert API.

Origins

Add supplier connection contacts

url = f"{base_url}/public/v2/eudr/suppliers/{supplierId}/connection-contacts"

response = requests.request("GET", url, headers=headers)

connectionContactId = response.json()[1]['id']

print(response.text)

Send out origin requests to your supplier connection contacts

url = f"{base_url}/public/v2/eudr/origin-requests"

payload = json.dumps({

"items": [

{

"productId": productId,

"supplierId": supplierId,

"connectionContactIds": [

connectionContactId

],

"comment": f"Product origins requested for {productName}, HS code : {hsCode}",

"sendCopy": False,

"ccEmails": [

"string"

],

"references": [

{

"productId": productId,

"reference": "PO9876",

"referenceType": "PurchaseOrder"

}

]

}

]

})

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)

Due Diligence Statements

Submit DDS

If you wish to submit a Due Diligence Statement (DDS) within TRACES NT or use the TRACES Emulator for testing purposes in the sandbox environment, you can use this endpoint. Please note that submitting a DDS in the production environment is legally binding.

url = f"{base_url}/public/v2/eudr/customer-dds/{ddsId}/submission"

response = requests.request("PUT", url, headers=headers)

print(response.text)

Fetch DDS

This endpoint retrieves the latest active Due Diligence Statement (DDS), including the reference and verification numbers if it has been submitted to TRACES. It can be used for purposes such as invoices, shipments, POs, and customs. Additionally, it allows you to verify the validity and check the expiration date of the DDS.

Get a customer DDS for one product

url = f"{base_url}/public/v2/eudr/products/{productId}/customer-dds"

response = requests.request("GET", url, headers=headers)

print(json.dumps(response.json(), indent=4))

DDS Status Reporting

To get an overview of the DDS status, you can generate a report. The results will be displayed in a table, including a calculated EUDR compliance score. Additionally, a high-level chart is created to visualize your overall compliance status.

Report Product DDS Status

import pandas as pd

product_data = []

for item in data:

try:

product_name = item.get('name', 'N/A')

supplier_name = item.get('supplier', {}).get('name', 'N/A')

dds_status = item.get('ddsStatus', {}).get('status', 'N/A')

dds_status_reason = item.get('ddsStatus', {}).get('reason', 'N/A')

product_data.append({

'Product Name': product_name,

'Supplier Name': supplier_name,

'DDS Status': dds_status,

'DDS Status Reason': dds_status_reason

})

except AttributeError:

# Handle cases where 'supplier' might not be a dictionary

print(f"Skipping an item due to a missing or invalid 'supplier' field")

continue

# Create a Pandas DataFrame from the extracted product data

df = pd.DataFrame(product_data)

df

High level EUDR compliance stats

complaint_count = df[df['DDS Status'].isin(['Allowed', 'Available', 'Draft'])].shape[0]

noncompliant_count = df[df['DDS Status'].isin(['Blocked', 'WaitingForOrigins'])].shape[0]

compliance_percentage = (complaint_count / (complaint_count + noncompliant_count)) * 100

print(f"Compliant products: {complaint_count}")

print(f"Non-compliant products: {noncompliant_count}")

print(f"Product compliance score: {compliance_percentage:.0f}%")

Product DDS status stats

grouped_df = df.groupby('DDS Status')

grouped_df.size()

API Troubleshooting

Error Code

Response Message

Fix

400

"code": "countryID"

Required property value is missing. Provide valid JSONs

401

"code": "session_expired"

Check if your API key is valid, regenerate if needed.

403

"code": "access_denied"

Verify API key and request headers.

404

"code": "resource_not_found"

Ensure the endpoint URL is correct.

500

"code": "server_error"

Retry after refreshing or clearing cookies.

400

"code": "foreign_key_violation"

It is related to the connection between the commodity ID and the HS code. HS Code and commodity have to be of the same type (e.g. Wood related products need a wood HS code).

Did this answer your question?