curl -X POST "https://api.dcycle.io/v2/wastes" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: c41d8e77-5d40-4a11-9e8b-2c9f4d1e7a03" \
-d '{
"records": [
{
"client_row_id": "ALB-2026-09-0001",
"facility_id": "660e8400-e29b-41d4-a716-446655440000",
"identification_name": "ALB-2026-09-0001",
"start_date": "2026-09-01",
"end_date": "2026-09-30",
"base_quantity": 1250.5,
"unit_id": "61743a63-ff70-459c-9567-5eee8f7dfd5c",
"waste_ler_code_id": "33d3e986-2e02-4aaa-8be0-708c143c02d9"
}
]
}'
import os
import uuid
import requests
response = requests.post(
"https://api.dcycle.io/v2/wastes",
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": [
{
"client_row_id": "ALB-2026-09-0001",
"facility_id": "660e8400-e29b-41d4-a716-446655440000",
"identification_name": "ALB-2026-09-0001",
"start_date": "2026-09-01",
"end_date": "2026-09-30",
"base_quantity": 1250.5,
"unit_id": "61743a63-ff70-459c-9567-5eee8f7dfd5c",
"waste_ler_code_id": "33d3e986-2e02-4aaa-8be0-708c143c02d9",
}
]
},
timeout=60,
)
print(response.status_code, response.headers["Location"])
const axios = require('axios');
const { randomUUID } = require('crypto');
const response = await axios.post('https://api.dcycle.io/v2/wastes', {
records: [
{
client_row_id: 'ALB-2026-09-0001',
facility_id: '660e8400-e29b-41d4-a716-446655440000',
identification_name: 'ALB-2026-09-0001',
start_date: '2026-09-01',
end_date: '2026-09-30',
base_quantity: 1250.5,
unit_id: '61743a63-ff70-459c-9567-5eee8f7dfd5c',
waste_ler_code_id: '33d3e986-2e02-4aaa-8be0-708c143c02d9',
},
],
}, {
headers: {
'x-api-key': process.env.DCYCLE_API_KEY,
'x-organization-id': process.env.DCYCLE_ORG_ID,
'Idempotency-Key': randomUUID(), // keep it to retry this same batch
},
});
console.log(response.status, response.headers.location);
{
"id": "2d4b90c1-7c1e-4b6f-9a55-3f2b8e61d0aa",
"status": "processing",
"entity_type": "wastes",
"operation": "create",
"source": "api_bulk",
"counts": { "submitted": 1, "succeeded": 0, "failed": 0 },
"chunks_total": 1,
"chunks_done": 0,
"created_at": "2026-09-29T10:15:02.184Z",
"finished_at": null,
"links": {
"self": "/v2/ingest-jobs/2d4b90c1-7c1e-4b6f-9a55-3f2b8e61d0aa",
"items": "/v2/ingest-jobs/2d4b90c1-7c1e-4b6f-9a55-3f2b8e61d0aa/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": "ALB-2026-09-0001",
"error_code": "REFERENCE_NOT_FOUND",
"error_params": { "field": "waste_ler_code_id", "value": "0f0e0d0c-0000-4000-8000-000000000000" }
}
],
"rejected": 1,
"request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
}
{
"dry_run": true,
"submitted": 1,
"valid": 1
}
Create Wastes (v2)
Create one or up to 5,000 wastes in one request; emissions are calculated asynchronously and tracked through an ingest job
POST
/
v2
/
wastes
curl -X POST "https://api.dcycle.io/v2/wastes" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: c41d8e77-5d40-4a11-9e8b-2c9f4d1e7a03" \
-d '{
"records": [
{
"client_row_id": "ALB-2026-09-0001",
"facility_id": "660e8400-e29b-41d4-a716-446655440000",
"identification_name": "ALB-2026-09-0001",
"start_date": "2026-09-01",
"end_date": "2026-09-30",
"base_quantity": 1250.5,
"unit_id": "61743a63-ff70-459c-9567-5eee8f7dfd5c",
"waste_ler_code_id": "33d3e986-2e02-4aaa-8be0-708c143c02d9"
}
]
}'
import os
import uuid
import requests
response = requests.post(
"https://api.dcycle.io/v2/wastes",
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": [
{
"client_row_id": "ALB-2026-09-0001",
"facility_id": "660e8400-e29b-41d4-a716-446655440000",
"identification_name": "ALB-2026-09-0001",
"start_date": "2026-09-01",
"end_date": "2026-09-30",
"base_quantity": 1250.5,
"unit_id": "61743a63-ff70-459c-9567-5eee8f7dfd5c",
"waste_ler_code_id": "33d3e986-2e02-4aaa-8be0-708c143c02d9",
}
]
},
timeout=60,
)
print(response.status_code, response.headers["Location"])
const axios = require('axios');
const { randomUUID } = require('crypto');
const response = await axios.post('https://api.dcycle.io/v2/wastes', {
records: [
{
client_row_id: 'ALB-2026-09-0001',
facility_id: '660e8400-e29b-41d4-a716-446655440000',
identification_name: 'ALB-2026-09-0001',
start_date: '2026-09-01',
end_date: '2026-09-30',
base_quantity: 1250.5,
unit_id: '61743a63-ff70-459c-9567-5eee8f7dfd5c',
waste_ler_code_id: '33d3e986-2e02-4aaa-8be0-708c143c02d9',
},
],
}, {
headers: {
'x-api-key': process.env.DCYCLE_API_KEY,
'x-organization-id': process.env.DCYCLE_ORG_ID,
'Idempotency-Key': randomUUID(), // keep it to retry this same batch
},
});
console.log(response.status, response.headers.location);
{
"id": "2d4b90c1-7c1e-4b6f-9a55-3f2b8e61d0aa",
"status": "processing",
"entity_type": "wastes",
"operation": "create",
"source": "api_bulk",
"counts": { "submitted": 1, "succeeded": 0, "failed": 0 },
"chunks_total": 1,
"chunks_done": 0,
"created_at": "2026-09-29T10:15:02.184Z",
"finished_at": null,
"links": {
"self": "/v2/ingest-jobs/2d4b90c1-7c1e-4b6f-9a55-3f2b8e61d0aa",
"items": "/v2/ingest-jobs/2d4b90c1-7c1e-4b6f-9a55-3f2b8e61d0aa/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": "ALB-2026-09-0001",
"error_code": "REFERENCE_NOT_FOUND",
"error_params": { "field": "waste_ler_code_id", "value": "0f0e0d0c-0000-4000-8000-000000000000" }
}
],
"rejected": 1,
"request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
}
{
"dry_run": true,
"submitted": 1,
"valid": 1
}
Create wastes: one, or up to 5,000 in a single request. The records are validated and stored at once, and their CO2e
emissions are calculated asynchronously by a background worker. The response is an ingest job, not the
wastes themselves: you poll the job to follow the calculation and read per-record outcomes.
While a job is
This creates two wastes (60 and 40 units) sharing one
Beta. This endpoint is in beta: the contract may still change before general availability. Pin your
integration to the fields documented here and handle unknown fields in responses gracefully. Feedback is welcome
through your Dcycle contact.
curl -X POST "https://api.dcycle.io/v2/wastes" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: c41d8e77-5d40-4a11-9e8b-2c9f4d1e7a03" \
-d '{
"records": [
{
"client_row_id": "ALB-2026-09-0001",
"facility_id": "660e8400-e29b-41d4-a716-446655440000",
"identification_name": "ALB-2026-09-0001",
"start_date": "2026-09-01",
"end_date": "2026-09-30",
"base_quantity": 1250.5,
"unit_id": "61743a63-ff70-459c-9567-5eee8f7dfd5c",
"waste_ler_code_id": "33d3e986-2e02-4aaa-8be0-708c143c02d9"
}
]
}'
import os
import uuid
import requests
response = requests.post(
"https://api.dcycle.io/v2/wastes",
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": [
{
"client_row_id": "ALB-2026-09-0001",
"facility_id": "660e8400-e29b-41d4-a716-446655440000",
"identification_name": "ALB-2026-09-0001",
"start_date": "2026-09-01",
"end_date": "2026-09-30",
"base_quantity": 1250.5,
"unit_id": "61743a63-ff70-459c-9567-5eee8f7dfd5c",
"waste_ler_code_id": "33d3e986-2e02-4aaa-8be0-708c143c02d9",
}
]
},
timeout=60,
)
print(response.status_code, response.headers["Location"])
const axios = require('axios');
const { randomUUID } = require('crypto');
const response = await axios.post('https://api.dcycle.io/v2/wastes', {
records: [
{
client_row_id: 'ALB-2026-09-0001',
facility_id: '660e8400-e29b-41d4-a716-446655440000',
identification_name: 'ALB-2026-09-0001',
start_date: '2026-09-01',
end_date: '2026-09-30',
base_quantity: 1250.5,
unit_id: '61743a63-ff70-459c-9567-5eee8f7dfd5c',
waste_ler_code_id: '33d3e986-2e02-4aaa-8be0-708c143c02d9',
},
],
}, {
headers: {
'x-api-key': process.env.DCYCLE_API_KEY,
'x-organization-id': process.env.DCYCLE_ORG_ID,
'Idempotency-Key': randomUUID(), // keep it to retry this same batch
},
});
console.log(response.status, response.headers.location);
{
"id": "2d4b90c1-7c1e-4b6f-9a55-3f2b8e61d0aa",
"status": "processing",
"entity_type": "wastes",
"operation": "create",
"source": "api_bulk",
"counts": { "submitted": 1, "succeeded": 0, "failed": 0 },
"chunks_total": 1,
"chunks_done": 0,
"created_at": "2026-09-29T10:15:02.184Z",
"finished_at": null,
"links": {
"self": "/v2/ingest-jobs/2d4b90c1-7c1e-4b6f-9a55-3f2b8e61d0aa",
"items": "/v2/ingest-jobs/2d4b90c1-7c1e-4b6f-9a55-3f2b8e61d0aa/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": "ALB-2026-09-0001",
"error_code": "REFERENCE_NOT_FOUND",
"error_params": { "field": "waste_ler_code_id", "value": "0f0e0d0c-0000-4000-8000-000000000000" }
}
],
"rejected": 1,
"request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
}
{
"dry_run": true,
"submitted": 1,
"valid": 1
}
What is a waste
A waste is a quantity of residue that one of your facilities generates and hands over to a manager, who transports it to a treatment plant. Both steps emit greenhouse gases:- Treatment: landfilling, incineration, recycling, composting… Each treatment of each type of waste has its own emission factor.
- Transport to the waste center: calculated from
total_km_to_waste_center.
| What | Field | Notes |
|---|---|---|
| Where it was generated | facility_id (or facility_percentages to split it across facilities) | A facility of your organization or of an accepted subsidiary. |
| When | start_date, end_date | The period the waste belongs to. |
| How much | base_quantity, unit_id | E.g. 1,250.5 kg. |
| What type of waste | waste_ler_code_id | The LER code (European List of Waste, Commission Decision 2000/532/EC): six digits with no spaces, e.g. 150106 mixed packaging; a trailing asterisk marks hazardous waste (130205*). |
| How it is treated | waste_rd_code_id | The R/D code: a recovery (R…) or disposal (D…) operation from Annexes I and II of the Waste Framework Directive 2008/98/EC, plus R14 (preparation for reuse), written with two digits as the catalog returns it. E.g. R03 recycling of organic substances, D01 landfill. List R/D Codes has the ones Dcycle accepts. |
| Who manages it | provider_name, transporter_name, destination | The waste manager, the carrier and the treatment plant. |
How the emissions are calculated
Emissions are the quantity multiplied by an emission factor. What you send determines how Dcycle finds that factor. Only when you bring your own factor do you choose it.- LER code (and R/D code when you have it): the standard route. The codes classify the waste in a common terminology, and Dcycle looks up the factor mapped to that LER + R/D pair in its emission factor databases: DEFRA by default, OCCC when DEFRA does not cover the code. Without an R/D code, only a factor defined for that LER with no treatment can be used, and not every LER has one, so send the R/D code whenever you have it.
- No codes, only a
description: Dcycle compares the description with the factors of its maintained emission factor databases and selects the closest one for each year of the waste period. No LER or R/D code is assigned to the record. A low-confidence match is still applied, but the waste is flagged for review. Use it when your source data has no LER code. - Your own factor,
custom_ef_record_id: the only route where you choose the factor. The id of a custom emission factor record you created beforehand in a custom emission factor database of your organization, withwastesamong its activity categories. Use it when you have a factor measured or provided by your waste manager. No LER code is needed; if you send one as well, your factor takes precedence. See Create Custom Record.
CALCULATION_FAILED in
the job’s items.
Sent with a description? The match can leave the waste waiting for a person:
GET /v1/wastes/{waste_id}/detail shows status: "review" and
LOW_CONFIDENCE_WASTE_FACTOR_MATCH in error_messages. The job item tells the two cases apart: succeeded when a
factor was applied with low confidence (the waste has emissions, someone should confirm the factor), failed with
CALCULATION_FAILED when no factor could be applied (no emissions until someone picks one). The job does not count
these records separately yet.Find the ids you send
A record does not carry your own codes: every reference is a Dcycle id (UUID). Look them up once and keep the mapping on your side; the catalogs rarely change. The ids of the LER, R/D and unit catalogs are the same in every Dcycle environment, so the ones in these examples work as they are.| Field | Where to find it |
|---|---|
facility_id | GET /v1/facilities: the facilities of your organization; add consolidate_group=true to include those of its subsidiaries. |
unit_id | Kilograms: 61743a63-ff70-459c-9567-5eee8f7dfd5c, the only unit GET /v2/units?type=wastes lists. A waste priced with a custom_ef_record_id may also use that record’s unit. |
waste_ler_code_id | GET /v1/wastes/ler-codes?search=150106: the id of the LER code. The catalog is the same for every organization. |
waste_rd_code_id | GET /v1/wastes/rd-codes?ler_code_id=…: scoped to the LER code, it lists only the treatments that have an emission factor for it. |
custom_ef_record_id | List Custom Records, only when you bring your own factor. |
How it works
- You send a list of records (
records), one object per waste. - All or nothing: every record is validated. If any record is invalid, the whole request is rejected with
422 BULK_RECORDS_REJECTED, the response lists every invalid record, and nothing is written. Fix them and resend the full batch. - If every record is valid, all wastes are written in one transaction and the API answers
202 Acceptedwith the ingest job and aLocationheader pointing at it. - A worker calculates the emissions in chunks of 100 records. Poll
GET /v2/ingest-jobs/{job_id}untilstatusiscompletedorcompleted_with_errors, then read the per-record outcomes atGET /v2/ingest-jobs/{job_id}/items.
One waste or many
The same request creates one waste or a whole batch. To create a single waste, sendrecords with one element; it
goes through the same validation, the same job and the same calculation as a batch. For more than 5,000 records,
split them into several requests (see Limits).
Request
Headers
string
required
Your API key for authenticationExample:
sk_live_1234567890abcdefstring
required
UUID of the organization the wastes are created for. Facilities of its accepted, enabled subsidiaries are also
valid destinations.Example:
a8315ef3-dd50-43f8-b7ce-d839e68d51fastring
required
Must be
application/jsonstring
required
1 to 255 characters. Send a unique value per batch (a UUID works well). If a request with the same key and the same
body was already accepted for this organization, the API returns that same job instead of creating the wastes
again, so a timeout or network error can be retried safely. The same key with a different body is rejected with
422 IDEMPOTENCY_KEY_REUSED: use a new key for a new batch.Example: c41d8e77-5d40-4a11-9e8b-2c9f4d1e7a03Query Parameters
boolean
default:"false"
Validate only. Every check runs and an invalid batch answers the same
422, but nothing is written and no job
is created: a valid batch answers 200 with {"dry_run": true, "submitted": n, "valid": n}. Use it to test an
integration without creating data. The Idempotency-Key header is still required, but a dry run does not record
it, so you can send the real request with the same key afterwards.Body Parameters
array[object]
required
The wastes to create: between 1 and 5,000 objects. Each record has the fields below.
Show record fields
Show record fields
uuid
required
UUID of the facility the waste belongs to. Must belong to your organization or to one of its accepted, enabled
subsidiaries. Retrieve options from
GET /v1/facilities.Example: "660e8400-e29b-41d4-a716-446655440000"string
required
Waste identification name or invoice/delivery-note number.Example:
"ALB-2026-09-0001"date
required
Start of the waste period (ISO 8601), a year from 1970 to next year.Example:
"2026-09-01"date
required
End of the waste period (ISO 8601), a year from 1970 to next year. Must be on or after
start_date.Example: "2026-09-30"number
required
Quantity of waste in
unit_id. Must be greater than 0.Example: 1250.5uuid
required
Kilograms:
61743a63-ff70-459c-9567-5eee8f7dfd5c, the unit GET /v2/units?type=wastes lists. Send the quantity
in kilograms (convert tonnes ×1,000). A waste priced with a custom_ef_record_id may instead use the unit of
that record, the one its factor is per. A record without a unit is rejected with SCHEMA_INVALID, and one with
any other unit with UNIT_NOT_SUPPORTED.Example: "61743a63-ff70-459c-9567-5eee8f7dfd5c"uuid
UUID of the LER (European List of Waste) code, taken from
GET /v1/wastes/ler-codes. Required unless you send a custom_ef_record_id,
or a non-empty description (then the description is matched to a waste type automatically before the
calculation).Example: "33d3e986-2e02-4aaa-8be0-708c143c02d9" (150106, mixed packaging)uuid
UUID of the R/D (recovery/disposal) treatment code, taken from
GET /v1/wastes/rd-codes?ler_code_id=…, which lists the treatments with an
emission factor for that LER code. Optional, but send it when you know the treatment: without it only a factor
defined for the LER code alone can be used.Example: "6617caab-9db0-40df-8716-54d8c94e8056" (R03)string
default:""
Description of the waste. Required (non-empty) when
waste_ler_code_id is omitted.Example: "Mezcla de envases ligeros"string
default:""
Waste destination or treatment facility.Example:
"Centro de tratamiento Ecoveza"string | null
Business name of the waste manager that treats the waste.Example:
"Ecoveza S.L."string | null
Business name of the carrier that takes the waste to the treatment plant, when it is not the waste manager.Example:
"Transportes Levante S.L."number
default:"0"
Distance to the waste center in km, used to calculate collection transport emissions. Must be 0 or greater.Example:
42array[object] | null
Split one record across several facilities (“divide consumptions”). With more than one entry, one waste is
created per facility, each with
quantity = base_quantity × percentage. Facilities must be unique, all inside
your perimeter, and the percentages must sum to at most 1. With one entry or none, the waste goes entirely to
facility_id.uuid | null
UUID of one of your custom emission factor records, to
calculate this waste with your own factor instead of the standard databases. The record must be visible to the organization that owns the destination facility,
enabled, and configured for the
wastes activity category. With it, waste_ler_code_id is optional. In a
split (facility_percentages), every resulting waste uses the same factor.Example: "c3d4e5f6-a7b8-9012-cdef-ab3456789012"string | null
URL of a supporting document (e.g. a waste manifest).
string | null
Your own identifier for the record, up to 255 characters. It is echoed on every outcome for this record (in
rejections and in the job’s items), so you can correlate results without tracking array positions.Example:
"ALB-2026-09-0001"Response
202 Accepted
Every record passed validation and was stored. The body is the ingest job; theLocation header holds its path.
HTTP/1.1 202 Accepted
Location: /v2/ingest-jobs/2d4b90c1-7c1e-4b6f-9a55-3f2b8e61d0aa
{
"id": "2d4b90c1-7c1e-4b6f-9a55-3f2b8e61d0aa",
"status": "processing",
"entity_type": "wastes",
"operation": "create",
"source": "api_bulk",
"counts": { "submitted": 250, "succeeded": 0, "failed": 0 },
"chunks_total": 3,
"chunks_done": 0,
"created_at": "2026-09-29T10:15:02.184Z",
"finished_at": null,
"links": {
"self": "/v2/ingest-jobs/2d4b90c1-7c1e-4b6f-9a55-3f2b8e61d0aa",
"items": "/v2/ingest-jobs/2d4b90c1-7c1e-4b6f-9a55-3f2b8e61d0aa/items"
}
}
uuid
Ingest job id. Use it with
GET /v2/ingest-jobs/{job_id}.string
processing right after the request. See Job status.string
Always
wastes for this endpoint.string
Always
create.string
api_bulk when sent with an API key; app when sent by a person signed in to Dcycle (e.g. the app’s waste form).object
integer
Number of calculation chunks (100 records each).
integer
Chunks already calculated.
chunks_done / chunks_total is the job’s progress.datetime
When the job was created (UTC).
datetime | null
When the last chunk finished (UTC),
null while the job is processing.object
self: the job. items: its per-record outcomes.Job status
status | Meaning |
|---|---|
processing | Wastes are stored; emissions are being calculated. |
completed | Every record was calculated. |
completed_with_errors | Every chunk ran, and at least one record failed to calculate (counts.failed > 0). The other records are calculated normally. |
processing, the new wastes already exist with co2e at 0; the value is filled in when their chunk
is calculated. Each record’s waste id is its entity_id in the job’s items.
If
chunks_done stops moving, see A job that does not finish:
never resend the batch with a new Idempotency-Key, because its wastes are already stored.Why a record failed to calculate
A failed item carrieserror_code: CALCULATION_FAILED but not the reason yet (error_params is null). Read the
waste with GET /v1/wastes/{waste_id}/detail:
status: "error": no emission factor covers its LER/R/D pair. A pair missing fromGET /v1/wastes/rd-codes?ler_code_id=…never has one; a listed pair usually does.status: "review": it came with adescriptionthat matched no factor. It has no emissions until someone picks the factor in Dcycle.
Errors
Errors follow RFC 9457 problem details (Content-Type: application/problem+json): a stable
code, a human detail, errors when there is a list of problems, and the request_id to quote to support.
422 BULK_RECORDS_REJECTED — invalid records
At least one record is invalid. Nothing was written.rejected is the number of invalid records and errors
lists them (up to 100) in submission order, each with its position in records (source_row_index, starting at 0),
your client_row_id and a stable error_code.
{
"type": "https://code.dcycle.io/api-reference/errors#BULK_RECORDS_REJECTED",
"title": "Unprocessable Entity",
"status": 422,
"code": "BULK_RECORDS_REJECTED",
"detail": "3 of the submitted records are invalid; nothing was written.",
"errors": [
{
"source_row_index": 1,
"client_row_id": "ALB-2026-09-0002",
"error_code": "FACILITY_NOT_BELONG_TO_ORGANIZATION",
"error_params": { "field": "facility_id", "value": "00000000-0000-0000-0000-00000000c0de" }
},
{
"source_row_index": 2,
"client_row_id": "ALB-2026-09-0003",
"error_code": "SCHEMA_INVALID",
"error_params": {
"errors": [
{ "loc": ["base_quantity"], "type": "greater_than", "msg": "Input should be greater than 0" }
]
}
},
{
"source_row_index": 4,
"client_row_id": "ALB-2026-09-0005",
"error_code": "UNIT_NOT_SUPPORTED",
"error_params": { "field": "unit_id", "value": "cab66828-c2b1-431b-92af-f9ab37149d3c", "supported": ["61743a63-ff70-459c-9567-5eee8f7dfd5c"] }
}
],
"rejected": 3,
"request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
}
error_code | Cause | Fix |
|---|---|---|
SCHEMA_INVALID | The record does not match the record schema: a missing required field (unit_id included), a wrong type, base_quantity ≤ 0, a date outside 1970 to next year (activity_date_out_of_range), end_date before start_date, or the record is not a JSON object. error_params.errors lists each problem with its field (loc). | Correct the listed fields. |
FACILITY_NOT_BELONG_TO_ORGANIZATION | facility_id (or a facility in facility_percentages) does not exist or is outside your organization’s perimeter. | Use a facility of your organization or of an accepted subsidiary. |
WASTE_LER_OR_DESCRIPTION_REQUIRED | None of waste_ler_code_id, custom_ef_record_id or a non-empty description was sent. | Send one of them. |
REFERENCE_NOT_FOUND | waste_ler_code_id or waste_rd_code_id does not exist. error_params.field names it. | Take the ids from LER codes and R/D codes. |
UNIT_NOT_SUPPORTED | unit_id is neither kilograms nor the unit of the record’s custom_ef_record_id. error_params.supported lists the accepted unit ids. | Send the quantity in kilograms with unit_id 61743a63-ff70-459c-9567-5eee8f7dfd5c. |
FACILITIES_MUST_BE_UNIQUE | A facility appears twice in facility_percentages. | List each facility once. |
FACILITY_PERCENTAGES_INVALID | The facility_percentages sum to more than 1. | Make them add up to 1 or less. |
MULTIDB_CUSTOM_RECORD_NOT_FOUND_OR_DISABLED | custom_ef_record_id does not exist, is disabled, or is not visible to the organization that owns the facility. | Use an enabled record of that organization (or shared with it). |
MULTIDB_CUSTOM_RECORD_INCOMPATIBLE_CATEGORY | The custom emission factor record is not configured for the wastes activity category. | Add wastes to the record’s activity categories. |
422 IDEMPOTENCY_KEY_REUSED
TheIdempotency-Key was already used in this organization with a different body. Nothing is written. Send a new
key for a new batch; to retry the original batch, resend exactly the same body (field order does not matter).
{
"type": "https://code.dcycle.io/api-reference/errors#IDEMPOTENCY_KEY_REUSED",
"title": "Unprocessable Entity",
"status": 422,
"code": "IDEMPOTENCY_KEY_REUSED",
"detail": "This Idempotency-Key was already used with a different request body. Use a new key per batch.",
"request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
}
422 REQUEST_VALIDATION_FAILED — malformed request
The request itself is invalid: theIdempotency-Key header is missing, or records is missing, empty or has more
than 5,000 entries. errors lists each problem with where it is (loc):
{
"type": "https://code.dcycle.io/api-reference/errors#REQUEST_VALIDATION_FAILED",
"title": "Unprocessable Entity",
"status": 422,
"code": "REQUEST_VALIDATION_FAILED",
"detail": "The request is invalid (1 error(s)); see `errors`.",
"errors": [{ "loc": ["header", "Idempotency-Key"], "msg": "field required", "type": "value_error.missing" }],
"request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
}
401 Unauthorized / 403 Forbidden
Missing or invalid API key (401), or the API key’s user is not a member of the organization in
x-organization-id (403 LOGGED_USER_NOT_MEMBER).
Examples
Create a batch and wait for the calculation
curl -i -X POST "https://api.dcycle.io/v2/wastes" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"records": [
{
"client_row_id": "ALB-2026-09-0001",
"facility_id": "660e8400-e29b-41d4-a716-446655440000",
"identification_name": "ALB-2026-09-0001",
"start_date": "2026-09-01",
"end_date": "2026-09-30",
"base_quantity": 1250.5,
"unit_id": "61743a63-ff70-459c-9567-5eee8f7dfd5c",
"waste_ler_code_id": "33d3e986-2e02-4aaa-8be0-708c143c02d9",
"waste_rd_code_id": "6617caab-9db0-40df-8716-54d8c94e8056",
"provider_name": "Ecoveza S.L.",
"total_km_to_waste_center": 42
},
{
"client_row_id": "ALB-2026-09-0002",
"facility_id": "660e8400-e29b-41d4-a716-446655440000",
"identification_name": "ALB-2026-09-0002",
"start_date": "2026-09-01",
"end_date": "2026-09-30",
"base_quantity": 380,
"unit_id": "61743a63-ff70-459c-9567-5eee8f7dfd5c",
"description": "Mezcla de envases ligeros"
}
]
}'
# Then poll the job from the Location header
curl "https://api.dcycle.io/v2/ingest-jobs/2d4b90c1-7c1e-4b6f-9a55-3f2b8e61d0aa" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}"
import os
import time
import uuid
import requests
BASE_URL = "https://api.dcycle.io"
HEADERS = {
"x-api-key": os.environ["DCYCLE_API_KEY"],
"x-organization-id": os.environ["DCYCLE_ORG_ID"],
}
records = [
{
"client_row_id": "ALB-2026-09-0001",
"facility_id": "660e8400-e29b-41d4-a716-446655440000",
"identification_name": "ALB-2026-09-0001",
"start_date": "2026-09-01",
"end_date": "2026-09-30",
"base_quantity": 1250.5,
"unit_id": "61743a63-ff70-459c-9567-5eee8f7dfd5c",
"waste_ler_code_id": "33d3e986-2e02-4aaa-8be0-708c143c02d9",
},
]
# One key per batch: reuse it only to retry this same batch.
idempotency_key = str(uuid.uuid4())
response = requests.post(
f"{BASE_URL}/v2/wastes",
headers={**HEADERS, "Idempotency-Key": idempotency_key},
json={"records": records},
timeout=60,
)
if response.status_code == 422 and response.json().get("code") == "BULK_RECORDS_REJECTED":
for rejection in response.json()["errors"]:
print(rejection["source_row_index"], rejection["client_row_id"], rejection["error_code"], rejection["error_params"])
raise SystemExit("Fix the listed records and resend the whole batch")
response.raise_for_status()
job_url = BASE_URL + response.headers["Location"]
while True:
job = requests.get(job_url, headers=HEADERS, timeout=30).json()
print(f"{job['status']}: {job['chunks_done']}/{job['chunks_total']} chunks")
if job["status"] != "processing":
break
time.sleep(10)
if job["status"] == "completed_with_errors":
failed = requests.get(
f"{job_url}/items", headers=HEADERS, params={"status": "failed", "size": 500}, timeout=30
).json()
for item in failed["items"]:
print("calculation failed:", item["client_row_id"], item["error_code"])
const axios = require('axios');
const { randomUUID } = require('crypto');
const api = axios.create({
baseURL: 'https://api.dcycle.io',
headers: {
'x-api-key': process.env.DCYCLE_API_KEY,
'x-organization-id': process.env.DCYCLE_ORG_ID,
},
});
async function createWastes(records) {
let response;
try {
response = await api.post('/v2/wastes', { records }, {
headers: { 'Idempotency-Key': randomUUID() },
});
} catch (error) {
const body = error.response && error.response.data;
if (body && body.code === 'BULK_RECORDS_REJECTED') {
body.errors.forEach(r =>
console.log(r.source_row_index, r.client_row_id, r.error_code, r.error_params));
}
throw error;
}
const jobPath = response.headers.location;
let job = response.data;
while (job.status === 'processing') {
await new Promise(resolve => setTimeout(resolve, 10000));
job = (await api.get(jobPath)).data;
console.log(`${job.status}: ${job.chunks_done}/${job.chunks_total} chunks`);
}
return job;
}
Split one waste across two facilities
{
"records": [
{
"client_row_id": "ALB-2026-09-0100",
"facility_id": "660e8400-e29b-41d4-a716-446655440000",
"identification_name": "ALB-2026-09-0100",
"start_date": "2026-09-01",
"end_date": "2026-09-30",
"base_quantity": 100,
"unit_id": "61743a63-ff70-459c-9567-5eee8f7dfd5c",
"waste_ler_code_id": "33d3e986-2e02-4aaa-8be0-708c143c02d9",
"facility_percentages": [
{ "facility_id": "660e8400-e29b-41d4-a716-446655440000", "percentage": 0.6 },
{ "facility_id": "770e8400-e29b-41d4-a716-446655440001", "percentage": 0.4 }
]
}
]
}
source_waste_id. The job has one item for the record,
whose entity_id is the first waste of the group.
Each share is a waste of its own: deleting that
entity_id removes only the
first share. If your integration may need to delete or replace the record later, send one record per facility
instead of facility_percentages, so every waste has its own item and id.Retries and idempotency
- Network error or timeout: retry with the same
Idempotency-Key. If the first request was accepted, you get the same job back and no wastes are duplicated. 422 BULK_RECORDS_REJECTED: nothing was stored. Fix the records and send the batch again (a new key is fine, since the first request created nothing).422 IDEMPOTENCY_KEY_REUSED: the key already belongs to another batch. Use a new key, or resend the original body unchanged.
Limits
| Limit | Value |
|---|---|
| Records per request | 1 to 5,000 |
Related Endpoints
Get Ingest Job
Status and progress of the batch
List Ingest Job Items
Per-record outcomes, including calculation failures
Was this page helpful?