Bulk Upload Purchases
const options = {
method: 'POST',
headers: {
'x-api-key': '<x-api-key>',
'x-organization-id': '<x-organization-id>',
'x-user-id': '<x-user-id>',
'Content-Type': 'application/json'
},
body: JSON.stringify({file_name: '<string>', content_type: '<string>'})
};
fetch('https://api.dcycle.io/api/v1/purchases/bulk/csv', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.dcycle.io/api/v1/purchases/bulk/csv"
payload = {
"file_name": "<string>",
"content_type": "<string>"
}
headers = {
"x-api-key": "<x-api-key>",
"x-organization-id": "<x-organization-id>",
"x-user-id": "<x-user-id>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)curl --request POST \
--url https://api.dcycle.io/api/v1/purchases/bulk/csv \
--header 'Content-Type: application/json' \
--header 'x-api-key: <x-api-key>' \
--header 'x-organization-id: <x-organization-id>' \
--header 'x-user-id: <x-user-id>' \
--data '
{
"file_name": "<string>",
"content_type": "<string>"
}
'{
"upload_url": "<string>",
"file_name": "<string>",
"file_id": "<string>",
"destination_file_key": "<string>",
"message": "<string>"
}Bulk Upload Purchases
Get a presigned URL to upload multiple purchases via CSV
POST
/
api
/
v1
/
purchases
/
bulk
/
csv
Bulk Upload Purchases
const options = {
method: 'POST',
headers: {
'x-api-key': '<x-api-key>',
'x-organization-id': '<x-organization-id>',
'x-user-id': '<x-user-id>',
'Content-Type': 'application/json'
},
body: JSON.stringify({file_name: '<string>', content_type: '<string>'})
};
fetch('https://api.dcycle.io/api/v1/purchases/bulk/csv', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.dcycle.io/api/v1/purchases/bulk/csv"
payload = {
"file_name": "<string>",
"content_type": "<string>"
}
headers = {
"x-api-key": "<x-api-key>",
"x-organization-id": "<x-organization-id>",
"x-user-id": "<x-user-id>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)curl --request POST \
--url https://api.dcycle.io/api/v1/purchases/bulk/csv \
--header 'Content-Type: application/json' \
--header 'x-api-key: <x-api-key>' \
--header 'x-organization-id: <x-organization-id>' \
--header 'x-user-id: <x-user-id>' \
--data '
{
"file_name": "<string>",
"content_type": "<string>"
}
'{
"upload_url": "<string>",
"file_name": "<string>",
"file_id": "<string>",
"destination_file_key": "<string>",
"message": "<string>"
}Bulk Upload Purchases
Upload hundreds of purchases at once via a CSV file. This endpoint returns a presigned S3 URL so you can upload your file, which will be processed asynchronously.This method is recommended for bulk uploads. Purchases are persisted in the database and emissions are calculated automatically based on economic input-output factors.
Upload Flow
1
Request presigned URL
Make a POST request to
/api/v1/purchases/bulk/csv with your file name2
Upload to S3
Use the presigned URL to upload your CSV directly to S3 (PUT request)
3
Asynchronous processing
The system processes your file in the background, creating purchases and calculating emissions
4
Verify results
Check your purchases with the List Purchases endpoint
Step 1: Request Presigned URL
Request
Headers
string
required
Your API key for authenticationExample:
sk_live_1234567890abcdefstring
required
Your organization UUIDExample:
ff4adcc7-8172-45fe-9cf1-e90a6de53aa9string
required
Your user UUIDExample:
a1b2c3d4-e5f6-7890-abcd-ef1234567890Body Parameters
string
required
Name of the CSV file you’re going to uploadExample:
"2024_q1_purchases.csv"string
MIME type of the file (defaults to CSV)Example:
"text/csv"Response
string
Presigned S3 URL to upload the purchases file
string
Sanitized file name (alphanumeric only)
string
File UUID for tracking
string
Full S3 key where file will be stored
string
Confirmation message
Example
curl -X POST "https://api.dcycle.io/api/v1/purchases/bulk/csv" \
-H "Authorization: Bearer ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "x-user-id: ${DCYCLE_USER_ID}" \
-H "Content-Type: application/json" \
-d '{
"file_name": "2024_q1_purchases.csv"
}'
import requests
import os
api_key = os.getenv("DCYCLE_API_KEY")
org_id = os.getenv("DCYCLE_ORG_ID")
user_id = os.getenv("DCYCLE_USER_ID")
headers = {
"Authorization": f"Bearer {api_key}",
"x-organization-id": org_id,
"x-user-id": user_id,
"Content-Type": "application/json"
}
payload = {
"file_name": "2024_q1_purchases.csv"
}
response = requests.post(
"https://api.dcycle.io/api/v1/purchases/bulk/csv",
headers=headers,
json=payload
)
result = response.json()
upload_url = result["upload_url"]
file_id = result["file_id"]
print(f"Upload URL obtained: {upload_url[:50]}...")
print(f"File ID: {file_id}")
const axios = require('axios');
const apiKey = process.env.DCYCLE_API_KEY;
const orgId = process.env.DCYCLE_ORG_ID;
const userId = process.env.DCYCLE_USER_ID;
const headers = {
'Authorization': `Bearer ${apiKey}`,
'x-organization-id': orgId,
'x-user-id': userId,
'Content-Type': 'application/json'
};
const payload = {
file_name: '2024_q1_purchases.csv'
};
axios.post(
'https://api.dcycle.io/api/v1/purchases/bulk/csv',
payload,
{ headers }
)
.then(response => {
const { upload_url, file_id } = response.data;
console.log('Upload URL:', upload_url.substring(0, 50) + '...');
console.log('File ID:', file_id);
})
.catch(error => console.error(error));
Successful Response
{
"upload_url": "https://dcycle-purchases.s3.amazonaws.com/files/dcycle/...",
"file_name": "2024_q1_purchases",
"file_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"destination_file_key": "files/dcycle/.../2024_q1_purchases.csv",
"message": "Upload pre-signed url generated"
}
Step 2: Upload CSV to S3
Use the presigned URL to upload your CSV file directly to S3:# The presigned URL already includes authentication parameters
curl -X PUT "${UPLOAD_URL}" \
-H "Content-Type: text/csv" \
--data-binary @2024_q1_purchases.csv
import requests
# Read CSV file
with open('2024_q1_purchases.csv', 'rb') as file:
csv_data = file.read()
# Upload to S3
response = requests.put(
upload_url,
data=csv_data,
headers={'Content-Type': 'text/csv'}
)
if response.status_code == 200:
print("✅ File uploaded successfully")
else:
print(f"❌ Error: {response.status_code}")
const fs = require('fs');
const axios = require('axios');
// Read CSV file
const csvData = fs.readFileSync('2024_q1_purchases.csv');
// Upload to S3
axios.put(uploadUrl, csvData, {
headers: { 'Content-Type': 'text/csv' }
})
.then(() => console.log('✅ File uploaded successfully'))
.catch(error => console.error('❌ Error:', error));
The presigned URL expires in 15 minutes. Make sure to upload the file within that time.
CSV Format
The CSV must follow this structure for creating purchases:quantity,unit_id,sector,product_name,purchase_date,country,description,recycled,frequency,supplier_business_name,supplier_country
25000.50,eur-unit-uuid,IT Services,Cloud Computing,2024-01-15,ES,AWS monthly costs,0.0,monthly,AWS EMEA SARL,IE
15000.00,eur-unit-uuid,Office Supplies,Printer Paper,2024-01-10,ES,Q1 paper purchase,0.3,quarterly,Office Depot,ES
8500.75,eur-unit-uuid,Transportation,Courier Services,2024-01-20,ES,Monthly DHL costs,0.0,monthly,DHL Express,ES
Required Columns
| Column | Description | Valid Values | Example |
|---|---|---|---|
quantity | Purchase amount | Positive number | 25000.50 |
unit_id | UUID of currency unit | UUID from /api/v1/units?type=fiat_currency | eur-unit-uuid |
sector | Economic sector | Sector name from EXIOBASE | "IT Services" |
product_name | Product or service name | Free text | "Cloud Computing" |
purchase_date | Date of purchase | YYYY-MM-DD format | 2024-01-15 |
country | Deprecated. Country of purchase — no longer used for emission calculations (the country is taken from the supplier). Kept for backward compatibility. | ISO 3166-1 alpha-2 code | ES |
Optional Columns
| Column | Description | Example |
|---|---|---|
description | Purchase description or notes | "Q1 AWS infrastructure" |
recycled | Recycled content percentage (0.0 to 1.0) | 0.3 (30% recycled) |
frequency | Purchase frequency | once, weekly, monthly, quarterly, yearly |
supplier_business_name | Supplier company name | "AWS EMEA SARL" |
supplier_country | Supplier country | ES, US, etc. |
Column Details
quantity:- Must be positive number
- Typically in currency units (EUR, USD, etc.)
- No negative values allowed
- Must be a valid UUID from the Units endpoint
- For spend-based purchases, use currency units (EUR, USD, GBP, etc.)
- Get valid unit IDs:
GET /api/v1/units?type=fiat_currency
- Economic sector classification
- Based on EXIOBASE or NAICS codes
- Examples:
"IT Services","Manufacturing","Transportation","Construction"
- Descriptive name of the product or service
- Free text field
- Examples:
"Cloud Computing","Office Furniture","Legal Services"
- Must be today or in the past
- Format:
YYYY-MM-DD - Used to select appropriate emission factors by year
- ISO 3166-1 alpha-2 country code (2 letters)
- Where the purchase was made
- Common values:
ES(Spain),US(USA),FR(France),DE(Germany),GB(UK)
- For recurring purchases
- Values:
once,weekly,monthly,quarterly,yearly - Default:
once
- Percentage of recycled content
- Range:
0.0(0%) to1.0(100%) - Reduces emission calculations
- Leave empty or
0.0if not applicable
Download Template
Download CSV Template
Template with correct format and sample data
Step 3: Asynchronous Processing
Once the CSV is uploaded:- Validation: The system validates format, units, sectors, and dates
- Processing: Each row is processed and purchases are created
- Emission Calculation: CO2e values calculated using economic input-output factors
- Supplier Matching: Suppliers are matched or created based on business_name and country
- Notification: (Coming soon) You’ll receive a webhook when complete
Processing can take from a few seconds to several minutes depending on file size and number of rows.
Step 4: Verify Results
After processing, you can query your purchases:# List purchases
curl "https://api.dcycle.io/api/v1/purchases" \
-H "Authorization: Bearer ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "x-user-id: ${DCYCLE_USER_ID}"
Complete Example Script
import requests
import os
from time import sleep
class DcyclePurchases:
def __init__(self, api_key, org_id, user_id):
self.api_key = api_key
self.org_id = org_id
self.user_id = user_id
self.base_url = "https://api.dcycle.io"
self.headers = {
"Authorization": f"Bearer {api_key}",
"x-organization-id": org_id,
"x-user-id": user_id,
"Content-Type": "application/json"
}
def upload_csv(self, csv_file_path):
"""Complete CSV upload: presigned URL + upload + verification"""
# 1. Get presigned URL
file_name = os.path.basename(csv_file_path)
response = requests.post(
f"{self.base_url}/api/v1/purchases/bulk/csv",
headers=self.headers,
json={"file_name": file_name}
)
response.raise_for_status()
upload_url = response.json()["upload_url"]
file_id = response.json()["file_id"]
print(f"✅ Presigned URL obtained (File ID: {file_id})")
# 2. Upload file
with open(csv_file_path, 'rb') as f:
upload_response = requests.put(
upload_url,
data=f,
headers={'Content-Type': 'text/csv'}
)
upload_response.raise_for_status()
print(f"✅ File uploaded successfully")
# 3. Wait for processing (optional)
print("⏳ Processing... (this may take a few minutes)")
sleep(30) # Wait 30 seconds
# 4. Verify purchases
purchases = self.get_purchases()
print(f"✅ Total purchases: {purchases['total']}")
return file_id
def get_purchases(self, page=1, size=50):
"""List purchases"""
response = requests.get(
f"{self.base_url}/api/v1/purchases",
headers=self.headers,
params={"page": page, "size": size}
)
response.raise_for_status()
return response.json()
# Usage
if __name__ == "__main__":
client = DcyclePurchases(
api_key=os.getenv("DCYCLE_API_KEY"),
org_id=os.getenv("DCYCLE_ORG_ID"),
user_id=os.getenv("DCYCLE_USER_ID")
)
client.upload_csv("2024_q1_purchases.csv")
#!/bin/bash
# Variables
API_KEY="${DCYCLE_API_KEY}"
ORG_ID="${DCYCLE_ORG_ID}"
USER_ID="${DCYCLE_USER_ID}"
CSV_FILE="2024_q1_purchases.csv"
BASE_URL="https://api.dcycle.io"
# 1. Get presigned URL
echo "📤 Requesting presigned URL..."
RESPONSE=$(curl -s -X POST "${BASE_URL}/api/v1/purchases/bulk/csv" \
-H "Authorization: Bearer ${API_KEY}" \
-H "x-organization-id: ${ORG_ID}" \
-H "x-user-id: ${USER_ID}" \
-H "Content-Type: application/json" \
-d "{\"file_name\": \"${CSV_FILE}\"}")
UPLOAD_URL=$(echo $RESPONSE | jq -r '.upload_url')
FILE_ID=$(echo $RESPONSE | jq -r '.file_id')
echo "✅ URL obtained (File ID: ${FILE_ID})"
# 2. Upload file
echo "📤 Uploading CSV file..."
curl -X PUT "${UPLOAD_URL}" \
-H "Content-Type: text/csv" \
--data-binary @"${CSV_FILE}"
echo "✅ File uploaded successfully"
echo "⏳ File is being processed..."
Common Errors
400 Bad Request - Missing file_name
Cause: Field required{
"detail": "Field required",
"field": "file_name"
}
file_name parameter in the request body.
403 Forbidden - URL Expired
Cause: The presigned URL expired (15 minutes) Solution: Request a new presigned URL and upload the file immediately.422 Validation Error - CSV Format
Cause: The CSV has incorrect format or missing required columns Solution: Verify that your CSV has all required columns and correct format.422 Validation Error - Invalid Unit ID
Cause:unit_id doesn’t exist or is not a currency unit
Solution: Use the Units endpoint to get valid currency unit IDs, or use EUR/USD/GBP units.
422 Validation Error - Invalid Date
Cause:purchase_date is in the future
Solution: Ensure all purchase dates are today or in the past.
422 Validation Error - Invalid Country
Cause: Country code is not ISO 3166-1 alpha-2 format Solution: Use valid 2-letter country codes (ES, US, FR, DE, GB, etc.).Limits and Recommendations
| Limit | Value |
|---|---|
| Maximum file size | 100 MB |
| Maximum rows per CSV | 50,000 |
| URL expiration time | 15 minutes |
| Processing time | ~1 second per 100 purchases |
For very large purchase lists (>50k records), consider splitting them into multiple smaller CSVs.
Best Practices
Data Preparation
- Clean supplier names: Use consistent naming (e.g., “AWS EMEA SARL” not “Amazon Web Services” or “AWS”)
- Validate unit IDs: Check that unit_id exists before upload using
/api/v1/units?type=fiat_currency - Standardize sectors: Use consistent sector names across purchases
- Include recycled content: For products with recycled materials, include the percentage
Error Handling
After upload, check for purchases withstatus: "error":
def check_for_errors():
"""Check if any purchases failed to process"""
response = requests.get(
"https://api.dcycle.io/api/v1/purchases",
headers=headers,
params={"filter_by": "status:eqerror"}
)
purchases = response.json()
if purchases['total'] > 0:
print(f"⚠️ {purchases['total']} purchases have errors:")
for purchase in purchases['items']:
print(f" - {purchase.get('product_name', 'Unnamed')}: {purchase.get('sector')}")
return purchases['total']
# Check after processing
errors_count = check_for_errors()
if errors_count == 0:
print("✅ All purchases processed successfully")
Incremental Updates
To update existing purchases or add new ones:- Export current purchases
- Modify or add rows
- Re-upload (system will handle duplicates)
Economic Sectors
Common sectors for spend-based emission calculations:Manufacturing & Production
Manufacturing- General manufacturingFood & Beverage ManufacturingTextile ManufacturingChemical Manufacturing
Services
IT Services- Technology and software servicesProfessional Services- Consulting, legal, accountingFinancial Services- Banking, insuranceHealthcare Services- Medical services
Transportation & Logistics
Transportation- General transport servicesAir Transport- Air freight and passengerWater Transport- Sea and inland water transportLand Transport- Road and rail transport
Construction & Real Estate
Construction- Building and infrastructureReal Estate- Property services
Other
Office Supplies- Stationery, equipmentUtilities- Water, waste managementAgriculture- Agricultural products
Emission factors vary significantly by sector. Using the correct sector classification is critical for accurate carbon accounting.
Special Notes
Spend-Based Method
Purchases uploaded via CSV use the spend-based method:- Emissions calculated based on economic input-output tables
- Formula:
CO2e = Amount (EUR) × Emission Factor (kg CO2e/EUR) - Emission factors vary by sector, product, and country/region
- Factors updated annually based on latest data
Recycled Content
Whenrecycled percentage is provided:
- Emission factor is reduced proportionally
- Example: 30% recycled content → 30% lower emissions
- Based on lifecycle assessment data
- Applies to materials like paper, plastics, metals
Supplier Matching
The system automatically:- Creates new suppliers if not found
- Matches by business_name and country
- Stores supplier data for future purchases
- Links purchases to supplier records
Country Codes
Use ISO 3166-1 alpha-2 country codes:- Spain:
ES - United States:
US - France:
FR - Germany:
DE - United Kingdom:
GB - Italy:
IT - Netherlands:
NL
Related Endpoints
List Purchases
View uploaded purchases
List Units
Get currency units for purchases
List Facilities
View your facilities
Authentication
Learn about API authentication
Was this page helpful?