curl -X POST "https://api.dcycle.io/v2/sold-products" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Idempotency-Key: 4d3c2b1a-0f9e-4d8c-9b7a-6f5e4d3c2b1a" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"name": "Industrial Compressor X200",
"client_row_id": "SKU-X200",
"periods": [
{
"start_date": "2025-01-01",
"end_date": "2025-12-31",
"unit_id": "bf800281-bcc7-42f4-81e4-8ec9abb9745e",
"sales": [
{ "country": "ES", "quantity": 250 },
{ "country": "DE", "quantity": 180 },
{ "country": "RoW", "quantity": 40 }
]
}
]
}
]
}'
import os
import uuid
import requests
UNITS = "bf800281-bcc7-42f4-81e4-8ec9abb9745e" # units (see "Units")
response = requests.post(
"https://api.dcycle.io/v2/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": [
{
"name": "Industrial Compressor X200",
"client_row_id": "SKU-X200",
"periods": [
{
"start_date": "2025-01-01",
"end_date": "2025-12-31",
"unit_id": UNITS,
"sales": [
{"country": "ES", "quantity": 250},
{"country": "DE", "quantity": 180},
{"country": "RoW", "quantity": 40},
],
}
],
}
]
},
timeout=60,
)
response.raise_for_status()
print([item["sold_product"]["id"] for item in response.json()["items"]])
{
"items": [
{
"source_row_index": 0,
"client_row_id": "SKU-X200",
"sold_product": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"organization_id": "a8315ef3-dd50-43f8-b7ce-d839e68d51fa",
"name": "Industrial Compressor X200",
"created_at": "2026-10-21T09:12:44.031Z",
"periods": [
{
"id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"start_date": "2025-01-01",
"end_date": "2025-12-31",
"unit_id": "bf800281-bcc7-42f4-81e4-8ec9abb9745e",
"sales": [
{ "id": "6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d", "country": "ES", "quantity": 250.0 },
{ "id": "7b8c9d0e-1f2a-4b3c-9d4e-5f6a7b8c9d0e", "country": "DE", "quantity": 180.0 },
{ "id": "8c9d0e1f-2a3b-4c4d-8e5f-6a7b8c9d0e1f", "country": "RoW", "quantity": 40.0 }
]
}
]
}
}
]
}
{
"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",
"error_code": "PERIOD_OVERLAPS",
"error_params": {
"field": "periods[0]",
"period_id": "9d0e1f2a-3b4c-4d5e-9f6a-7b8c9d0e1f2a"
}
}
],
"rejected": 1,
"request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
}
{ "dry_run": true, "submitted": 1, "valid": 1 }
Create Sold Products (v2)
Create up to 1,000 sold products with their sales per period and country, or add periods to existing ones
POST
/
v2
/
sold-products
curl -X POST "https://api.dcycle.io/v2/sold-products" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Idempotency-Key: 4d3c2b1a-0f9e-4d8c-9b7a-6f5e4d3c2b1a" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"name": "Industrial Compressor X200",
"client_row_id": "SKU-X200",
"periods": [
{
"start_date": "2025-01-01",
"end_date": "2025-12-31",
"unit_id": "bf800281-bcc7-42f4-81e4-8ec9abb9745e",
"sales": [
{ "country": "ES", "quantity": 250 },
{ "country": "DE", "quantity": 180 },
{ "country": "RoW", "quantity": 40 }
]
}
]
}
]
}'
import os
import uuid
import requests
UNITS = "bf800281-bcc7-42f4-81e4-8ec9abb9745e" # units (see "Units")
response = requests.post(
"https://api.dcycle.io/v2/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": [
{
"name": "Industrial Compressor X200",
"client_row_id": "SKU-X200",
"periods": [
{
"start_date": "2025-01-01",
"end_date": "2025-12-31",
"unit_id": UNITS,
"sales": [
{"country": "ES", "quantity": 250},
{"country": "DE", "quantity": 180},
{"country": "RoW", "quantity": 40},
],
}
],
}
]
},
timeout=60,
)
response.raise_for_status()
print([item["sold_product"]["id"] for item in response.json()["items"]])
{
"items": [
{
"source_row_index": 0,
"client_row_id": "SKU-X200",
"sold_product": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"organization_id": "a8315ef3-dd50-43f8-b7ce-d839e68d51fa",
"name": "Industrial Compressor X200",
"created_at": "2026-10-21T09:12:44.031Z",
"periods": [
{
"id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"start_date": "2025-01-01",
"end_date": "2025-12-31",
"unit_id": "bf800281-bcc7-42f4-81e4-8ec9abb9745e",
"sales": [
{ "id": "6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d", "country": "ES", "quantity": 250.0 },
{ "id": "7b8c9d0e-1f2a-4b3c-9d4e-5f6a7b8c9d0e", "country": "DE", "quantity": 180.0 },
{ "id": "8c9d0e1f-2a3b-4c4d-8e5f-6a7b8c9d0e1f", "country": "RoW", "quantity": 40.0 }
]
}
]
}
}
]
}
{
"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",
"error_code": "PERIOD_OVERLAPS",
"error_params": {
"field": "periods[0]",
"period_id": "9d0e1f2a-3b4c-4d5e-9f6a-7b8c9d0e1f2a"
}
}
],
"rejected": 1,
"request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
}
{ "dry_run": true, "submitted": 1, "valid": 1 }
← Sold Products API
Send the products your organization sells with their sales: one or more periods, each with the quantity sold in each
country. Nothing is calculated, so the request is synchronous (see
how bulk ingestion works): it answers
Request-level errors:
201 Created with each product and the
periods the request wrote. It also records an ingest job, born completed, that
sends no webhook.
A product is identified by its name in the organization: a record whose name already exists adds its periods to
that product. The emissions of a sold product come from the data of its use
(use of sold products), not from this route.
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/sold-products" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Idempotency-Key: 4d3c2b1a-0f9e-4d8c-9b7a-6f5e4d3c2b1a" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"name": "Industrial Compressor X200",
"client_row_id": "SKU-X200",
"periods": [
{
"start_date": "2025-01-01",
"end_date": "2025-12-31",
"unit_id": "bf800281-bcc7-42f4-81e4-8ec9abb9745e",
"sales": [
{ "country": "ES", "quantity": 250 },
{ "country": "DE", "quantity": 180 },
{ "country": "RoW", "quantity": 40 }
]
}
]
}
]
}'
import os
import uuid
import requests
UNITS = "bf800281-bcc7-42f4-81e4-8ec9abb9745e" # units (see "Units")
response = requests.post(
"https://api.dcycle.io/v2/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": [
{
"name": "Industrial Compressor X200",
"client_row_id": "SKU-X200",
"periods": [
{
"start_date": "2025-01-01",
"end_date": "2025-12-31",
"unit_id": UNITS,
"sales": [
{"country": "ES", "quantity": 250},
{"country": "DE", "quantity": 180},
{"country": "RoW", "quantity": 40},
],
}
],
}
]
},
timeout=60,
)
response.raise_for_status()
print([item["sold_product"]["id"] for item in response.json()["items"]])
{
"items": [
{
"source_row_index": 0,
"client_row_id": "SKU-X200",
"sold_product": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"organization_id": "a8315ef3-dd50-43f8-b7ce-d839e68d51fa",
"name": "Industrial Compressor X200",
"created_at": "2026-10-21T09:12:44.031Z",
"periods": [
{
"id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"start_date": "2025-01-01",
"end_date": "2025-12-31",
"unit_id": "bf800281-bcc7-42f4-81e4-8ec9abb9745e",
"sales": [
{ "id": "6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d", "country": "ES", "quantity": 250.0 },
{ "id": "7b8c9d0e-1f2a-4b3c-9d4e-5f6a7b8c9d0e", "country": "DE", "quantity": 180.0 },
{ "id": "8c9d0e1f-2a3b-4c4d-8e5f-6a7b8c9d0e1f", "country": "RoW", "quantity": 40.0 }
]
}
]
}
}
]
}
{
"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",
"error_code": "PERIOD_OVERLAPS",
"error_params": {
"field": "periods[0]",
"period_id": "9d0e1f2a-3b4c-4d5e-9f6a-7b8c9d0e1f2a"
}
}
],
"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, the unit and countries of each period, and its periods against those the
product already has. 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). - The name identifies a product in the organization, case aside (
Compressor X200andcompressor x200are one product). A record whose name is new creates the product; a record whose name exists adds its periods to that product, and the answer gives the existing product’sidand storedname. Two records with the same name reject the second (DUPLICATE_PRODUCT): put all the periods of a product in one record. - A period cannot overlap another of its product, the dates of both included. Within a record that is a schema
error (
SCHEMA_INVALID); against a period the product already has, the same period included, it isPERIOD_OVERLAPS, with the id of that period. So send every country of a period in one request: a later request cannot add countries to it. To replace a period, delete it first with Delete Period, or delete the product if it is its only period. - Each country appears once in a period (
DUPLICATE_SALEotherwise). Unlike the file import, two quantities of the same country are never summed silently. - Everything is written in one transaction and returned, in the order sent.
source_row_indexis the position of the record inrecords, andclient_row_idechoes yours. Each product comes with the periods this request wrote, with their sales; the periods it already had are left out. - The products belong to the
x-organization-idorganization. A record cannot name another one, not even one of your perimeter: send each organization’s products with its ownx-organization-id.
Idempotency-Key.
Units
Theunit_id of a period is one of the units of a product (GET /v2/units?type=products, see
List Units). Any other is UNIT_NOT_SUPPORTED.
| Unit | unit_id |
|---|---|
| Units (pieces) | bf800281-bcc7-42f4-81e4-8ec9abb9745e |
| Kilograms (kg) | 61743a63-ff70-459c-9567-5eee8f7dfd5c |
| Cubic metres (m³) | 2a09be22-a6e2-4317-a680-94aaa1048765 |
| Tonnes (t) | cab66828-c2b1-431b-92af-f9ab37149d3c |
Request
Headers
string
required
Your API key (see Authentication).
string
required
The organization the products belong to.
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 answers the same products (with
Idempotent-Replayed: true) instead of
writing them 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; 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 products to create or add periods to, 1 to 1,000. A field not listed here is rejected (
SCHEMA_INVALID).Show record
Show record
string
required
The product’s name, 1 to 255 characters: its identity in the organization, case aside. An existing name adds
the periods to that product.
array[object]
required
The periods of sales, 1 to 20, none overlapping another.
Show period
Show period
date
required
First day of the period,
YYYY-MM-DD, from 1970 to next year.date
required
Last day of the period, on or after
start_date, from 1970 to next year.uuid
required
The unit of every quantity of the period: one of the units of a product.
array[object]
required
The quantity sold in each country during the period, 1 to 300, each country once.
string
Your own reference for the record, up to 255 characters, echoed in errors and in the answer.
Response
201 Created with items: one per record, in the order sent, with its source_row_index, client_row_id and its
sold_product:
id: the product’s id, new or existing, the one Delete Sold Products (v2) takes and theproduct_idof List Sold Products.organization_id,name(as stored: an existing product keeps its own spelling),created_at(when the product was created, earlier for an existing one).periods: the periods this request wrote, bystart_date, each with itsid,start_date,end_date,unit_idandsales(id,country,quantity).
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 name or periods, no sales, more than 20 periods or 300 sales, a quantity not over 0, a country that is neither two letters nor RoW, a date out of range, end_date before start_date, two periods of the record overlapping, an unknown field (e.g. organization_id)… error_params.errors says where. |
DUPLICATE_PRODUCT | Another record of the request has the same name, case aside (error_params.field is name). Put all its periods in one record. |
PERIOD_OVERLAPS | The period overlaps one the product already has, or is the same one. error_params.field is periods[i] and error_params.period_id the existing period. Send all the countries of a period in one request. |
DUPLICATE_SALE | A country appears twice in the same period (error_params.field is periods[i].sales[j].country). |
REFERENCE_NOT_FOUND | Dcycle does not know the country (error_params.field is periods[i].sales[j].country). |
UNIT_NOT_SUPPORTED | unit_id is not one of the units of a product (error_params.field is periods[i].unit_id). |
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 1,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 1,000 |
| Periods per record | 1 to 20 |
| Countries per period | 1 to 300 |
Related Endpoints
Delete Sold Products (v2)
Delete products by id
List Sold Products
The products stored
List Periods
All the periods of a product
List Ingest Jobs
The job each request records
Was this page helpful?