curl -X POST "https://api.dcycle.io/v2/transports" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Idempotency-Key: 9c8b7a69-5847-4e36-b251-a4f3e2d1c0b9" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"transport_date": "2026-03-04",
"transport_direction": "downstream",
"quantity_transported": 12.5,
"unit_id": "cab66828-c2b1-431b-92af-f9ab37149d3c",
"supplier": "LogiTrans S.A.",
"sections": [
{
"transport_type": "road",
"travel_method": "truck",
"origin": "Madrid, Spain",
"destination": "Valencia Port, Spain"
},
{
"transport_type": "maritime",
"distance_km": 2950
}
],
"client_row_id": "SHIP-2026-0311"
},
{
"transport_date": "2026-03-05",
"transport_direction": "upstream",
"distance_only": true,
"sections": [
{ "transport_type": "road", "travel_method": "car", "distance_km": 42 }
]
}
]
}'
import os
import uuid
import requests
response = requests.post(
"https://api.dcycle.io/v2/transports",
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_date": "2026-03-04",
"transport_direction": "upstream",
"quantity_transported": 1500,
"unit_id": "61743a63-ff70-459c-9567-5eee8f7dfd5c",
"sections": [
{
"transport_type": "do_not_know",
"origin": "Lyon, France",
"destination": "Zaragoza, Spain",
}
],
"client_row_id": "SHIP-2026-0312",
}
]
},
timeout=60,
)
response.raise_for_status()
job = response.json()
print(job["id"], job["status"])
{
"id": "2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d",
"status": "processing",
"entity_type": "transport_routes",
"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/2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d",
"items": "/v2/ingest-jobs/2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d/items"
}
}
{
"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": "SHIP-2026-0311",
"error_code": "TRANSPORT_COMBINATION_INVALID",
"error_params": { "field": "sections[1]" }
}
],
"rejected": 1,
"request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
}
{ "dry_run": true, "submitted": 2, "valid": 2 }
Create Freight Transports (v2)
Create up to 5,000 freight shipments in one request; distances and emissions are calculated asynchronously
POST
/
v2
/
transports
curl -X POST "https://api.dcycle.io/v2/transports" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Idempotency-Key: 9c8b7a69-5847-4e36-b251-a4f3e2d1c0b9" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"transport_date": "2026-03-04",
"transport_direction": "downstream",
"quantity_transported": 12.5,
"unit_id": "cab66828-c2b1-431b-92af-f9ab37149d3c",
"supplier": "LogiTrans S.A.",
"sections": [
{
"transport_type": "road",
"travel_method": "truck",
"origin": "Madrid, Spain",
"destination": "Valencia Port, Spain"
},
{
"transport_type": "maritime",
"distance_km": 2950
}
],
"client_row_id": "SHIP-2026-0311"
},
{
"transport_date": "2026-03-05",
"transport_direction": "upstream",
"distance_only": true,
"sections": [
{ "transport_type": "road", "travel_method": "car", "distance_km": 42 }
]
}
]
}'
import os
import uuid
import requests
response = requests.post(
"https://api.dcycle.io/v2/transports",
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_date": "2026-03-04",
"transport_direction": "upstream",
"quantity_transported": 1500,
"unit_id": "61743a63-ff70-459c-9567-5eee8f7dfd5c",
"sections": [
{
"transport_type": "do_not_know",
"origin": "Lyon, France",
"destination": "Zaragoza, Spain",
}
],
"client_row_id": "SHIP-2026-0312",
}
]
},
timeout=60,
)
response.raise_for_status()
job = response.json()
print(job["id"], job["status"])
{
"id": "2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d",
"status": "processing",
"entity_type": "transport_routes",
"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/2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d",
"items": "/v2/ingest-jobs/2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d/items"
}
}
{
"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": "SHIP-2026-0311",
"error_code": "TRANSPORT_COMBINATION_INVALID",
"error_params": { "field": "sections[1]" }
}
],
"rejected": 1,
"request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
}
{ "dry_run": true, "submitted": 2, "valid": 2 }
← Transport API
Create one or many freight shipments (Scope 3 categories 4 and 9, upstream and downstream transportation). Say when
each shipment was made, in which direction, how much it weighed and its legs (sections), each with its transport and
either its distance or the places it joins; Dcycle works out the distance when you leave it out, picks the emission
factor and calculates the emissions in the background. The shipments are the ones
Coming from
The record is the shipment v1 takes, with these differences:
Request-level errors:
/v1/transports reads and edits.
The request checks every record and writes them all at once, then answers 202 Accepted with an
ingest job that calculates them (see
how bulk ingestion works). Follow it with
GET /v2/ingest-jobs/{id} or, for a job submitted with an API key, the
ingest_job.finished webhook.
Beta. Part of the bulk ingest API, in beta. The contract may still change before general availability.
curl -X POST "https://api.dcycle.io/v2/transports" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Idempotency-Key: 9c8b7a69-5847-4e36-b251-a4f3e2d1c0b9" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"transport_date": "2026-03-04",
"transport_direction": "downstream",
"quantity_transported": 12.5,
"unit_id": "cab66828-c2b1-431b-92af-f9ab37149d3c",
"supplier": "LogiTrans S.A.",
"sections": [
{
"transport_type": "road",
"travel_method": "truck",
"origin": "Madrid, Spain",
"destination": "Valencia Port, Spain"
},
{
"transport_type": "maritime",
"distance_km": 2950
}
],
"client_row_id": "SHIP-2026-0311"
},
{
"transport_date": "2026-03-05",
"transport_direction": "upstream",
"distance_only": true,
"sections": [
{ "transport_type": "road", "travel_method": "car", "distance_km": 42 }
]
}
]
}'
import os
import uuid
import requests
response = requests.post(
"https://api.dcycle.io/v2/transports",
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_date": "2026-03-04",
"transport_direction": "upstream",
"quantity_transported": 1500,
"unit_id": "61743a63-ff70-459c-9567-5eee8f7dfd5c",
"sections": [
{
"transport_type": "do_not_know",
"origin": "Lyon, France",
"destination": "Zaragoza, Spain",
}
],
"client_row_id": "SHIP-2026-0312",
}
]
},
timeout=60,
)
response.raise_for_status()
job = response.json()
print(job["id"], job["status"])
{
"id": "2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d",
"status": "processing",
"entity_type": "transport_routes",
"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/2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d",
"items": "/v2/ingest-jobs/2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d/items"
}
}
{
"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": "SHIP-2026-0311",
"error_code": "TRANSPORT_COMBINATION_INVALID",
"error_params": { "field": "sections[1]" }
}
],
"rejected": 1,
"request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
}
{ "dry_run": true, "submitted": 2, "valid": 2 }
How it works
- Every record is checked: its fields, its weight unit, each section’s transport against the
combinations there are emission factors for, and its custom factors. If
any is invalid the whole request is rejected with
422and nothing is written;rejectedis the number of invalid records anderrorslists them (up to 100). The request looks up no place: an address that cannot be found only shows in the job. - The shipments are written at once, with their sections and status
pending, in thex-organization-idorganization. - The job calculates them in the background, in chunks. A section sent without
distance_kmgets the distance between its origin and destination for its transport (road or rail route, sea route formaritime, great-circle forair), and its places are located. Ado_not_knowsection is inferred as forPOST /v1/transports: road by truck within a country when there is a road route, air otherwise. Then each section gets the emission factor of its transport, its distance and the shipment’s weight. Each shipment endsactiveorerror. Its job item is thenfailedwith the reason when there is one:DISTANCE_CALCULATION_ERROR: the distance of a section could not be had (an address that cannot be found, no route for that transport between the two places, or the distance provider unavailable). The shipment is not calculated: send it again with the places fixed, or withdistance_km.CALCULATION_FAILED: no emission factor for a section, or another calculation error.
- When every chunk is done the job is
completed(orcompleted_with_errors). For a job submitted with an API key,ingest_job.finishedis sent to the webhook endpoints of thex-organization-idorganization.
Coming from POST /v1/transports
The record is the shipment v1 takes, with these differences:
transport_directionis required (v1 assumesdownstreamwhen it is missing).- A section gives its distance as
distance_km, instead ofkms_manualwithdistance_manual: true. A section without it givesoriginanddestination. - There is no
detail: the distance or weight band of the factor is picked from the section’s distance and the shipment’s weight, as v1 does whendetailis left out. - There is no
organization_id, nor any other field not listed below: the shipments belong to thex-organization-idorganization.
Request
Headers
string
required
Your API key (see Authentication).
string
required
The organization the shipments belong to. A record cannot name another organization: send each organization’s
shipments with its own
x-organization-id. The job and its ingest_job.finished webhook belong to that
organization too, so register a webhook endpoint in each
organization you send as.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 shipments 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.Query Parameters
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.Body Parameters
array[object]
required
The shipments to create, 1 to 5,000. A field not listed here is rejected (
SCHEMA_INVALID).Show record
Show record
date
required
Day of the shipment,
YYYY-MM-DD, from 1970 to next year.string
required
upstream for goods you buy (category 4) or downstream for goods you sell (category 9).array[object]
required
The legs of the shipment, in order, 1 to 20.
The
Show section
Show section
string
required
road, rail, maritime, air, or do_not_know to let the job infer road or air from the places.string
The road vehicle, only for
road: truck, car, motorbike, bicycle or electric_kick_scooter.boolean
default:"false"
The vehicle is electric.
boolean
default:"false"
The goods travel refrigerated.
number
Distance of the leg, in km, from 0.01 to 500,000. Omitted: calculated by the job from
origin and
destination. Not taken by do_not_know, whose transport depends on the places.string
Where the leg starts: an address, a city, a port, a postal code, 1 to 500 characters. Send it with
destination when there is no distance_km.string
Where the leg ends, as
origin.uuid
A record of your custom emission factor databases to
calculate the leg with instead of the default: find it with
List Records. Its
activity_categories must include
the shipment’s direction: transport_distribution_upstream or transport_distribution_downstream.transport_type, travel_method, refrigerated and electric of a section must be one of the
transport combinations (TRANSPORT_COMBINATION_INVALID otherwise).number
Weight carried, in
unit_id, over 0 and up to 999,999,999.99999. Required unless distance_only.uuid
The unit of
quantity_transported: a weight unit, one of those
GET /v2/units?type=transport lists (kilogram
61743a63-ff70-459c-9567-5eee8f7dfd5c, metric tonne cab66828-c2b1-431b-92af-f9ab37149d3c, pound). Required
unless distance_only.boolean
default:"false"
Calculate from the distance alone, with per vehicle-km factors, when the weight is not known:
road sections
only, and no quantity_transported or unit_id.string
A label for the shipment, up to 255 characters.
string
The carrier, up to 255 characters.
string
Your own reference for the record, up to 255 characters, echoed in errors and in the job’s items.
Response
202 Accepted with the job (entity_type: transport_routes, operation: create) and a Location header to it. To
read the result:
GET /v2/ingest-jobs/{id}/itemshas one item per record, in the order sent. Itsentity_idis the shipment’s id. Afaileditem carries itserror_code.- List Transport Routes returns the shipments with their status and emissions
(
co2e); Get Transport Route adds each section’s distance and errors.
Errors
Problem details. Per-record codes inerrors[].error_code of a 422 BULK_RECORDS_REJECTED
(each invalid record is listed once, with its first error):
error_code | Cause |
|---|---|
SCHEMA_INVALID | The record does not match the schema: a missing or unknown field (e.g. organization_id, kms_manual, detail), no transport_direction, no sections or more than 20, a section with no distance_km and no origin + destination, a do_not_know section with distance_km, no quantity_transported or unit_id without distance_only, a weight or a section other than road with distance_only, a date out of range… error_params.errors says where. |
UNIT_NOT_SUPPORTED | unit_id is not a weight unit a shipment takes (error_params.field is unit_id). |
TRANSPORT_COMBINATION_INVALID | A section’s transport_type, travel_method, refrigerated and electric are not one of the transport combinations. error_params.field names the section, e.g. sections[1]. |
MULTIDB_CUSTOM_RECORD_NOT_FOUND_OR_DISABLED | A section’s custom_ef_record_id does not exist, is disabled or is not visible from your organization (error_params.field is e.g. sections[0].custom_ef_record_id). |
MULTIDB_CUSTOM_RECORD_INCOMPATIBLE_CATEGORY | A section’s custom_ef_record_id is not configured for the shipment’s direction (transport_distribution_upstream or transport_distribution_downstream). |
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.
Limits
| Limit | Value |
|---|---|
| Records per request | 1 to 5,000 |
| Sections per shipment | 1 to 20 |
| Shipments per calculation chunk | 50 |
Related Endpoints
Delete Freight Transports (v2)
Delete shipments by id
Transport Combinations
The section transports there are factors for
Get Ingest Job
Follow the calculation
List Job Items
The shipment id and outcome of each record
List Transport Routes
The shipments and their emissions
List Units
The weight units a shipment takes
Was this page helpful?