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"
}
]
}'
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"])
{
"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"
}
}
{
"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"
}
{ "dry_run": true, "submitted": 1, "valid": 1 }
Create Use of Sold Products (v2)
Set how up to 5,000 periods of sold products are used; their emissions are calculated asynchronously
POST
/
v2
/
use-of-sold-products
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"
}
]
}'
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"])
{
"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"
}
}
{
"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"
}
{ "dry_run": true, "submitted": 1, "valid": 1 }
← Sold Products API
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) answers (
Request-level errors:
periods[].id).
The request checks every record and writes them all at once, then answers 202 Accepted with an
ingest job that calculates their emissions (see
how bulk ingestion works). Follow it with
GET /v2/ingest-jobs/{id} or, for a job submitted with an API key, the
ingest_job.finished 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}.
Beta. Part of the bulk ingest API, in beta. The contract may still change before general availability.
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"
}
]
}'
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"])
{
"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"
}
}
{
"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"
}
{ "dry_run": true, "submitted": 1, "valid": 1 }
How it works
- Every record is checked: its schema, its period (it must exist in your organization’s
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
422and nothing is written (all or nothing):rejectedis the number of invalid records anderrorslists them (up to 100). - 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). - 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.fieldsays which block disagrees (combustionorfugitive) anderror_params.period_idwith which period. - 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
quantityperfrequencyover its lifespan (all_life:quantityis 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 itemfailedwithCALCULATION_FAILED. - When every chunk is done the job is
completed(orcompleted_with_errors). For a job submitted with an API key,ingest_job.finishedis sent to the webhook endpoints of thex-organization-idorganization.
Units
Each unit is checked against its block; any other isUNIT_NOT_SUPPORTED, with error_params.field naming it.
GET /v2/units 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
string
required
Your API key (see Authentication).
string
required
The organization you act as. A record can name a period of its products or of the organizations under it (its
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.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.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. It does not check or store the
Idempotency-Key.Body Parameters
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.Show record
Show record
uuid
required
The period of sales the use is for: a
periods[].id of
Create Sold Products (v2), or an id of
List Periods.number
required
How long one unit of the product is used, over 0 and under 1,000,000, in
lifespan_unit_id.object
Electricity one unit consumes. Send at least one of
electricity, combustion, water and fugitive.object
Fuel one unit burns:
quantity, unit_id (see Units) and frequency, as in electricity, plus:Show combustion
Show combustion
uuid
required
The fuel, an
id of GET /v1/sold-products/combustion-fuels (see
Lookups outside the document).object
Refrigerant one unit leaks.
Show fugitive
Show fugitive
uuid
required
The refrigerant gas, an
id of GET /v1/recharge/gases.number
required
Kilograms one unit leaks per
frequency, over 0 and under 10¹².string
required
all_life, yearly, monthly, weekly or daily, as in electricity.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}/itemshas one item per record, in the order sent. Itsentity_idis the use’s id, the one Delete Use of Sold Products (v2) takes; a replaced use keeps its id. Afaileditem carries itserror_code.- Period Emissions returns the use-phase emissions of a period, with
the
use_of_product_data_idof its use.
If
chunks_done stops moving, see 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. Per-record codes inerrors[].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 (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. 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. |
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.
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)
Delete uses by id
Create Sold Products (v2)
The products and periods a use is for
Period Emissions
The emissions of a period
Get Ingest Job
Follow the calculation
Was this page helpful?