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

> Create up to 5,000 refrigerant recharges (fugitive emissions) in one request; emissions are calculated asynchronously

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

Create one or many refrigerant recharges: the gas topped up in air conditioning, heat pumps or cold rooms, which
replaces what leaked (the fugitive emissions of scope 1). The request checks every record and writes them at once; their
emissions (the kilograms recharged times the gas's global warming potential) 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 refrigerant recharge 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/refrigerants" \
    -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": 3.5,
          "unit_id": "61743a63-ff70-459c-9567-5eee8f7dfd5c",
          "refrigerant_fuel_id": "475c39ac-1435-4b10-9559-70966ea0800c",
          "invoice_number": "MNT-2026-0042",
          "client_row_id": "RF-0001"
        },
        {
          "facility_id": "7f6e5d4c-3b2a-4190-8e7f-6d5c4b3a2918",
          "start_date": "2026-01-01",
          "end_date": "2026-03-31",
          "quantity": 1.2,
          "unit_id": "61743a63-ff70-459c-9567-5eee8f7dfd5c",
          "refrigerant_fuel_id": "8fd1ff5d-3f57-47cf-ae83-a841fd084b95"
        }
      ]
    }'
  ```
</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": "RF-0001",
        "error_code": "REFERENCE_NOT_FOUND",
        "error_params": { "field": "refrigerant_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 (`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>
      Kilograms of gas recharged in the period, 0 or more, for the whole invoice.
    </ParamField>

    <ParamField body="unit_id" type="uuid" required>
      Kilograms: `61743a63-ff70-459c-9567-5eee8f7dfd5c`. Put what the recharge cost in `total_spend`.
    </ParamField>

    <ParamField body="refrigerant_fuel_id" type="uuid">
      The refrigerant gas recharged (`GET /v1/recharge/gases`), e.g. R-134a `475c39ac-1435-4b10-9559-70966ea0800c`, R-32
      `8fd1ff5d-3f57-47cf-ae83-a841fd084b95`. 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 gas's default one.
    </ParamField>

    <ParamField body="invoice_number" type="string">
      The invoice number of the maintenance company, 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 `refrigerant_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 kg, or `currency_unit_id` is not a currency (`error_params.field`). |
| `REFERENCE_NOT_FOUND` | `refrigerant_fuel_id` is not a refrigerant gas 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 refrigerant recharges. |

`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 Fuel Invoices (v2)" icon="fire" href="/api-reference/invoices/fuels-create-v2">
    The fuel 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.