Skip to main content
POST
https://api.dcycle.io
/
api
/
v1
/
logistics
/
presigned_url
Upload CSV
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({requests_file_name: '<string>', recharges_file_name: '<string>'})
};

fetch('https://api.dcycle.io/api/v1/logistics/presigned_url', options)
  .then(res => res.json())
  .then(res => console.log(res))
  .catch(err => console.error(err));
{
  "requests.upload_url": "<string>",
  "requests.file_id": "<string>",
  "requests.expires_at": "<string>"
}

Bulk Shipment Upload

Upload thousands of shipments 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. Shipments are persisted in the database and available for subsequent reports.

Upload Flow

1

Request presigned URL

Make a POST request to /api/v1/logistics/presigned_url 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 and calculates emissions
4

Verify results

Check your clients and reports with the listing endpoints

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

requests_file_name
string
required
Name of the CSV file you’re going to uploadExample: "correos_shipments_2024-11.csv"
recharges_file_name
string
Optional: Name of the recharges/returns CSV fileExample: "correos_returns_2024-11.csv"

Response

requests.upload_url
string
Presigned S3 URL to upload the shipments file
requests.file_id
string
File UUID (use it for tracking)
requests.expires_at
string
Timestamp when the URL expires (15 minutes)

Example

curl -X POST "https://api.dcycle.io/api/v1/logistics/presigned_url" \
  -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 '{
    "requests_file_name": "shipments_2024-11.csv"
  }'

Successful Response

{
  "requests": {
    "upload_url": "https://dcycle-logistics.s3.amazonaws.com/uploads/...",
    "file_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "expires_at": "2024-11-20T15:45:00Z"
  }
}

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 @shipments_2024-11.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:
client,origin,destination,toc,load,load_unit,date,reference
Correos Express,Madrid,Barcelona,van_diesel,1000,kg,2024-11-20,REF-001
Correos Express,Barcelona,Valencia,truck_diesel,2500,kg,2024-11-20,REF-002
Correos Express,Madrid,Sevilla,van_electric,800,kg,2024-11-21,REF-003

Required Columns

ColumnDescriptionExample
clientClient name"Correos Express"
originOrigin city and country"Madrid, Spain"
destinationDestination city and country"Barcelona, Spain"
tocTransport type"van_diesel"
loadTransported quantity1000
load_unitUnit of measurement"kg"
dateShipment date"2024-11-20"
referenceShipment reference"REF-001"

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 the format and data
  2. Processing: Each row is processed and emissions are calculated
  3. Storage: Shipments are saved in the database
  4. Notification: (Coming soon) You’ll receive a webhook when complete
Processing can take from a few seconds to several minutes depending on file size.

Step 4: Verify Results

After processing, you can query your data:
# List clients
curl "https://api.dcycle.io/api/v1/logistics/clients" \
  -H "Authorization: Bearer ${DCYCLE_API_KEY}" \
  -H "x-organization-id: ${DCYCLE_ORG_ID}" \
  -H "x-user-id: ${DCYCLE_USER_ID}"

# Get report
curl "https://api.dcycle.io/api/v1/logistics/clients/report?client=Correos+Express&start_date=2024-11-01&end_date=2024-11-30" \
  -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 DcycleLogistics:
    def __init__(self, api_key, org_id):
        self.api_key = api_key
        self.org_id = org_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/logistics/presigned_url",
            headers=self.headers,
            json={"requests_file_name": file_name}
        )
        response.raise_for_status()

        upload_url = response.json()["requests"]["upload_url"]
        file_id = response.json()["requests"]["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 clients
        clients = self.get_clients()
        print(f"✅ Available clients: {', '.join(clients)}")

        return file_id

    def get_clients(self):
        """List logistics clients"""
        response = requests.get(
            f"{self.base_url}/api/v1/logistics/clients",
            headers=self.headers
        )
        response.raise_for_status()
        return response.json()

# Usage
if __name__ == "__main__":
    client = DcycleLogistics(
        api_key=os.getenv("DCYCLE_API_KEY"),
        org_id=os.getenv("DCYCLE_ORG_ID")
    )

    client.upload_csv("shipments_2024-11.csv")

Common Errors

400 Bad Request - Missing file_name

{
  "detail": "requests_file_name is required"
}
Solution: Include the requests_file_name parameter in the 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 columns Solution: Verify that your CSV has all required columns and correct format.

Limits and Recommendations

LimitValue
Maximum file size100 MB
Maximum rows per CSV50,000
URL expiration time15 minutes
Processing time~1 second per 100 rows
For very large files (>50k rows), consider splitting them into multiple smaller CSVs.