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

# Bulk Upload Vehicles

> Get a presigned URL to upload multiple vehicles via CSV

# Bulk Upload Vehicles

Upload hundreds of vehicles 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. Vehicles are persisted in the database and emissions are calculated automatically based on consumption data.
</Note>

## Upload Flow

<Steps>
  <Step title="Request presigned URL">
    Make a POST request to `/api/v1/vehicles/bulk/csv` 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, creating vehicles and validating data
  </Step>

  <Step title="Verify results">
    Check your fleet with the [List Vehicles](/api-docs/vehicles/list) endpoint
  </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="file_name" type="string" required>
  Name of the CSV file you're going to upload

  **Example:** `"company_fleet_2024.csv"`
</ParamField>

### Response

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

<ResponseField name="file_name" type="string">
  Sanitized file name (alphanumeric only)
</ResponseField>

<ResponseField name="file_id" type="string">
  File UUID for tracking
</ResponseField>

<ResponseField name="destination_file_key" type="string">
  Full S3 key where file will be stored
</ResponseField>

<ResponseField name="message" type="string">
  Confirmation message
</ResponseField>

### Example

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X POST "https://api.dcycle.io/api/v1/vehicles/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": "company_fleet_2024.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")
  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": "company_fleet_2024.csv"
  }

  response = requests.post(
      "https://api.dcycle.io/api/v1/vehicles/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}")
  ```

  ```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 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: 'company_fleet_2024.csv'
  };

  axios.post(
    'https://api.dcycle.io/api/v1/vehicles/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));
  ```
</CodeGroup>

### Successful Response

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "upload_url": "https://dcycle-vehicles.s3.amazonaws.com/files/dcycle/...",
  "file_name": "company_fleet_2024",
  "file_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "destination_file_key": "files/dcycle/.../company_fleet_2024.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:

<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 @company_fleet_2024.csv
  ```

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

  # Read CSV file
  with open('company_fleet_2024.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('company_fleet_2024.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 for creating vehicles:

```csv theme={"theme":{"light":"github-light","dark":"github-dark"}}
name,license_plate,type,ownership,country,is_known,known_vehicle_id,vehicle_fuel_id,registration_year,market_segment,size
Company Van #1,1234-ABC,freight,owned,ES,true,b2c3d4e5-f6g7-8901-bcde-fg2345678901,c3d4e5f6-g7h8-9012-cdef-gh3456789012,2020,lower_medium,medium
CEO Car,9876-XYZ,passenger,owned,ES,true,e5f6g7h8-i9j0-1234-efgh-ij5678901234,f6g7h8i9-j0k1-2345-fghi-jk6789012345,2023,executive,large_car
Delivery Truck,5555-DEF,freight,rented,ES,false,,i9j0k1l2-m3n4-5678-ijkl-mn9012345678,2019,,
```

### Required Columns

| Column      | Description                     | Valid Values           | Example     |
| ----------- | ------------------------------- | ---------------------- | ----------- |
| `type`      | Vehicle usage type              | `passenger`, `freight` | `"freight"` |
| `ownership` | Ownership status                | `owned`, `rented`      | `"owned"`   |
| `country`   | ISO 3166-1 alpha-2 country code | `ES`, `FR`, `DE`, etc. | `"ES"`      |
| `is_known`  | Whether from known database     | `true`, `false`        | `true`      |

### Optional Columns

| Column              | Description                                             | Example                                           |
| ------------------- | ------------------------------------------------------- | ------------------------------------------------- |
| `name`              | Vehicle name or description                             | `"Company Van #1"`                                |
| `license_plate`     | License plate number                                    | `"1234-ABC"`                                      |
| `known_vehicle_id`  | UUID from known vehicles database (if `is_known: true`) | UUID string                                       |
| `vehicle_fuel_id`   | UUID of fuel type                                       | UUID string                                       |
| `registration_year` | Year first registered                                   | `2020`                                            |
| `market_segment`    | Market segment (for passenger vehicles)                 | `mini`, `supermini`, `executive`, etc.            |
| `size`              | Vehicle size classification                             | `small_car`, `medium`, `large_car`, `average_car` |

### Column Details

**is\_known**:

* `true`: Vehicle from Dcycle's known vehicles database. Must provide `known_vehicle_id`.
* `false`: Custom vehicle. System will create an unknown vehicle entry.

**type**:

* `passenger`: Company cars, executive vehicles, personal use
* `freight`: Delivery vans, trucks, commercial vehicles

**ownership**:

* `owned`: Vehicles owned by your organization
* `rented`: Leased or rented vehicles

**market\_segment** (optional, for passenger vehicles):

* `mini` - City cars
* `supermini` - Small cars
* `lower_medium` - Compact cars
* `upper_medium` - Mid-size cars
* `executive` - Large cars
* `luxury` - Premium cars
* `sports` - Sports cars
* `dual_purpose_4x4` - SUVs
* `mpv` - Minivans

### Download Template

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

## Step 3: Asynchronous Processing

Once the CSV is uploaded:

1. **Validation**: The system validates format, vehicle IDs, and data
2. **Processing**: Each row is processed and vehicles are created
3. **Emission Calculation**: CO2e values initialized to 0 (updated when consumption data is added)
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 vehicles:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
# List vehicles
curl "https://api.dcycle.io/api/v1/vehicles" \
  -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 DcycleVehicles:
      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/vehicles/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 vehicles
          vehicles = self.get_vehicles()
          print(f"✅ Total vehicles in fleet: {vehicles['total']}")

          return file_id

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

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

      client.upload_csv("company_fleet_2024.csv")
  ```

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

  # Variables
  API_KEY="${DCYCLE_API_KEY}"
  ORG_ID="${DCYCLE_ORG_ID}"
  USER_ID="${DCYCLE_USER_ID}"
  CSV_FILE="company_fleet_2024.csv"
  BASE_URL="https://api.dcycle.io"

  # 1. Get presigned URL
  echo "📤 Requesting presigned URL..."
  RESPONSE=$(curl -s -X POST "${BASE_URL}/api/v1/vehicles/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..."
  ```
</CodeGroup>

## Common Errors

### 400 Bad Request - Missing file\_name

**Cause:** Field required

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "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 (`type`, `ownership`, `country`, `is_known`) and correct format.

### 422 Validation Error - Invalid Vehicle ID

**Cause:** `known_vehicle_id` doesn't exist in the known vehicles database

**Solution:** Use the Known Vehicles endpoint to get valid vehicle IDs, or set `is_known: false` to create a custom vehicle.

## Limits and Recommendations

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

<Tip>
  For very large fleets (>10k vehicles), consider splitting them into multiple smaller CSVs.
</Tip>

## Best Practices

### Data Preparation

1. **Clean license plates**: Remove special characters, use consistent format
2. **Validate vehicle IDs**: Check that `known_vehicle_id` exists before upload
3. **Consistent naming**: Use standardized vehicle names (e.g., "Van #1", "Van #2")
4. **Complete data**: Include as many optional fields as possible for better reporting

### Error Handling

After upload, check for vehicles with `status: "error"`:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
def check_for_errors():
    """Check if any vehicles failed to process"""
    response = requests.get(
        "https://api.dcycle.io/api/v1/vehicles",
        headers=headers,
        params={"filter_by": "status:error"}
    )

    vehicles = response.json()

    if vehicles['total'] > 0:
        print(f"⚠️ {vehicles['total']} vehicles have errors:")
        for vehicle in vehicles['items']:
            print(f"  - {vehicle.get('name', 'Unnamed')}: {vehicle.get('error_messages')}")

    return vehicles['total']

# Check after processing
errors_count = check_for_errors()
if errors_count == 0:
    print("✅ All vehicles processed successfully")
```

### Incremental Updates

To update existing vehicles or add new ones:

1. Export current fleet to CSV
2. Modify or add rows
3. Re-upload (existing vehicles will be updated by license plate)

## Special Notes

### Known vs Unknown Vehicles

**Known Vehicles** (is\_known: true):

* Must provide valid `known_vehicle_id`
* Emission factors are based on manufacturer data
* Includes detailed specifications (engine size, power, etc.)

**Unknown Vehicles** (is\_known: false):

* System creates generic vehicle entry
* Emission factors based on vehicle type and fuel
* Less accurate but flexible for custom vehicles

### Vehicle Status After Upload

Vehicles created via CSV will have:

* **status**: `"active"` if successful, `"error"` if validation failed
* **co2e**: `0.0` initially (updated when consumption data is added)
* **created\_at**: Upload timestamp

### Adding Consumption Data

After creating vehicles, add fuel consumption data using the [Create Invoice](/api-docs/invoices/create) endpoint with `type: "recharge"` for electric vehicles or facility fuels for combustion vehicles.

### Country Codes

Use ISO 3166-1 alpha-2 country codes:

* Spain: `ES`
* France: `FR`
* Germany: `DE`
* United Kingdom: `GB`
* United States: `US`

## Related Endpoints

<CardGroup cols={2}>
  <Card title="List Vehicles" icon="car" href="/api-docs/vehicles/list">
    View uploaded vehicles
  </Card>

  <Card title="Create Invoice" icon="file-invoice" href="/api-docs/invoices/create">
    Add fuel consumption data
  </Card>

  <Card title="List Facilities" icon="building" href="/api-docs/facilities/list">
    View your facilities
  </Card>

  <Card title="Authentication" icon="key" href="/api-docs/authentication">
    Learn about API authentication
  </Card>
</CardGroup>
