> ## 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 Use of Sold Products (v2)

> Set how up to 5,000 periods of sold products are used; their emissions are calculated asynchronously

[← Sold Products API](/api-reference/sold-products/overview)

Say how a sold product is used during one of its periods of sales (Scope 3 category 11): how long one unit lasts and
what it consumes, in up to four blocks: electricity, fuel it burns, water and refrigerant it leaks. The periods are
the ones [Create Sold Products (v2)](/api-reference/sold-products/create-v2) answers (`periods[].id`).

The request checks every record and writes them all at once, then answers `202 Accepted` with an
[ingest job](/api-reference/ingest-jobs/get) that calculates their emissions (see
[how bulk ingestion works](/api-reference/ingest-jobs/overview)). Follow it with
[`GET /v2/ingest-jobs/{id}`](/api-reference/ingest-jobs/get) or, for a job submitted with an API key, the
[`ingest_job.finished`](/api-reference/webhooks/overview) webhook. This route replaces
`POST /v1/sold-products/{sold_product_id}/use-of-product` and
`PATCH /v1/sold-products/{sold_product_id}/use-of-product/{use_of_product_data_id}`.

> [!WARNING]
>
> **Beta.** Part of the bulk ingest API, in beta. The contract may still change before general availability.

```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST "https://api.dcycle.io/v2/use-of-sold-products" \
  -H "x-api-key: ${DCYCLE_API_KEY}" \
  -H "x-organization-id: ${DCYCLE_ORG_ID}" \
  -H "Idempotency-Key: 3c2b1a0f-9e8d-4c7b-8a6f-5e4d3c2b1a0f" \
  -H "Content-Type: application/json" \
  -d '{
    "records": [
      {
        "period_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
        "lifespan": 10,
        "lifespan_unit_id": "22651595-5867-49bd-ad9b-e44259e68378",
        "electricity": {
          "quantity": 1200,
          "unit_id": "ba80e6cb-86a4-4bb1-a0c5-8104365d523c",
          "frequency": "yearly"
        },
        "fugitive": {
          "gas_id": "475c39ac-1435-4b10-9559-70966ea0800c",
          "quantity": 0.05,
          "frequency": "yearly"
        },
        "client_row_id": "SKU-X200-2025"
      }
    ]
  }'
```

```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
import os
import uuid

import requests

YEARS = "22651595-5867-49bd-ad9b-e44259e68378"  # lifespan unit (see "Units")
KWH = "ba80e6cb-86a4-4bb1-a0c5-8104365d523c"

response = requests.post(
    "https://api.dcycle.io/v2/use-of-sold-products",
    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": [
            {
                "period_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",  # periods[].id of POST /v2/sold-products
                "lifespan": 10,
                "lifespan_unit_id": YEARS,
                "electricity": {"quantity": 1200, "unit_id": KWH, "frequency": "yearly"},
                "client_row_id": "SKU-X200-2025",
            }
        ]
    },
    timeout=60,
)
response.raise_for_status()
job = response.json()
print(job["id"], job["status"])
```

```json 202 theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": "5c4b3a29-1807-4f6e-9d5c-4b3a29180f6e",
  "status": "processing",
  "entity_type": "use_of_product",
  "operation": "create",
  "source": "api_bulk",
  "counts": { "submitted": 1, "succeeded": 0, "failed": 0 },
  "chunks_total": 1,
  "chunks_done": 0,
  "created_at": "2026-10-21T09:15:02.418Z",
  "finished_at": null,
  "links": {
    "self": "/v2/ingest-jobs/5c4b3a29-1807-4f6e-9d5c-4b3a29180f6e",
    "items": "/v2/ingest-jobs/5c4b3a29-1807-4f6e-9d5c-4b3a29180f6e/items"
  }
}
```

```json 422 theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "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": "SKU-X200-2025",
      "error_code": "USE_OF_PRODUCT_INCONSISTENT",
      "error_params": {
        "field": "combustion",
        "period_id": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f"
      }
    }
  ],
  "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": 1, "valid": 1 }
```

## How it works

