> ## 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 Fuel Invoices (v2)

> Create up to 5,000 fuel invoices (stationary combustion) in one request; emissions are calculated asynchronously

[← Invoices API](/api-reference/invoices/overview)

Create one or many fuel invoices: fuels burned on site (natural gas, diesel, LPG, biomass...), the stationary
combustion of scope 1. The request checks every record and writes them at once; their emissions (combustion and the
fuel's upstream, well-to-tank) are calculated in the background. The
answer is `202 Accepted` with a **job**: follow it with [`GET /v2/ingest-jobs/{id}`](/api-reference/ingest-jobs/get)
or the [`ingest_job.finished`](/api-reference/webhooks/overview) webhook.

This route only takes what a fuel invoice has. The other categories get their own route; until then they
keep [`POST /v1/invoices`](/api-reference/invoices/create).

<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/invoices/fuels" \
    -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": 18200,
          "unit_id": "ba80e6cb-86a4-4bb1-a0c5-8104365d523c",
          "stationary_fuel_id": "d247c183-4cc1-4fad-aa47-3b2f2dcc6da8",
          "cups": "ES0230000000000001XY",
          "invoice_number": "GN-2026-0042",
          "client_row_id": "GAS-0001"
        },
        {
          "facility_id": "7f6e5d4c-3b2a-4190-8e7f-6d5c4b3a2918",
          "start_date": "2026-01-01",
          "end_date": "2026-03-31",
          "quantity": 2500,
          "unit_id": "54a709bf-79fe-4acb-ab85-5e1d7cd26eb4",
          "stationary_fuel_id": "242c7255-0b18-4907-8fef-374baf06d7a0"
        }
      ]
    }'
  ```
</RequestExample>

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

  ```json 422 theme={"theme":{"light":"github-light","dark":"github-dark"}}
  {
    "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": "GAS-0001",
        "error_code": "REFERENCE_NOT_FOUND",
        "error_params": { "field": "stationary_fuel_id", "value": "00000000-0000-0000-0000-000000000000" }
      }
    ],
    "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 invoices are written at once**, with status `loading`. An invoice split across facilities
   (`facility_percentages`) becomes one invoice per facility, each with its share of `quantity` and `total_spend`.
3. **The job calculates their emissions** in the background. Each invoice ends `active`, or `error` when it could not
   be calculated, e.g. when the fuel's emission factors cannot be converted to the unit sent (`CALCULATION_FAILED` in
   the job's items).
4. **When every chunk is done** the job is `completed` (or `completed_with_errors`) and `ingest_job.finished` is sent
   to your webhook endpoints.

## Request

### Headers

<ParamField header="x-api-key" type="string" required>
  Your API key for authentication
</ParamField>

<ParamField header="x-organization-id" type="string" required>
  Your organization UUID
</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 instead of
  creating the invoices again; the same key with another body is `422 IDEMPOTENCY_KEY_REUSED`.
</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 invoices to create, 1 to 5,000.

  <Expandable title="record">
    <ParamField body="facility_id" type="uuid">
      The facility that consumed the whole invoice. Send it **or** `facility_percentages`.
    </ParamField>

    <ParamField body="facility_percentages" type="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.
    </ParamField>

    <ParamField body="start_date" type="date" required>
      First day of the billing period, `YYYY-MM-DD`.
    </ParamField>

    <ParamField body="end_date" type="date" required>
      Last day of the billing period, on or after `start_date`.
    </ParamField>

    <ParamField body="quantity" type="number" required>
      Fuel consumed in the period, 0 or more, for the whole invoice.
    </ParamField>

    <ParamField body="unit_id" type="uuid" required>
      A combustion unit of [`GET /v2/units`](/api-reference/units/list): energy (kWh, MJ, GJ, therm, MMBtu...),
      volume (m³, litre, gallon, barrel, ft³) or mass (kg, tonne, lb). E.g. kWh
      `ba80e6cb-86a4-4bb1-a0c5-8104365d523c`, m³ `2a09be22-a6e2-4317-a680-94aaa1048765`, litre
      `54a709bf-79fe-4acb-ab85-5e1d7cd26eb4`.
    </ParamField>

    <ParamField body="stationary_fuel_id" type="uuid">
      The fuel burned (`GET /v1/stationary/fuels`), e.g. natural gas `d247c183-4cc1-4fad-aa47-3b2f2dcc6da8`.
      Required unless `custom_ef_record_id` is sent.
    </ParamField>

    <ParamField body="custom_ef_record_id" type="uuid">
      A custom emission factor record to calculate with, instead of the fuel's default one.
    </ParamField>

    <ParamField body="cups" type="string">
      The supply point code, e.g. the natural gas CUPS in Spain, up to 50 characters. Also added to the facility's CUPS.
    </ParamField>

    <ParamField body="invoice_number" type="string">
      The supplier's invoice number, up to 255 characters.
    </ParamField>

    <ParamField body="total_spend" type="number">
      What the invoice cost, 0 or more, in `currency_unit_id` (sent together). Informative.
    </ParamField>

    <ParamField body="currency_unit_id" type="uuid">
      A currency unit of [`GET /v2/units`](/api-reference/units/list), e.g. euros
      `d3e37f2b-0fc3-4532-82f8-3890ab56ad37`.
    </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>

## Response

`202 Accepted`, the job (`entity_type: invoices`, `operation: create`) and a `Location` header to it.
[`GET /v2/ingest-jobs/{id}/items`](/api-reference/ingest-jobs/list-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](/api-reference/errors). Per-record codes in `errors[].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`, neither `stationary_fuel_id` nor `custom_ef_record_id`, 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 a combustion unit, or `currency_unit_id` is not a currency (`error_params.field`). |
| `REFERENCE_NOT_FOUND` | `stationary_fuel_id` is not a fuel Dcycle knows. |
| `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 stationary combustion. |

`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

<CardGroup cols={2}>
  <Card title="Create Electricity Invoices (v2)" icon="bolt" href="/api-reference/invoices/electricity-create-v2">
    The electricity route
  </Card>

  <Card title="Delete Invoices (v2)" icon="trash" href="/api-reference/invoices/delete-v2">
    Delete invoices of any category
  </Card>

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


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