curl -X POST "https://api.dcycle.io/v2/invoices/district-cooling" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Idempotency-Key: 4d3c2b1a-0f9e-4d8c-b7a6-5f4e3d2c1b0a" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"facility_id": "0b8e4d2a-1c3f-4e5a-9b7d-6c8e0f2a4b6d",
"start_date": "2026-01-01",
"end_date": "2026-01-31",
"quantity": 8400,
"unit_id": "ba80e6cb-86a4-4bb1-a0c5-8104365d523c",
"supplier_id": "5f4e3d2c-1b0a-4f9e-8d7c-6b5a4f3e2d1c",
"invoice_number": "DH-2026-0042",
"client_row_id": "DH-0001"
},
{
"facility_id": "7f6e5d4c-3b2a-4190-8e7f-6d5c4b3a2918",
"start_date": "2026-01-01",
"end_date": "2026-03-31",
"quantity": 2.5,
"unit_id": "45f61d28-93a8-4312-9c55-f2b65814aebc"
}
]
}'
{
"id": "6a5b4c3d-2e1f-4a0b-9c8d-7e6f5a4b3c2d",
"status": "processing",
"entity_type": "invoices",
"operation": "create",
"source": "api_bulk",
"counts": { "submitted": 2, "succeeded": 0, "failed": 0 },
"chunks_total": 1,
"chunks_done": 0,
"created_at": "2026-10-13T10:41:07.512Z",
"finished_at": null,
"links": {
"self": "/v2/ingest-jobs/6a5b4c3d-2e1f-4a0b-9c8d-7e6f5a4b3c2d",
"items": "/v2/ingest-jobs/6a5b4c3d-2e1f-4a0b-9c8d-7e6f5a4b3c2d/items"
}
}
{
"type": "https://code.dcycle.io/api-reference/errors#BULK_RECORDS_REJECTED",
"title": "Unprocessable Content",
"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": "DH-0001",
"error_code": "UNIT_NOT_SUPPORTED",
"error_params": { "field": "unit_id", "value": "ba80e6cb-86a4-4bb1-a0c5-8104365d523c" }
}
],
"rejected": 1,
"request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
}
{ "dry_run": true, "submitted": 2, "valid": 2 }
Create District Cooling Invoices (v2)
Create up to 5,000 district cooling invoices in one request; emissions are calculated asynchronously
POST
/
v2
/
invoices
/
district-cooling
curl -X POST "https://api.dcycle.io/v2/invoices/district-cooling" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Idempotency-Key: 4d3c2b1a-0f9e-4d8c-b7a6-5f4e3d2c1b0a" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"facility_id": "0b8e4d2a-1c3f-4e5a-9b7d-6c8e0f2a4b6d",
"start_date": "2026-01-01",
"end_date": "2026-01-31",
"quantity": 8400,
"unit_id": "ba80e6cb-86a4-4bb1-a0c5-8104365d523c",
"supplier_id": "5f4e3d2c-1b0a-4f9e-8d7c-6b5a4f3e2d1c",
"invoice_number": "DH-2026-0042",
"client_row_id": "DH-0001"
},
{
"facility_id": "7f6e5d4c-3b2a-4190-8e7f-6d5c4b3a2918",
"start_date": "2026-01-01",
"end_date": "2026-03-31",
"quantity": 2.5,
"unit_id": "45f61d28-93a8-4312-9c55-f2b65814aebc"
}
]
}'
{
"id": "6a5b4c3d-2e1f-4a0b-9c8d-7e6f5a4b3c2d",
"status": "processing",
"entity_type": "invoices",
"operation": "create",
"source": "api_bulk",
"counts": { "submitted": 2, "succeeded": 0, "failed": 0 },
"chunks_total": 1,
"chunks_done": 0,
"created_at": "2026-10-13T10:41:07.512Z",
"finished_at": null,
"links": {
"self": "/v2/ingest-jobs/6a5b4c3d-2e1f-4a0b-9c8d-7e6f5a4b3c2d",
"items": "/v2/ingest-jobs/6a5b4c3d-2e1f-4a0b-9c8d-7e6f5a4b3c2d/items"
}
}
{
"type": "https://code.dcycle.io/api-reference/errors#BULK_RECORDS_REJECTED",
"title": "Unprocessable Content",
"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": "DH-0001",
"error_code": "UNIT_NOT_SUPPORTED",
"error_params": { "field": "unit_id", "value": "ba80e6cb-86a4-4bb1-a0c5-8104365d523c" }
}
],
"rejected": 1,
"request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
}
{ "dry_run": true, "submitted": 2, "valid": 2 }
← Invoices API
Create one or many district cooling invoices: chilled water bought from a district network, instead of produced on site. The request checks every record and writes
them at once; their emissions are calculated in the background. The answer is
202 Accepted with a job: follow it with GET /v2/ingest-jobs/{id}
or the ingest_job.finished webhook.
This route only takes what a district cooling invoice has; district heating has its own route,
POST /v2/invoices/district-heating. The other categories get their own route; until then they
keep POST /v1/invoices.
Beta. Part of the bulk ingest API, currently in beta. The contract may still change before general availability.
curl -X POST "https://api.dcycle.io/v2/invoices/district-cooling" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Idempotency-Key: 4d3c2b1a-0f9e-4d8c-b7a6-5f4e3d2c1b0a" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"facility_id": "0b8e4d2a-1c3f-4e5a-9b7d-6c8e0f2a4b6d",
"start_date": "2026-01-01",
"end_date": "2026-01-31",
"quantity": 8400,
"unit_id": "ba80e6cb-86a4-4bb1-a0c5-8104365d523c",
"supplier_id": "5f4e3d2c-1b0a-4f9e-8d7c-6b5a4f3e2d1c",
"invoice_number": "DH-2026-0042",
"client_row_id": "DH-0001"
},
{
"facility_id": "7f6e5d4c-3b2a-4190-8e7f-6d5c4b3a2918",
"start_date": "2026-01-01",
"end_date": "2026-03-31",
"quantity": 2.5,
"unit_id": "45f61d28-93a8-4312-9c55-f2b65814aebc"
}
]
}'
{
"id": "6a5b4c3d-2e1f-4a0b-9c8d-7e6f5a4b3c2d",
"status": "processing",
"entity_type": "invoices",
"operation": "create",
"source": "api_bulk",
"counts": { "submitted": 2, "succeeded": 0, "failed": 0 },
"chunks_total": 1,
"chunks_done": 0,
"created_at": "2026-10-13T10:41:07.512Z",
"finished_at": null,
"links": {
"self": "/v2/ingest-jobs/6a5b4c3d-2e1f-4a0b-9c8d-7e6f5a4b3c2d",
"items": "/v2/ingest-jobs/6a5b4c3d-2e1f-4a0b-9c8d-7e6f5a4b3c2d/items"
}
}
{
"type": "https://code.dcycle.io/api-reference/errors#BULK_RECORDS_REJECTED",
"title": "Unprocessable Content",
"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": "DH-0001",
"error_code": "UNIT_NOT_SUPPORTED",
"error_params": { "field": "unit_id", "value": "ba80e6cb-86a4-4bb1-a0c5-8104365d523c" }
}
],
"rejected": 1,
"request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
}
{ "dry_run": true, "submitted": 2, "valid": 2 }
How it works
- Every record is checked. If any is invalid the whole request is rejected with
422and nothing is written; the answer lists every invalid record, so you can fix them all at once. - The invoices are written at once, with status
loading. An invoice split across facilities (facility_percentages) becomes one invoice per facility, each with its share ofquantityandtotal_spend. - The job calculates their emissions in the background. Each invoice ends
active, orerrorwhen it could not be calculated (CALCULATION_FAILEDin the job’s items). - When every chunk is done the job is
completed(orcompleted_with_errors) andingest_job.finishedis sent to your webhook endpoints.
Request
Headers
string
required
Your API key for authentication
string
required
Your organization UUID
string
required
1 to 255 characters, unique per batch. A retry with the same key and body returns the original job instead of
creating the invoices again; the same key with another body is
422 IDEMPOTENCY_KEY_REUSED.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 invoices to create, 1 to 5,000.
Show record
Show record
uuid
The facility that consumed the whole invoice. Send it or
facility_percentages.array[object]
Split the invoice across facilities:
[{"facility_id": "...", "percentage": 0.6}, ...]. Each facility once;
each share above 0 and at most 1, four decimals; the shares add up to 1 at most.date
required
First day of the billing period,
YYYY-MM-DD.date
required
Last day of the billing period, on or after
start_date.number
required
Energy delivered in the period, 0 or more, for the whole invoice.
uuid
required
An energy unit of
GET /v2/units: kWh ba80e6cb-86a4-4bb1-a0c5-8104365d523c, MWh
45f61d28-93a8-4312-9c55-f2b65814aebc, MJ b2f588f1-664b-4fe1-a230-3d342d2e2e85 or GJ
588ff56e-a428-43a3-9cc3-b59a19b54a44. Put what the invoice cost in total_spend.uuid
The network’s supplier (
GET /v2/suppliers), a district cooling one.uuid
A custom emission factor record to calculate with, instead of the default one.
string
The supplier’s invoice number, up to 255 characters.
number
What the invoice cost, 0 or more, in
currency_unit_id (sent together). Informative.uuid
A currency unit of
GET /v2/units, e.g. euros
d3e37f2b-0fc3-4532-82f8-3890ab56ad37.string
Your own reference for the record, up to 255 characters, echoed in errors and in the job’s items.
Response
202 Accepted, the job (entity_type: invoices, operation: create) and a Location header to it.
GET /v2/ingest-jobs/{id}/items has one item per record, in the order sent,
whose entity_id is the invoice’s id (for a split invoice, the id of its first facility’s invoice).
Errors
Problem details. Per-record codes inerrors[].error_code:
error_code | Cause |
|---|---|
SCHEMA_INVALID | The record does not match the schema: a missing field, both or neither of facility_id and facility_percentages, shares above 1, end_date before start_date, total_spend without currency_unit_id… error_params.errors says where. |
FACILITY_NOT_FOUND | A facility does not exist or is outside your organization’s perimeter (both answer the same). |
UNIT_NOT_SUPPORTED | unit_id is not kWh, MWh, MJ or GJ, or currency_unit_id is not a currency (error_params.field). |
REFERENCE_NOT_FOUND | supplier_id does not exist or your organization cannot use it. |
INVALID_SUPPLIER_TYPE | supplier_id is not a district cooling supplier. |
MULTIDB_CUSTOM_RECORD_NOT_FOUND_OR_DISABLED | custom_ef_record_id does not exist, is disabled or is not visible from the facility’s organization. |
MULTIDB_CUSTOM_RECORD_INCOMPATIBLE_CATEGORY | custom_ef_record_id is not configured for district cooling. |
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 for a key already used with another body.
Limits
| Limit | Value |
|---|---|
| Records per request | 1 to 5,000 |
Related Endpoints
Create District Heating Invoices (v2)
The district heating route
Delete Invoices (v2)
Delete invoices of any category
Get Ingest Job
Follow the calculation
Was this page helpful?