1. **Every record is checked**: its schema, its period (it must exist in your organization's
   [perimeter](/api-reference/ingest-jobs/overview#perimeter)), the units, fuel and gas of its blocks, and the fuel and
   gas against the other periods of the product (see step 3). If any record is invalid the **whole** request is
   rejected with `422` and nothing is written ([all or nothing](/api-reference/ingest-jobs/overview#all-or-nothing)):
   `rejected` is the number of invalid records and `errors` lists them (up to 100).
2. **A period has one use.** A record for a period that has none creates it; a record for a period that already has one
   **replaces** it whole (a block you leave out is removed), keeps its id and calculates its emissions again. Two
   records for the same period reject the second (`DUPLICATE_PERIOD`).
3. **Periods of a product that share a calendar year burn the same fuel and leak the same gas**, or none: a period of
   2025 that burns natural gas and another of 2025 that burns diesel, or burns nothing, are
   `USE_OF_PRODUCT_INCONSISTENT`. The rule is checked on the state after the request, against the uses the product
   already has and the other records of the request; a period with no use is not compared. `error_params.field` says
   which block disagrees (`combustion` or `fugitive`) and `error_params.period_id` with which period.
4. **The uses are written at once**, in the organization of their product, and the job calculates them in the
   background, 50 per chunk. For each block, one unit of the product consumes `quantity` per `frequency` over its
   lifespan (`all_life`: `quantity` is already the whole life), and that is multiplied by the quantity sold in each
   country of the period. A use whose calculation fails for some sale has its job item `failed` with
   `CALCULATION_FAILED`.
5. **When every chunk is done** the job is `completed` (or `completed_with_errors`). For a job submitted with an API
   key, `ingest_job.finished` is sent to the webhook endpoints of the `x-organization-id` organization.

### Units

Each unit is checked against its block; any other is `UNIT_NOT_SUPPORTED`, with `error_params.field` naming it.
[`GET /v2/units`](/api-reference/units/list#what-each-type-returns) lists them with `?type=use_of_product_electricity`,
`use_of_product_combustion` and `use_of_product_water`. The lifespan units have no `type`: they are in the whole
catalog (`GET /v2/units` without `type`).

| Field | Units | `unit_id` |
| - | - | - |
| `lifespan_unit_id` | day, week, month, year | `80d92030-2e57-4a1c-95a3-5f9bf481fdf8`, `1b622195-120a-42ff-a003-0b692ccca1d1`, `41351f44-ed23-4ba0-9fae-12d3b4f2d46e`, `22651595-5867-49bd-ad9b-e44259e68378` |
| `electricity.unit_id` | kWh, MWh | `ba80e6cb-86a4-4bb1-a0c5-8104365d523c`, `45f61d28-93a8-4312-9c55-f2b65814aebc` |
| `combustion.unit_id` | kWh, litre, kg, tonne, MJ | `ba80e6cb-86a4-4bb1-a0c5-8104365d523c`, `54a709bf-79fe-4acb-ab85-5e1d7cd26eb4`, `61743a63-ff70-459c-9567-5eee8f7dfd5c`, `cab66828-c2b1-431b-92af-f9ab37149d3c`, `b2f588f1-664b-4fe1-a230-3d342d2e2e85` |
| `water.unit_id` | litre, m³, kg, tonne, gallon (US) | `54a709bf-79fe-4acb-ab85-5e1d7cd26eb4`, `2a09be22-a6e2-4317-a680-94aaa1048765`, `61743a63-ff70-459c-9567-5eee8f7dfd5c`, `cab66828-c2b1-431b-92af-f9ab37149d3c`, `81dd018a-5b1c-4f6a-b304-e36664af5aa7` |

`fugitive` has no unit: its `quantity` is in kilograms.

## Request

### Headers

**x-api-key** `string` *required*

Your API key (see [Authentication](/api-reference/introduction#authentication)).

**x-organization-id** `string` *required*

The organization you act as. A record can name a period of its products or of the organizations under it (its
[perimeter](/api-reference/ingest-jobs/overview#perimeter)); each use belongs to the organization of its product. The
job and its `ingest_job.finished` webhook belong to the `x-organization-id` organization.

**Idempotency-Key** `string` *required*

1 to 255 characters, one per batch, looked up per organization across every v2 bulk route. Within 24 hours, the
same key with the same route and body returns the original job (with `Idempotent-Replayed: true`) instead of
writing the uses again; the same key with another body or on another route is `422 IDEMPOTENCY_KEY_REUSED`, and
while the first request is still being processed `409 IDEMPOTENCY_KEY_IN_PROGRESS`. After 24 hours the key is free,
and a request with it is a new one. See [The Idempotency-Key](/api-reference/ingest-jobs/overview#the-idempotency-key).

### Query Parameters

**dry\_run** `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. It does not check or store the
`Idempotency-Key`.

### Body Parameters

**records** `array[object]` *required*

The uses to write, 1 to 5,000, one per period. A field not listed here is rejected (`SCHEMA_INVALID`), in the
record and in its blocks.

**record**

**period\_id** `uuid` *required*

The period of sales the use is for: a `periods[].id` of
[Create Sold Products (v2)](/api-reference/sold-products/create-v2), or an `id` of
[List Periods](/api-reference/sold-products/list-periods).

**lifespan** `number` *required*

How long one unit of the product is used, over 0 and under 1,000,000, in `lifespan_unit_id`.

**lifespan\_unit\_id** `uuid` *required*

Day, week, month or year (see [Units](#units)).

**electricity** `object`

Electricity one unit consumes. Send at least one of `electricity`, `combustion`, `water` and `fugitive`.

**electricity**

**quantity** `number` *required*

What one unit consumes per `frequency`, over 0 and under 10¹².

**unit\_id** `uuid` *required*

kWh or MWh (see [Units](#units)).

**frequency** `string` *required*

`all_life` (the `quantity` covers the whole lifespan), `yearly`, `monthly`, `weekly` or `daily`.

**combustion** `object`

Fuel one unit burns: `quantity`, `unit_id` (see [Units](#units)) and `frequency`, as in `electricity`, plus:

**combustion**

**fuel\_id** `uuid` *required*

The fuel, an `id` of `GET /v1/sold-products/combustion-fuels` (see
[Lookups outside the document](/api-reference/openapi-sdks#lookups-outside-the-document)).

**water** `object`

Water one unit consumes: `quantity`, `unit_id` (see [Units](#units)) and `frequency`, as in `electricity`.

**fugitive** `object`

Refrigerant one unit leaks.

**fugitive**

**gas\_id** `uuid` *required*

The refrigerant gas, an `id` of [`GET /v1/recharge/gases`](/api-reference/invoices/refrigerant-gases-list).

**quantity** `number` *required*

Kilograms one unit leaks per `frequency`, over 0 and under 10¹².

**frequency** `string` *required*

`all_life`, `yearly`, `monthly`, `weekly` or `daily`, as in `electricity`.

**client\_row\_id** `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: use_of_product`, `operation: create`) and a `Location` header to it. To read
the result:

* [`GET /v2/ingest-jobs/{id}/items`](/api-reference/ingest-jobs/list-items) has one item per record, in the order
  sent. Its `entity_id` is the use's id, the one [Delete Use of Sold Products (v2)](/api-reference/use-of-sold-products/delete-v2)
  takes; a replaced use keeps its id. A `failed` item carries its `error_code`.
* [Period Emissions](/api-reference/sold-products/emissions) returns the use-phase emissions of a period, with
  the `use_of_product_data_id` of its use.

> [!WARNING]
>
> If `chunks_done` stops moving, see [A job that does not finish](/api-reference/ingest-jobs/get#a-job-that-does-not-finish).
> Resending the batch with a new `Idempotency-Key` replaces the same uses and calculates them again.

## Errors

[Problem details](/api-reference/errors). Per-record codes in `errors[].error_code` of a `422 BULK_RECORDS_REJECTED`:

| `error_code` | Cause |
| - | - |
| `SCHEMA_INVALID` | The record does not match the schema: no `period_id`, `lifespan` or `lifespan_unit_id`, none of the four blocks, a `quantity` or `lifespan` not over 0, an unknown `frequency`, an unknown field (e.g. `organization_id`, or `unit_id` in `fugitive`)... `error_params.errors` says where. |
| `REFERENCE_NOT_FOUND` | The period does not exist or is outside your organization's [perimeter](/api-reference/ingest-jobs/overview#perimeter) (both answer the same; `error_params.field` is `period_id`), or Dcycle does not know the fuel (`combustion.fuel_id`) or the gas (`fugitive.gas_id`). |
| `DUPLICATE_PERIOD` | Another record of the request is for the same period (`error_params.field` is `period_id`). Send one use per period. |
| `UNIT_NOT_SUPPORTED` | A unit is not one of [its block's](#units). `error_params.field` is `lifespan_unit_id`, `electricity.unit_id`, `combustion.unit_id` or `water.unit_id`. |
| `USE_OF_PRODUCT_INCONSISTENT` | A period of the same product sharing a calendar year burns another fuel, or leaks another gas, or one burns or leaks and the other does not, counting what is stored and what the request sends. `error_params.field` is `combustion` or `fugitive` and `error_params.period_id` the other period. |

Request-level errors: `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](/api-reference/rate-limits).

## Limits

| Limit | Value |
| - | - |
| Records per request | 1 to 5,000 |
| Uses per period | 1 |
| Uses per calculation chunk | 50 |

## Related Endpoints

> **[Delete Use of Sold Products (v2)](/api-reference/use-of-sold-products/delete-v2)**
>
> Delete uses by id

> **[Create Sold Products (v2)](/api-reference/sold-products/create-v2)**
>
> The products and periods a use is for

> **[Period Emissions](/api-reference/sold-products/emissions)**
>
> The emissions of a period

> **[Get Ingest Job](/api-reference/ingest-jobs/get)**
>
> Follow the calculation


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