Skip to main content
POST
https://api.dcycle.io
/
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));
{
  "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 name
2

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

x-api-key
string
required
Your API key for authenticationExample: sk_live_1234567890abcdef
x-organization-id
string
required
Your organization UUIDExample: ff4adcc7-8172-45fe-9cf1-e90a6de53aa9
x-user-id
string
required
Your user UUIDExample: a1b2c3d4-e5f6-7890-abcd-ef1234567890

Body Parameters

file_name
string
required
Name of the CSV file you’re going to uploadExample: "2024_q1_purchases.csv"
content_type
string
MIME type of the file (defaults to CSV)Example: "text/csv"

Response

upload_url
string
Presigned S3 URL to upload the purchases file
file_name
string
Sanitized file name (alphanumeric only)
file_id
string
File UUID for tracking
destination_file_key
string
Full S3 key where file will be stored
message
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"
  }'

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

ColumnDescriptionValid ValuesExample
quantityPurchase amountPositive number25000.50
unit_idUUID of currency unitUUID from /api/v1/units?type=fiat_currencyeur-unit-uuid
sectorEconomic sectorSector name from EXIOBASE"IT Services"
product_nameProduct or service nameFree text"Cloud Computing"
purchase_dateDate of purchaseYYYY-MM-DD format2024-01-15
countryCountry of purchaseISO 3166-1 alpha-2 codeES

Optional Columns

ColumnDescriptionExample
descriptionPurchase description or notes"Q1 AWS infrastructure"
recycledRecycled content percentage (0.0 to 1.0)0.3 (30% recycled)
frequencyPurchase frequencyonce, weekly, monthly, quarterly, yearly
supplier_business_nameSupplier company name"AWS EMEA SARL"
supplier_countrySupplier countryES, US, etc.

Column Details

quantity:
  • Must be positive number
  • Typically in currency units (EUR, USD, etc.)
  • No negative values allowed
unit_id:
  • 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
sector:
  • Economic sector classification
  • Based on EXIOBASE or NAICS codes
  • Examples: "IT Services", "Manufacturing", "Transportation", "Construction"
product_name:
  • Descriptive name of the product or service
  • Free text field
  • Examples: "Cloud Computing", "Office Furniture", "Legal Services"
purchase_date:
  • Must be today or in the past
  • Format: YYYY-MM-DD
  • Used to select appropriate emission factors by year
country:
  • 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)
frequency:
  • For recurring purchases
  • Values: once, weekly, monthly, quarterly, yearly
  • Default: once
recycled:
  • Percentage of recycled content
  • Range: 0.0 (0%) to 1.0 (100%)
  • Reduces emission calculations
  • Leave empty or 0.0 if not applicable

Download Template

Download CSV Template

Template with correct format and sample data

Step 3: Asynchronous Processing

Once the CSV is uploaded:
  1. Validation: The system validates format, units, sectors, and dates
  2. Processing: Each row is processed and purchases are created
  3. Emission Calculation: CO2e values calculated using economic input-output factors
  4. Supplier Matching: Suppliers are matched or created based on business_name and country
  5. 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")

Common Errors

400 Bad Request - Missing file_name

{
  "detail": "Field required",
  "field": "file_name"
}
Solution: Include the 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

LimitValue
Maximum file size100 MB
Maximum rows per CSV50,000
URL expiration time15 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

  1. Clean supplier names: Use consistent naming (e.g., “AWS EMEA SARL” not “Amazon Web Services” or “AWS”)
  2. Validate unit IDs: Check that unit_id exists before upload using /api/v1/units?type=fiat_currency
  3. Standardize sectors: Use consistent sector names across purchases
  4. Include recycled content: For products with recycled materials, include the percentage

Error Handling

After upload, check for purchases with status: "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:
  1. Export current purchases
  2. Modify or add rows
  3. Re-upload (system will handle duplicates)

Economic Sectors

Common sectors for spend-based emission calculations:

Manufacturing & Production

  • Manufacturing - General manufacturing
  • Food & Beverage Manufacturing
  • Textile Manufacturing
  • Chemical Manufacturing

Services

  • IT Services - Technology and software services
  • Professional Services - Consulting, legal, accounting
  • Financial Services - Banking, insurance
  • Healthcare Services - Medical services

Transportation & Logistics

  • Transportation - General transport services
  • Air Transport - Air freight and passenger
  • Water Transport - Sea and inland water transport
  • Land Transport - Road and rail transport

Construction & Real Estate

  • Construction - Building and infrastructure
  • Real Estate - Property services

Other

  • Office Supplies - Stationery, equipment
  • Utilities - Water, waste management
  • Agriculture - 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

When recycled 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