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

# Create Logistics Requests (Bulk, v2)

> Bulk-create logistics shipments with asynchronous emissions calculation and progress tracking

# Create Logistics Requests (Bulk, v2)

`v2` of [Create Logistics Requests (Bulk)](/api-reference/logistics/create-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.

<Note>
  **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](/api-reference/logistics/create-requests-bulk) for the full request
  documentation.
</Note>

## v1 vs v2

| Behaviour                                           | v1 (`/v1/logistics/requests/bulk`)                       | v2 (`/v2/logistics/requests/bulk`)                                    |
| --------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------- |
| Distance & emissions                                | Calculated **inline** for every record before responding | **Deferred** to a background worker                                   |
| `results[].co2e`                                    | Final emissions value                                    | **`null`** while the async calculation is pending                     |
| Response latency                                    | Scales with batch size (geocoding + distance APIs)       | Low and roughly constant                                              |
| Progress tracking                                   | —                                                        | A **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         |

<Warning>
  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`](/api-reference/logistics/get-requests), and track overall
  progress through the processing job surfaced in the activity panel.
</Warning>

## Request

The request body is identical to v1.

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

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

  response = requests.post(
      "https://api.dcycle.io/v2/logistics/requests/bulk",
      headers={
          "x-api-key": DCYCLE_API_KEY,
          "x-organization-id": 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")
  ```
</CodeGroup>

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

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

<ResponseField name="success" type="integer">
  Number of records accepted and enqueued for asynchronous calculation (records that passed
  synchronous validation and deduplication).
</ResponseField>

<ResponseField name="failed" type="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.
</ResponseField>

<ResponseField name="results" type="array">
  One entry per accepted record. Each `co2e` is **`null`** until the asynchronous calculation
  completes.
</ResponseField>

<ResponseField name="errors" type="array">
  Synchronous-phase errors only (validation, in-batch duplicates), each with `index`,
  `movement_id` and `error`.
</ResponseField>

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

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

<Tip>
  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.
</Tip>
