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

# Upload CSV

> Get a presigned URL to upload bulk shipments via CSV

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

<Note>
  This method is **recommended** for bulk uploads. Shipments are persisted in the database and available for subsequent reports.
</Note>

## Upload Flow

<Steps>
  <Step title="Request presigned URL">
    Make a POST request to `/api/v1/logistics/presigned_url` with your file name
  </Step>

  <Step title="Upload to S3">
    Use the presigned URL to upload your CSV directly to S3 (PUT request)
  </Step>

  <Step title="Asynchronous processing">
    The system processes your file in the background and calculates emissions
  </Step>

  <Step title="Verify results">
    Check your clients and reports with the listing endpoints
  </Step>
</Steps>

## Step 1: Request Presigned URL

## Request

### Headers

<ParamField header="x-api-key" type="string" required>
  Your API key for authentication

  **Example:** `sk_live_1234567890abcdef`
</ParamField>

<ParamField header="x-organization-id" type="string" required>
  Your organization UUID

  **Example:** `ff4adcc7-8172-45fe-9cf1-e90a6de53aa9`
</ParamField>

<ParamField header="x-user-id" type="string" required>
  Your user UUID

  **Example:** `a1b2c3d4-e5f6-7890-abcd-ef1234567890`
</ParamField>

### Body Parameters

<ParamField body="requests_file_name" type="string" required>
  Name of the CSV file you're going to upload

  **Example:** `"correos_shipments_2024-11.csv"`
</ParamField>

<ParamField body="recharges_file_name" type="string">
  Optional: Name of the recharges/returns CSV file

  **Example:** `"correos_returns_2024-11.csv"`
</ParamField>

### Response

<ResponseField name="requests.upload_url" type="string">
  Presigned S3 URL to upload the shipments file
</ResponseField>

<ResponseField name="requests.file_id" type="string">
  File UUID (use it for tracking)
</ResponseField>

<ResponseField name="requests.expires_at" type="string">
  Timestamp when the URL expires (15 minutes)
</ResponseField>

### Example

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  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"
    }'
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import requests
  import os

  api_key = os.getenv("DCYCLE_API_KEY")
  org_id = os.getenv("DCYCLE_ORG_ID")

  headers = {
      "Authorization": f"Bearer {api_key}",
      "x-organization-id": org_id,
      "x-user-id": user_id,
      "Content-Type": "application/json"
  }

  payload = {
      "requests_file_name": "shipments_2024-11.csv"
  }

  response = requests.post(
      "https://api.dcycle.io/api/v1/logistics/presigned_url",
      headers=headers,
      json=payload
  )

  result = response.json()
  upload_url = result["requests"]["upload_url"]
  file_id = result["requests"]["file_id"]

  print(f"Upload URL obtained: {upload_url[:50]}...")
  print(f"File ID: {file_id}")
  ```

  ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const axios = require('axios');

  const apiKey = process.env.DCYCLE_API_KEY;
  const orgId = process.env.DCYCLE_ORG_ID;

  const headers = {
    'Authorization': `Bearer ${apiKey}`,
    'x-organization-id': orgId,
    'x-user-id': userId,
    'Content-Type': 'application/json'
  };

  const payload = {
    requests_file_name: "shipments_2024-11.csv"
  };

  axios.post(
    'https://api.dcycle.io/api/v1/logistics/presigned_url',
    payload,
    { headers }
  )
  .then(response => {
    const { upload_url, file_id } = response.data.requests;
    console.log('Upload URL:', upload_url.substring(0, 50) + '...');
    console.log('File ID:', file_id);
  })
  .catch(error => console.error(error));
  ```
</CodeGroup>

### Successful Response

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "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:

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  # The presigned URL already includes authentication parameters
  curl -X PUT "${UPLOAD_URL}" \
    -H "Content-Type: text/csv" \
    --data-binary @shipments_2024-11.csv
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import requests

  # Read CSV file
  with open('shipments_2024-11.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}")
  ```

  ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const fs = require('fs');
  const axios = require('axios');

  // Read CSV file
  const csvData = fs.readFileSync('shipments_2024-11.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));
  ```
</CodeGroup>

<Warning>
  The presigned URL expires in **15 minutes**. Make sure to upload the file within that time.
</Warning>

## CSV Format

The CSV must follow this structure:

```csv theme={"theme":{"light":"github-light","dark":"github-dark"}}
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

| Column        | Description                  | Example              |
| ------------- | ---------------------------- | -------------------- |
| `client`      | Client name                  | `"Correos Express"`  |
| `origin`      | Origin city and country      | `"Madrid, Spain"`    |
| `destination` | Destination city and country | `"Barcelona, Spain"` |
| `toc`         | Transport type               | `"van_diesel"`       |
| `load`        | Transported quantity         | `1000`               |
| `load_unit`   | Unit of measurement          | `"kg"`               |
| `date`        | Shipment date                | `"2024-11-20"`       |
| `reference`   | Shipment reference           | `"REF-001"`          |

### Download Template

<Card title="Download CSV Template" icon="download" href="/templates/logistics_shipments_template.csv">
  Template with correct format and sample data
</Card>

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

<Info>
  Processing can take from a few seconds to several minutes depending on file size.
</Info>

## Step 4: Verify Results

After processing, you can query your data:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
# 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

<CodeGroup>
  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  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")
  ```

  ```bash Bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
  #!/bin/bash

  # Variables
  API_KEY="${DCYCLE_API_KEY}"
  ORG_ID="${DCYCLE_ORG_ID}"
  CSV_FILE="shipments_2024-11.csv"
  BASE_URL="https://api.dcycle.io"

  # 1. Get presigned URL
  echo "📤 Requesting presigned URL..."
  RESPONSE=$(curl -s -X POST "${BASE_URL}/api/v1/logistics/presigned_url" \
    -H "Authorization: Bearer ${API_KEY}" \
    -H "x-organization-id: ${ORG_ID}" \
    -H "x-user-id: ${DCYCLE_USER_ID}" \
    -H "Content-Type: application/json" \
    -d "{\"requests_file_name\": \"${CSV_FILE}\"}")

  UPLOAD_URL=$(echo $RESPONSE | jq -r '.requests.upload_url')
  FILE_ID=$(echo $RESPONSE | jq -r '.requests.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..."
  ```
</CodeGroup>

## Common Errors

### 400 Bad Request - Missing file\_name

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "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

| Limit                    | Value                   |
| ------------------------ | ----------------------- |
| **Maximum file size**    | 100 MB                  |
| **Maximum rows per CSV** | 50,000                  |
| **URL expiration time**  | 15 minutes              |
| **Processing time**      | \~1 second per 100 rows |

<Tip>
  For very large files (>50k rows), consider splitting them into multiple smaller CSVs.
</Tip>

## Related Endpoints

<CardGroup cols={2}>
  <Card title="Get Clients" icon="list" href="/api-docs/logistics/get-clients">
    List your logistics clients
  </Card>

  <Card title="Get Report" icon="chart-line" href="/api-docs/logistics/get-report">
    Generate ISO 14083 report
  </Card>

  <Card title="Calculate Shipment" icon="calculator" href="/api-docs/logistics/calculate-shipment">
    Individual calculation without persistence
  </Card>

  <Card title="Get TOCs" icon="truck" href="/api-docs/logistics/get-tocs">
    List transport types
  </Card>
</CardGroup>
