Skip to main content
POST
/
v2
/
logistics
/
requests
/
bulk
Create Logistics Requests (Bulk, v2)
const options = {method: 'POST'};

fetch('https://api.dcycle.io/v2/logistics/requests/bulk', options)
  .then(res => res.json())
  .then(res => console.log(res))
  .catch(err => console.error(err));
import requests

url = "https://api.dcycle.io/v2/logistics/requests/bulk"

response = requests.post(url)

print(response.text)
curl --request POST \
  --url https://api.dcycle.io/v2/logistics/requests/bulk
{
  "success": 123,
  "failed": 123,
  "results": [
    {}
  ],
  "errors": [
    {}
  ]
}

Create Logistics Requests (Bulk, v2)

v2 of Create Logistics Requests (Bulk). It accepts the exact same request body and returns the same response shape, but the distance and emissions calculation for every record is performed asynchronously by a background worker instead of inline. The endpoint returns as soon as the records are persisted, which makes it well suited for large daily batches.
Same contract as v1. Headers, body parameters, validation, deduplication and the 5,000-record limit are identical to POST /v1/logistics/requests/bulk. See the v1 reference for the full request documentation.

v1 vs v2

Behaviourv1 (/v1/logistics/requests/bulk)v2 (/v2/logistics/requests/bulk)
Distance & emissionsCalculated inline for every record before respondingDeferred to a background worker
results[].co2eFinal emissions valuenull while the async calculation is pending
Response latencyScales with batch size (geocoding + distance APIs)Low and roughly constant
Progress trackingA processing job is created and updated as records are calculated
Sync-phase errors (validation, in-batch duplicates)Returned in errors[]Returned in errors[] and recorded on the processing job
In v2, each results[].co2e is null in the immediate response. Distances and emissions are filled in asynchronously. Read the final values back via GET /v1/logistics/requests, and track overall progress through the processing job surfaced in the activity panel.

Request

The request body is identical to v1.
curl -X POST "https://api.dcycle.io/v2/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",
        "origin_country": "ES",
        "load": 1000,
        "load_unit": "kg",
        "toc": "truck_diesel",
        "movement_id": "SHIP-001"
      }
    ],
    "options": { "continue_on_error": true }
  }'
import os
import requests

response = requests.post(
    "https://api.dcycle.io/v2/logistics/requests/bulk",
    headers={
        "x-api-key": os.environ["DCYCLE_API_KEY"],
        "x-organization-id": os.environ["DCYCLE_ORG_ID"],
    },
    json={
        "records": [
            {
                "origin": "Madrid, Spain",
                "destination": "Barcelona, Spain",
                "origin_country": "ES",
                "load": 1000,
                "load_unit": "kg",
                "toc": "truck_diesel",
                "movement_id": "SHIP-001",
            }
        ],
        "options": {"continue_on_error": True},
    },
)
result = response.json()
print(f"Accepted {result['success']}/{result['total_received']} records for async calculation")
const axios = require('axios');

axios.post('https://api.dcycle.io/v2/logistics/requests/bulk', {
  records: [
    {
      origin: 'Madrid, Spain',
      destination: 'Barcelona, Spain',
      origin_country: 'ES',
      load: 1000,
      load_unit: 'kg',
      toc: 'truck_diesel',
      movement_id: 'SHIP-001',
    },
  ],
  options: { continue_on_error: true },
}, {
  headers: {
    'x-api-key': process.env.DCYCLE_API_KEY,
    'x-organization-id': process.env.DCYCLE_ORG_ID,
  },
}).then(response => {
  const { success, total_received } = response.data;
  console.log(`Accepted ${success}/${total_received} records for async calculation`);
});

Successful Response

The response shape matches v1. The key difference is that each results[].co2e is null because emissions are not yet calculated when the response is returned.
{
  "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": null
    },
    {
      "index": 1,
      "id": "683a0184-6dac-44ab-80a2-ce31b14ce927",
      "movement_id": "SHIP-002",
      "package_id": null,
      "co2e": null
    }
  ],
  "errors": []
}
success
integer
Number of records accepted and enqueued for asynchronous calculation (records that passed synchronous validation and deduplication).
failed
integer
Number of records that failed synchronous validation or were rejected as in-batch duplicates. Failures detected during the asynchronous calculation phase (e.g. missing distance or load) are reported on the processing job, not in this response.
results
array
One entry per accepted record. Each co2e is null until the asynchronous calculation completes.
errors
array
Synchronous-phase errors only (validation, in-batch duplicates), each with index, movement_id and error.

Tracking progress

A processing job is created for the batch and updated as records are calculated. Its status moves PENDING → RUNNING → COMPLETED (or FAILED), with per-record success/failure counts and a capped list of error entries (stage: "sync" for validation/dedup, stage: "calculation" for async failures such as NO_TOC, NO_LOAD or NO_DISTANCE). The job is visible in the activity panel. Read the calculated emissions back once the job completes:
curl -X GET "https://api.dcycle.io/v1/logistics/requests?file_id[]=..." \
  -H "x-api-key: ${DCYCLE_API_KEY}" \
  -H "x-organization-id: ${DCYCLE_ORG_ID}"

Common Errors

401 Unauthorized

Cause: Missing or invalid API key
{"detail": "Invalid API key for organization", "code": "INVALID_API_KEY"}

403 Forbidden

Cause: The authenticated user is not a member of the organization
{"detail": "Logged User is not Member of Organization", "code": "LOGGED_USER_NOT_MEMBER"}

422 Unprocessable Entity

Cause: Invalid or missing required fields in the request body
{
  "detail": [
    {
      "loc": ["body", "requests"],
      "msg": "field required",
      "type": "value_error.missing"
    }
  ]
}
Migrating from v1? The request body is unchanged. Update the URL prefix to /v2, treat results[].co2e as pending (null), and read final emissions back from the list endpoint or wait for the processing job to complete.

Create Request (v2)

Create a single request

Get Requests

Retrieve requests with filtering and pagination

Upload CSV

Bulk import via CSV file

Get Packages

View aggregated package emissions