> ## 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 Business Travels (v2)

> Create up to 5,000 business travels in one request; distances and emissions are calculated asynchronously

[← Business Travels API](/api-reference/business-travels/overview)

Create one or many business travels (Scope 3 category 6). Say how each trip was made, when, how often and how far, or
between which places; Dcycle works out the distance when you leave it out, picks the emission factor and calculates
the emissions in the background. The travels are the ones [`/v1/business-travels`](/api-reference/business-travels/list)
reads and edits.

The request checks every record and writes them all at once, then answers `202 Accepted` with an
[ingest job](/api-reference/ingest-jobs/get) that calculates them. Follow it with
[`GET /v2/ingest-jobs/{id}`](/api-reference/ingest-jobs/get) or the
[`ingest_job.finished`](/api-reference/webhooks/overview) webhook.

<Warning>
  **Beta.** Part of the bulk ingest API, currently in beta. The contract may still change before general availability.
</Warning>

<RequestExample>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X POST "https://api.dcycle.io/v2/business-travels" \
    -H "x-api-key: ${DCYCLE_API_KEY}" \
    -H "x-organization-id: ${DCYCLE_ORG_ID}" \
    -H "Idempotency-Key: 3e2d1c0b-9a8f-4e7d-a6c5-b4a392817161" \
    -H "Content-Type: application/json" \
    -d '{
      "records": [
        {
          "transport_type": "train",
          "fuel_type": "electric",
          "renewable_energy": "no",
          "origin": "Madrid",
          "destination": "Barcelona",
          "travel_type": "round",
          "start_date": "2026-03-04",
          "email": "ana@example.com",
          "client_row_id": "TRIP-2026-0311"
        },
        {
          "transport_type": "car",
          "vehicle_size": "medium",
          "fuel_type": "diesel",
          "distance_km": 42,
          "travel_number": 20,
          "start_date": "2026-03-01",
          "end_date": "2026-03-31"
        }
      ]
    }'
  ```

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

  import requests

  response = requests.post(
      "https://api.dcycle.io/v2/business-travels",
      headers={
          "x-api-key": os.environ["DCYCLE_API_KEY"],
          "x-organization-id": os.environ["DCYCLE_ORG_ID"],
          "Idempotency-Key": str(uuid.uuid4()),  # keep it to retry this same batch
      },
      json={
          "records": [
              {
                  "transport_type": "aircraft",
                  "origin": "Madrid",
                  "destination": "London",
                  "travel_type": "round",
                  "start_date": "2026-03-04",
                  "client_row_id": "TRIP-2026-0312",
              }
          ]
      },
      timeout=60,
  )
  response.raise_for_status()
  job = response.json()
  print(job["id"], job["status"])
  ```
</RequestExample>

<ResponseExample>
  ```json 202 theme={"theme":{"light":"github-light","dark":"github-dark"}}
  {
    "id": "4c3b2a19-0817-4f6e-9d5c-4b3a29181706",
    "status": "processing",
    "entity_type": "business_travels",
    "operation": "create",
    "source": "api_bulk",
    "counts": { "submitted": 2, "succeeded": 0, "failed": 0 },
    "chunks_total": 1,
    "chunks_done": 0,
    "created_at": "2026-10-20T09:12:44.031Z",
    "finished_at": null,
    "links": {
      "self": "/v2/ingest-jobs/4c3b2a19-0817-4f6e-9d5c-4b3a29181706",
      "items": "/v2/ingest-jobs/4c3b2a19-0817-4f6e-9d5c-4b3a29181706/items"
    }
  }
  ```

  ```json 422 theme={"theme":{"light":"github-light","dark":"github-dark"}}
  {
    "type": "https://code.dcycle.io/api-reference/errors#BULK_RECORDS_REJECTED",
    "title": "Unprocessable Entity",
    "status": 422,
    "code": "BULK_RECORDS_REJECTED",
    "detail": "1 of the submitted records are invalid; nothing was written.",
    "errors": [
      {
        "source_row_index": 0,
        "client_row_id": "TRIP-2026-0311",
        "error_code": "FUEL_NOT_ALLOWED_FOR_TRANSPORT",
        "error_params": { "field": "fuel_type", "value": "petrol" }
      }
    ],
    "rejected": 1,
    "request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
  }
  ```

  ```json 200 (dry_run=true) theme={"theme":{"light":"github-light","dark":"github-dark"}}
  { "dry_run": true, "submitted": 2, "valid": 2 }
  ```
</ResponseExample>

## How it works

1. **Every record is checked.** If any is invalid the **whole** request is rejected with `422` and nothing is
   written; the answer lists every invalid record, so you can fix them all at once.
