Skip to main content
POST
https://api.dcycle.io
/
v1
/
logistics
/
requests
/
bulk
Create Logistics Requests (Bulk)
const options = {
  method: 'POST',
  headers: {
    'x-api-key': '<x-api-key>',
    'x-organization-id': '<x-organization-id>',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({records: [{}], options: {}})
};

fetch('https://api.dcycle.io/v1/logistics/requests/bulk', options)
  .then(res => res.json())
  .then(res => console.log(res))
  .catch(err => console.error(err));
{
  "total_received": 123,
  "total_processed": 123,
  "success": 123,
  "failed": 123,
  "results": [
    {}
  ],
  "errors": [
    {}
  ]
}

Create Logistics Requests (Bulk)

Process multiple logistics requests in a single API call. Optimized for high-volume scenarios like daily batch uploads from logistics providers.
New API: This endpoint is part of the new API architecture with improved design and maintainability.

Key Features

High Volume

Process up to 5,000 records per request

Optimized Performance

Internal caching of TOCs, hubs, and distances for faster processing

Error Handling

Continue processing even if some records fail with continue_on_error

Same Schema

Uses the same record format as the single request endpoint

Request

Headers

x-api-key
string
required
Your API key for authenticationExample: sk_live_1234567890abcdef
x-organization-id
string
required
Your organization UUIDExample: a8315ef3-dd50-43f8-b7ce-d839e68d51fa

Body Parameters

records
array
required
Array of logistics request objects. Each record uses the same schema as the single Create Logistics Request endpoint.Maximum items: 5,000
options
object
Processing options for the bulk operation.

Response

total_received
integer
Total number of records received in the request
total_processed
integer
Total records processed (success + failed)
success
integer
Number of records processed successfully
failed
integer
Number of records that failed
results
array
Array of successfully processed records with essential fields
errors
array
Array of errors for failed records

Example

curl -X POST "https://api.dcycle.io/v1/logistics/requests/bulk" \
  -H "x-api-key: ${DCYCLE_API_KEY}" \
  -H "x-organization-id: ${DCYCLE_ORG_ID}" \
  -H "Content-Type: application/json" \
  -d '{
    "records": [
      {
        "origin": "Madrid, Spain",
        "destination": "Barcelona, Spain",
        "load": 500,
        "load_unit": "kg",
        "category": "road",
        "movement_id": "SHIP-001",
        "client": "AMAZON"
      },
      {
        "origin": "Madrid, Spain",
        "destination": "Barcelona, Spain",
        "load": 300,
        "load_unit": "kg",
        "category": "road",
        "movement_id": "SHIP-002",
        "client": "AMAZON"
      },
      {
        "origin": "Valencia, Spain",
        "destination": "Sevilla, Spain",
        "load": 1000,
        "load_unit": "kg",
        "category": "road",
        "movement_id": "SHIP-003",
        "client": "ZARA"
      }
    ],
    "options": {
      "continue_on_error": true
    }
  }'

Successful Response

{
  "total_received": 3,
  "total_processed": 3,
  "success": 3,
  "failed": 0,
  "results": [
    {
      "index": 0,
      "id": "0d9b8817-fc47-47c3-b134-b3565ae1a57f",
      "movement_id": "SHIP-001",
      "package_id": null,
      "co2e": 20.99
    },
    {
      "index": 1,
      "id": "683a0184-6dac-44ab-80a2-ce31b14ce927",
      "movement_id": "SHIP-002",
      "package_id": null,
      "co2e": 12.59
    },
    {
      "index": 2,
      "id": "1048a12d-46e6-4e0d-955e-833d4693bcab",
      "movement_id": "SHIP-003",
      "package_id": null,
      "co2e": 43.99
    }
  ],
  "errors": []
}

Response with Partial Errors

{
  "total_received": 3,
  "total_processed": 3,
  "success": 2,
  "failed": 1,
  "results": [
    {
      "index": 0,
      "id": "0d9b8817-fc47-47c3-b134-b3565ae1a57f",
      "movement_id": "SHIP-001",
      "package_id": null,
      "co2e": 20.99
    },
    {
      "index": 2,
      "id": "1048a12d-46e6-4e0d-955e-833d4693bcab",
      "movement_id": "SHIP-003",
      "package_id": null,
      "co2e": 43.99
    }
  ],
  "errors": [
    {
      "index": 1,
      "movement_id": "SHIP-002",
      "error": "TOC not found for toc=invalid_toc, category=None, region=EU"
    }
  ]
}

Performance Optimizations

