curl -X POST "https://api.dcycle.io/v2/invoices/electricity" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Idempotency-Key: 8c1f2e3d-4a5b-4c6d-9e7f-0a1b2c3d4e5f" \
-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": 12450,
"unit_id": "ba80e6cb-86a4-4bb1-a0c5-8104365d523c",
"supplier_id": "5f4e3d2c-1b0a-4f9e-8d7c-6b5a4f3e2d1c",
"cups": "ES0021000000000001AB",
"invoice_number": "FE26-000123",
"total_spend": 2130.4,
"currency_unit_id": "d3e37f2b-0fc3-4532-82f8-3890ab56ad37",
"client_row_id": "INV-0001"
},
{
"facility_percentages": [
{ "facility_id": "0b8e4d2a-1c3f-4e5a-9b7d-6c8e0f2a4b6d", "percentage": 0.6 },
{ "facility_id": "7f6e5d4c-3b2a-4190-8e7f-6d5c4b3a2918", "percentage": 0.4 }
],
"start_date": "2026-01-01",
"end_date": "2026-01-31",
"quantity": 3000,
"unit_id": "ba80e6cb-86a4-4bb1-a0c5-8104365d523c"
}
]
}'
import os
import uuid
import requests
KWH = "ba80e6cb-86a4-4bb1-a0c5-8104365d523c"
response = requests.post(
"https://api.dcycle.io/v2/invoices/electricity",
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": [
{
"facility_id": "0b8e4d2a-1c3f-4e5a-9b7d-6c8e0f2a4b6d",
"start_date": "2026-01-01",
"end_date": "2026-01-31",
"quantity": 12450,
"unit_id": KWH,
"client_row_id": "INV-0001",
}
]
},
timeout=60,
)
response.raise_for_status()
job = response.json()
print(job["id"], job["status"])
{
"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": "INV-0001",
"error_code": "UNIT_NOT_SUPPORTED",
"error_params": { "field": "unit_id", "value": "61743a63-ff70-459c-9567-5eee8f7dfd5c" }
}
],
"rejected": 1,
"request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
}
{ "dry_run": true, "submitted": 2, "valid": 2 }
Create Electricity Invoices (v2)
Create up to 5,000 electricity invoices in one request; emissions are calculated asynchronously
POST
/
v2
/
invoices
/
electricity
curl -X POST "https://api.dcycle.io/v2/invoices/electricity" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Idempotency-Key: 8c1f2e3d-4a5b-4c6d-9e7f-0a1b2c3d4e5f" \
-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": 12450,
"unit_id": "ba80e6cb-86a4-4bb1-a0c5-8104365d523c",
"supplier_id": "5f4e3d2c-1b0a-4f9e-8d7c-6b5a4f3e2d1c",
"cups": "ES0021000000000001AB",
"invoice_number": "FE26-000123",
"total_spend": 2130.4,
"currency_unit_id": "d3e37f2b-0fc3-4532-82f8-3890ab56ad37",
"client_row_id": "INV-0001"
},
{
"facility_percentages": [
{ "facility_id": "0b8e4d2a-1c3f-4e5a-9b7d-6c8e0f2a4b6d", "percentage": 0.6 },
{ "facility_id": "7f6e5d4c-3b2a-4190-8e7f-6d5c4b3a2918", "percentage": 0.4 }
],
"start_date": "2026-01-01",
"end_date": "2026-01-31",
"quantity": 3000,
"unit_id": "ba80e6cb-86a4-4bb1-a0c5-8104365d523c"
}
]
}'
import os
import uuid
import requests
KWH = "ba80e6cb-86a4-4bb1-a0c5-8104365d523c"
response = requests.post(
"https://api.dcycle.io/v2/invoices/electricity",
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": [
{
"facility_id": "0b8e4d2a-1c3f-4e5a-9b7d-6c8e0f2a4b6d",
"start_date": "2026-01-01",
"end_date": "2026-01-31",
"quantity": 12450,
"unit_id": KWH,
"client_row_id": "INV-0001",
}
]
},
timeout=60,
)
response.raise_for_status()
job = response.json()
print(job["id"], job["status"])
{
"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": "INV-0001",
"error_code": "UNIT_NOT_SUPPORTED",
"error_params": { "field": "unit_id", "value": "61743a63-ff70-459c-9567-5eee8f7dfd5c" }
}
],
"rejected": 1,
"request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
}
{ "dry_run": true, "submitted": 2, "valid": 2 }
← Invoices API
Create one or many electricity invoices. The request checks every record and writes them at once; their emissions
(market-based, location-based, generation and transmission & distribution) 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 an electricity invoice has. 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/electricity" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Idempotency-Key: 8c1f2e3d-4a5b-4c6d-9e7f-0a1b2c3d4e5f" \
-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": 12450,
"unit_id": "ba80e6cb-86a4-4bb1-a0c5-8104365d523c",
"supplier_id": "5f4e3d2c-1b0a-4f9e-8d7c-6b5a4f3e2d1c",
"cups": "ES0021000000000001AB",
"invoice_number": "FE26-000123",
"total_spend": 2130.4,
"currency_unit_id": "d3e37f2b-0fc3-4532-82f8-3890ab56ad37",
"client_row_id": "INV-0001"
},
{
"facility_percentages": [
{ "facility_id": "0b8e4d2a-1c3f-4e5a-9b7d-6c8e0f2a4b6d", "percentage": 0.6 },
{ "facility_id": "7f6e5d4c-3b2a-4190-8e7f-6d5c4b3a2918", "percentage": 0.4 }
],
"start_date": "2026-01-01",
"end_date": "2026-01-31",
"quantity": 3000,
"unit_id": "ba80e6cb-86a4-4bb1-a0c5-8104365d523c"
}
]
}'
import os
import uuid
import requests
KWH = "ba80e6cb-86a4-4bb1-a0c5-8104365d523c"
response = requests.post(
"https://api.dcycle.io/v2/invoices/electricity",
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": [
{
"facility_id": "0b8e4d2a-1c3f-4e5a-9b7d-6c8e0f2a4b6d",
"start_date": "2026-01-01",
"end_date": "2026-01-31",
"quantity": 12450,
"unit_id": KWH,
"client_row_id": "INV-0001",
}
]
},
timeout=60,
)
response.raise_for_status()
job = response.json()
print(job["id"], job["status"])
{
"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": "INV-0001",
"error_code": "UNIT_NOT_SUPPORTED",
"error_params": { "field": "unit_id", "value": "61743a63-ff70-459c-9567-5eee8f7dfd5c" }
}
],
"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 (see 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
Electricity consumed in the period, 0 or more, for the whole invoice.
uuid
required
Kilowatt-hours:
ba80e6cb-86a4-4bb1-a0c5-8104365d523c. Put what the invoice cost in total_spend.uuid
The id of an electricity supplier (
GET /v2/suppliers), for the market-based
calculation. Not together with custom_ef_record_id.uuid
A custom emission factor record to calculate with, instead of the supplier’s or the default one.
string
The supply point code (CUPS in Spain), up to 50 characters. Also added to the facility’s CUPS.
boolean
default:"false"
Electricity generated and consumed on site (e.g. solar panels).
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, supplier_id with custom_ef_record_id, 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, 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 an electricity 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 electricity. |
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
Delete Invoices (v2)
Delete invoices of any category
Get Ingest Job
Follow the calculation
Was this page helpful?