2. **The travels are written at once**, with status `pending`, in the `x-organization-id` organization.
3. **The job calculates them** in the background, in chunks. A travel sent without `distance_km` gets the distance
   between its origin and destination for its transport (road or rail route, great-circle for a flight, sea route for a
   ferry), and its places are located. Then the emission factor of its transport, fuel and country is applied.
   Each travel ends `active` or `error`. Its job item is then `failed` with the reason when there is one:
   * `TRANSPORT_MODE_INVALID_FOR_ROUTE`: there is no route for that transport between the two places (e.g. a car
     between continents).
   * `DISTANCE_CALCULATION_ERROR` or `GEOCODE_LOCATION_ERROR`: a place could not be found.
   * `CALCULATION_FAILED`: no emission factor for the trip, or the distance provider was unavailable (send the
     travel again later, or with `distance_km`).
4. **When every chunk is done** the job is `completed` (or `completed_with_errors`) and `ingest_job.finished` is sent
   to your webhook endpoints.

### How a trip is counted

A record is a trip made `travel_number` times in its period: its emissions are those of `distance_km` (twice for
`travel_type: round`) for each trip, times `passenger_number`. A trip over several days is one record with its
`start_date` and `end_date`; recurring trips (a commute to a client) are one record with their count.

## Request

### Headers