The bulk endpoint includes several optimizations for high-volume processing:
All active vehicle types (TOCs) are pre-loaded at the start of the request, avoiding repeated database queries for each record.
Organization logistics hubs are pre-loaded, allowing instant name-to-address resolution without individual lookups.
Calculated distances are cached by route (origin, destination, category). Repeated routes reuse cached distances, reducing Google Maps API calls.Example: If 100 records share the same Madrid → Barcelona route, the distance is calculated only once.
Records are inserted in batches of 500 for efficient database writes.

Use Cases

Daily Batch Upload (Logistics Provider)

Process daily shipment data from a logistics provider:
import pandas as pd

def upload_daily_shipments(csv_file: str):
    """Upload daily shipments from CSV file"""
    df = pd.read_csv(csv_file)

    records = []
    for _, row in df.iterrows():
        records.append({
            "origin": row["origin"],
            "destination": row["destination"],
            "load": row["weight_kg"],
            "load_unit": "kg",
            "category": "road",
            "movement_id": row["tracking_number"],
            "client": row["client_code"],
            "shipment_date": row["date"],
            "distance_km": row.get("distance_km"),  # Use if available
        })

    # Process in chunks of 5000
    chunk_size = 5000
    total_success = 0
    total_failed = 0

    for i in range(0, len(records), chunk_size):
        chunk = records[i:i + chunk_size]

        response = requests.post(
            "https://api.dcycle.io/v1/logistics/requests/bulk",
            headers=headers,
            json={"records": chunk, "options": {"continue_on_error": True}}
        )

        result = response.json()
        total_success += result["success"]
        total_failed += result["failed"]

        if result["errors"]:
            print(f"Chunk {i//chunk_size + 1} errors: {result['errors']}")

    print(f"Total: {total_success} success, {total_failed} failed")
    return total_success, total_failed

# Example: Upload 400k daily records
upload_daily_shipments("daily_shipments_2024-01-15.csv")

Multi-leg Package Tracking (Bulk)

Track packages through multiple legs in a single request:
def track_packages_bulk(packages_data: list):
    """
    Track multiple packages with their legs in bulk.

    packages_data format:
    [
        {
            "package_key": "PKG-001",
            "movement_id": "SHIP-001",
            "legs": [
                {"origin": "Madrid Hub", "destination": "Valencia Hub", "load_kg": 15},
                {"origin": "Valencia Hub", "destination": "Barcelona Hub", "load_kg": 15},
            ]
        },
        ...
    ]
    """
    records = []

    for pkg in packages_data:
        for leg in pkg["legs"]:
            records.append({
                "origin": leg["origin"],
                "destination": leg["destination"],
                "load": leg["load_kg"],
                "load_unit": "kg",
                "category": "road",
                "package_key": pkg["package_key"],
                "movement_id": pkg["movement_id"],
            })

    response = requests.post(
        "https://api.dcycle.io/v1/logistics/requests/bulk",
        headers=headers,
        json={"records": records, "options": {"continue_on_error": True}}
    )

    return response.json()

With Pre-calculated Distances

When you have pre-calculated distances (e.g., from your routing system), skip geocoding:
records = [
    {
        "distance_km": 620,  # Pre-calculated
        "load": 500,
        "load_unit": "kg",
        "category": "road",
        "origin_country": "ES",
        "movement_id": "SHIP-001",
    },
    {
        "distance_km": 450,
        "load": 300,
        "load_unit": "kg",
        "category": "road",
        "origin_country": "ES",
        "movement_id": "SHIP-002",
    },
]

response = requests.post(
    "https://api.dcycle.io/v1/logistics/requests/bulk",
    headers=headers,
    json={"records": records}
)

Common Errors

422 Validation Error

Cause: Invalid record format or missing required fields
{
  "detail": [
    {
      "loc": ["body", "records", 0, "category"],
      "msg": "Please provide a category if toc is not present",
      "type": "value_error"
    }
  ]
}
Solution: Ensure each record has either toc or category, and either distance_km or both origin and destination.

Partial Failures

When some records fail but continue_on_error is true, the response includes both successful results and errors:
{
  "success": 98,
  "failed": 2,
  "results": [...],
  "errors": [
    {"index": 5, "movement_id": "SHIP-006", "error": "TOC not found"},
    {"index": 42, "movement_id": "SHIP-043", "error": "Load not found and no default available"}
  ]
}
Solution: Review the errors array to identify and fix problematic records, then resubmit only the failed ones.

Limits

LimitValue
Maximum records per request5,000
Request timeout5 minutes
Recommended batch size1,000 - 2,000
For very large uploads (100k+ records), split into multiple requests of 2,000-5,000 records each and process sequentially or in parallel.