<ParamField header="x-api-key" type="string" required>
  Your API key (see [Authentication](/api-reference/introduction#authentication)).
</ParamField>

<ParamField header="x-organization-id" type="string" required>
  The organization the travels belong to. A record cannot name another organization: send each organization's
  travels with its own `x-organization-id`.
</ParamField>

<ParamField header="Idempotency-Key" type="string" required>
  1 to 255 characters, unique per batch. A retry with the same key and body returns the original job
  (with `Idempotent-Replayed: true`) instead of creating the travels again; the same key with another body is `422 IDEMPOTENCY_KEY_REUSED`, and while the first request is still being processed
  `409 IDEMPOTENCY_KEY_IN_PROGRESS`.
</ParamField>

### Query Parameters

<ParamField query="dry_run" type="boolean" default="false">
  Check only: the same `422` for invalid records, nothing written, no job; `200` with
  `{"dry_run": true, "submitted": n, "valid": n}` when every record is valid.
</ParamField>

### Body Parameters

<ParamField body="records" type="array[object]" required>
  The business travels to create, 1 to 5,000. A field not listed here is rejected (`SCHEMA_INVALID`).

  <Expandable title="record">
    <ParamField body="transport_type" type="string" required>
      `car`, `motorbike`, `bus`, `trolleybus`, `metro`, `train`, `aircraft` or `ferry`.
    </ParamField>

    <ParamField body="start_date" type="date" required>
      Day of the trip, or first day of the period it repeats in, `YYYY-MM-DD`, from 1970, not in the future.
    </ParamField>

    <ParamField body="end_date" type="date">
      Last day of the period, on or after `start_date` and at most 366 days in, not in the future. Omitted: the trip
      is a one-day record.
    </ParamField>

    <ParamField body="distance_km" type="number">
      One-way distance of one trip, in km, over 0. Omitted: calculated by the job from `origin` and `destination`.
    </ParamField>

    <ParamField body="origin" type="string">
      Where the trip starts: an address, a city, an airport, or `lat,lng`, up to 500 characters. Send it with
      `destination` when there is no `distance_km`. When it is a place, its country picks the emission factor's
      country.
    </ParamField>

    <ParamField body="destination" type="string">
      Where it ends, as `origin`. With no `distance_km` it must be another place than `origin`.
    </ParamField>

    <ParamField body="travel_type" type="string" default="one_way">
      `one_way`, or `round` for there and back (twice the distance).
    </ParamField>

    <ParamField body="travel_number" type="integer" default="1">
      How many times the trip was made in the period: 1 to 100 a day of the period, and 10,000 at most.
    </ParamField>

    <ParamField body="passenger_number" type="integer" default="1">
      People travelling together, 1 to 100.
    </ParamField>

    <ParamField body="vehicle_size" type="string">
      For a `car`, and only for a car (required): `small`, `medium` or `large`.
    </ParamField>

    <ParamField body="fuel_type" type="string">
      What the vehicle runs on: `diesel`, `natural_gas`, `petrol`, `lpg`, `electric`, `hybrid` or `do_not_know`.
      Required except for `aircraft` and `ferry`, which take none. Only the fuels the transport is calculated with are
      accepted (`FUEL_NOT_ALLOWED_FOR_TRANSPORT` otherwise):

      | `transport_type` | `fuel_type` |
      | - | - |
      | `car` | `diesel`, `natural_gas`, `petrol`, `lpg`, `electric`, `hybrid`, `do_not_know` |
      | `motorbike` | `diesel`, `petrol`, `electric`, `do_not_know` |
      | `bus` | `diesel`, `natural_gas`, `petrol`, `lpg`, `do_not_know` |
      | `train` | `diesel`, `electric`, `do_not_know` |
      | `metro`, `trolleybus` | `electric`, `do_not_know` |
    </ParamField>

    <ParamField body="renewable_energy" type="string">
      With `fuel_type: electric`, and only with it (required): `yes` when the electricity is renewable, else `no`.
    </ParamField>

    <ParamField body="name" type="string">
      A label for the trip, up to 255 characters.
    </ParamField>

    <ParamField body="email" type="string">
      Who travelled, e.g. their work email, up to 255 characters.
    </ParamField>

    <ParamField body="custom_ef_record_id" type="uuid">
      A record of your [custom emission factor databases](/api-reference/custom-emission-factors/overview) to
      calculate with instead of the default: find it with [List Records](/api-reference/custom-emission-factors/list-records).
      Its `activity_categories` must include `travels`.
    </ParamField>

    <ParamField body="client_row_id" type="string">
      Your own reference for the record, up to 255 characters, echoed in errors and in the job's items.
    </ParamField>
  </Expandable>
</ParamField>

<Note>
  The [file import template](/api-reference/imports/list) writes some of these values differently: `car_small` is
  `transport_type: car` + `vehicle_size: small` here, `return_trip` is `travel_type: round`, and
  `electric_renewable` / `electric_not_renewable` is `fuel_type: electric` + `renewable_energy: yes` / `no`.
</Note>

## Response

`202 Accepted` with the job (`entity_type: business_travels`, `operation: create`) and a `Location` header to it. To
read the result:

* [`GET /v2/ingest-jobs/{id}/items`](/api-reference/ingest-jobs/list-items) has one item per record, in the order
  sent. Its `entity_id` is the travel's id. A `failed` item carries its `error_code`.
* [List Business Travels](/api-reference/business-travels/list) returns the travels with their status, distance and
  emissions (`co2e`).

## Errors

[Problem details](/api-reference/errors). Per-record codes in `errors[].error_code` of a `422 BULK_RECORDS_REJECTED`:

| `error_code` | Cause |
| - | - |
| `SCHEMA_INVALID` | The record does not match the schema: a missing or unknown field (e.g. `organization_id`), no `distance_km` and no `origin` + `destination`, the same place twice without a distance, a date in the future or out of range, a car without `vehicle_size`, a fuel on an aircraft or ferry, no fuel on another transport, `renewable_energy` without `electric`, more than 100 passengers... `error_params.errors` says where. |
| `FUEL_NOT_ALLOWED_FOR_TRANSPORT` | `fuel_type` is not one the transport is calculated with (see the table above). |
| `MULTIDB_CUSTOM_RECORD_NOT_FOUND_OR_DISABLED` | `custom_ef_record_id` does not exist, is disabled or is not visible from your organization. |
| `MULTIDB_CUSTOM_RECORD_INCOMPATIBLE_CATEGORY` | `custom_ef_record_id` is not configured for the `travels` activity category. |

Request-level errors: `401` for a missing or invalid API key, `403 LOGGED_USER_NOT_MEMBER` when the key's user is not a
member of the organization, `422 REQUEST_VALIDATION_FAILED` for a malformed body (no `records`, more than 5,000, no
`Idempotency-Key`), `422 IDEMPOTENCY_KEY_REUSED`, `409 IDEMPOTENCY_KEY_IN_PROGRESS`, and `429` beyond the
[rate limits](/api-reference/rate-limits).

## Limits

| Limit | Value |
| - | - |
| Records per request | 1 to 5,000 |
| Travels per calculation chunk | 100 |

## Related Endpoints

<CardGroup cols={2}>
  <Card title="Delete Business Travels (v2)" icon="trash" href="/api-reference/business-travels/delete-v2">
    Delete travels by id
  </Card>

  <Card title="Get Ingest Job" icon="magnifying-glass" href="/api-reference/ingest-jobs/get">
    Follow the calculation
  </Card>

  <Card title="List Job Items" icon="list" href="/api-reference/ingest-jobs/list-items">
    The travel id and outcome of each record
  </Card>

  <Card title="List Business Travels" icon="table" href="/api-reference/business-travels/list">
    The travels and their emissions
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.