curl -X POST "https://api.dcycle.io/v2/waste-water-treatments" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Idempotency-Key: 4b3a2918-7f6e-4d5c-9b8a-1e0d9c8b7a69" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"facility_id": "660e8400-e29b-41d4-a716-446655440000",
"name": "January 2026",
"start_date": "2026-01-01",
"end_date": "2026-01-31",
"m3_water_in": 125000,
"m3_water_out": 120000,
"kg_sludge": 48000,
"kg_bod_per_m3_wwt_line": 0.3,
"kg_bod_per_kg_sludge_line": 0.05,
"kg_bod_per_m3_wwd_line": 0.02,
"kg_n_per_m3_wwt_line": 0.04,
"kg_n_per_m3_wwd_line": 0.01,
"kg_r_wwt_line": 150,
"client_row_id": "EDAR-SUR-2026-01"
}
]
}'
import os
import uuid
import requests
response = requests.post(
"https://api.dcycle.io/v2/waste-water-treatments",
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": [
{
"facility_id": "660e8400-e29b-41d4-a716-446655440000",
"name": "February 2026",
"start_date": "2026-02-01",
"end_date": "2026-02-28",
"m3_water_in": 118000,
"m3_water_out": 114000,
"kg_sludge": 45500,
"kg_bod_per_m3_wwt_line": 0.29,
"kg_bod_per_kg_sludge_line": 0.05,
"kg_bod_per_m3_wwd_line": 0.02,
"kg_n_per_m3_wwt_line": 0.04,
"kg_n_per_m3_wwd_line": 0.01,
"client_row_id": "EDAR-SUR-2026-02",
}
]
},
timeout=60,
)
response.raise_for_status()
job = response.json()
print(job["id"], job["status"])
{
"id": "5d4c3b2a-1908-4f7e-8d6c-5b4a39281706",
"status": "processing",
"entity_type": "waste_water_treatments",
"operation": "create",
"source": "api_bulk",
"counts": { "submitted": 1, "succeeded": 0, "failed": 0 },
"chunks_total": 1,
"chunks_done": 0,
"created_at": "2026-10-21T09:12:44.031Z",
"finished_at": null,
"links": {
"self": "/v2/ingest-jobs/5d4c3b2a-1908-4f7e-8d6c-5b4a39281706",
"items": "/v2/ingest-jobs/5d4c3b2a-1908-4f7e-8d6c-5b4a39281706/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": "EDAR-SUR-2026-01",
"error_code": "FACILITY_NOT_WASTE_WATER",
"error_params": { "field": "facility_id", "value": "660e8400-e29b-41d4-a716-446655440000" }
}
],
"rejected": 1,
"request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
}
{ "dry_run": true, "submitted": 1, "valid": 1 }
Create Waste Water Treatments (v2)
Write up to 5,000 measurements of waste water treatment plants in one request; their emissions are calculated asynchronously
POST
/
v2
/
waste-water-treatments
curl -X POST "https://api.dcycle.io/v2/waste-water-treatments" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Idempotency-Key: 4b3a2918-7f6e-4d5c-9b8a-1e0d9c8b7a69" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"facility_id": "660e8400-e29b-41d4-a716-446655440000",
"name": "January 2026",
"start_date": "2026-01-01",
"end_date": "2026-01-31",
"m3_water_in": 125000,
"m3_water_out": 120000,
"kg_sludge": 48000,
"kg_bod_per_m3_wwt_line": 0.3,
"kg_bod_per_kg_sludge_line": 0.05,
"kg_bod_per_m3_wwd_line": 0.02,
"kg_n_per_m3_wwt_line": 0.04,
"kg_n_per_m3_wwd_line": 0.01,
"kg_r_wwt_line": 150,
"client_row_id": "EDAR-SUR-2026-01"
}
]
}'
import os
import uuid
import requests
response = requests.post(
"https://api.dcycle.io/v2/waste-water-treatments",
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": [
{
"facility_id": "660e8400-e29b-41d4-a716-446655440000",
"name": "February 2026",
"start_date": "2026-02-01",
"end_date": "2026-02-28",
"m3_water_in": 118000,
"m3_water_out": 114000,
"kg_sludge": 45500,
"kg_bod_per_m3_wwt_line": 0.29,
"kg_bod_per_kg_sludge_line": 0.05,
"kg_bod_per_m3_wwd_line": 0.02,
"kg_n_per_m3_wwt_line": 0.04,
"kg_n_per_m3_wwd_line": 0.01,
"client_row_id": "EDAR-SUR-2026-02",
}
]
},
timeout=60,
)
response.raise_for_status()
job = response.json()
print(job["id"], job["status"])
{
"id": "5d4c3b2a-1908-4f7e-8d6c-5b4a39281706",
"status": "processing",
"entity_type": "waste_water_treatments",
"operation": "create",
"source": "api_bulk",
"counts": { "submitted": 1, "succeeded": 0, "failed": 0 },
"chunks_total": 1,
"chunks_done": 0,
"created_at": "2026-10-21T09:12:44.031Z",
"finished_at": null,
"links": {
"self": "/v2/ingest-jobs/5d4c3b2a-1908-4f7e-8d6c-5b4a39281706",
"items": "/v2/ingest-jobs/5d4c3b2a-1908-4f7e-8d6c-5b4a39281706/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": "EDAR-SUR-2026-01",
"error_code": "FACILITY_NOT_WASTE_WATER",
"error_params": { "field": "facility_id", "value": "660e8400-e29b-41d4-a716-446655440000" }
}
],
"rejected": 1,
"request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
}
{ "dry_run": true, "submitted": 1, "valid": 1 }
← Waste Water Treatments
Write one or many measurements of waste water treatment plants (WWTP, EDAR). A measurement is what a plant measured
during a period, typically a month: the water in and out, the sludge, and the BOD and nitrogen of each treatment line.
Dcycle calculates its CH₄ and N₂O emissions in the background with the lines the plant was created with. The
measurements are the ones
Coming from
The record is the measurement v1 takes, with these differences:
Request-level errors:
GET /v1/waste-water-treatments reads.
The plant is a facility created with a waste_water block in
POST /v2/facilities, which sets its three
treatment lines.
The request checks every record and writes them all at once, then answers 202 Accepted with an
ingest job that calculates them (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.
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/waste-water-treatments" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Idempotency-Key: 4b3a2918-7f6e-4d5c-9b8a-1e0d9c8b7a69" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"facility_id": "660e8400-e29b-41d4-a716-446655440000",
"name": "January 2026",
"start_date": "2026-01-01",
"end_date": "2026-01-31",
"m3_water_in": 125000,
"m3_water_out": 120000,
"kg_sludge": 48000,
"kg_bod_per_m3_wwt_line": 0.3,
"kg_bod_per_kg_sludge_line": 0.05,
"kg_bod_per_m3_wwd_line": 0.02,
"kg_n_per_m3_wwt_line": 0.04,
"kg_n_per_m3_wwd_line": 0.01,
"kg_r_wwt_line": 150,
"client_row_id": "EDAR-SUR-2026-01"
}
]
}'
import os
import uuid
import requests
response = requests.post(
"https://api.dcycle.io/v2/waste-water-treatments",
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": [
{
"facility_id": "660e8400-e29b-41d4-a716-446655440000",
"name": "February 2026",
"start_date": "2026-02-01",
"end_date": "2026-02-28",
"m3_water_in": 118000,
"m3_water_out": 114000,
"kg_sludge": 45500,
"kg_bod_per_m3_wwt_line": 0.29,
"kg_bod_per_kg_sludge_line": 0.05,
"kg_bod_per_m3_wwd_line": 0.02,
"kg_n_per_m3_wwt_line": 0.04,
"kg_n_per_m3_wwd_line": 0.01,
"client_row_id": "EDAR-SUR-2026-02",
}
]
},
timeout=60,
)
response.raise_for_status()
job = response.json()
print(job["id"], job["status"])
{
"id": "5d4c3b2a-1908-4f7e-8d6c-5b4a39281706",
"status": "processing",
"entity_type": "waste_water_treatments",
"operation": "create",
"source": "api_bulk",
"counts": { "submitted": 1, "succeeded": 0, "failed": 0 },
"chunks_total": 1,
"chunks_done": 0,
"created_at": "2026-10-21T09:12:44.031Z",
"finished_at": null,
"links": {
"self": "/v2/ingest-jobs/5d4c3b2a-1908-4f7e-8d6c-5b4a39281706",
"items": "/v2/ingest-jobs/5d4c3b2a-1908-4f7e-8d6c-5b4a39281706/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": "EDAR-SUR-2026-01",
"error_code": "FACILITY_NOT_WASTE_WATER",
"error_params": { "field": "facility_id", "value": "660e8400-e29b-41d4-a716-446655440000" }
}
],
"rejected": 1,
"request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
}
{ "dry_run": true, "submitted": 1, "valid": 1 }
How it works
-
Every record is checked, its plant included:
facility_idmust be an enabled facility of your perimeter, a waste water treatment plant, with its three treatment lines set. If any record is invalid the whole request is rejected with422and nothing is written;rejectedis the number of invalid records anderrorslists them (up to 100). -
The measurements are written at once, with status
uploaded. Each belongs to its plant, so to the plant’s organization (thex-organization-idone or a subsidiary of it). Periods of a plant may overlap, as in v1 and the Data Importer: nothing is merged or replaced. -
The job calculates them in the background, in chunks, with the IPCC 2019 method and the emission factors of
the plant’s lines:
No line’s CH₄ goes below 0. The gases are converted to CO₂e with the IPCC GWPs and spread over the days of the period. The treatment and sludge lines are direct emissions of the plant; the discharge is reported apart, under other Scope 3 emissions (WWTP discharges). A measurement keeps the status
Line Gas From Emission categoryWater treatment ( wwt_line_id)CH₄ The BOD treated ( m3_water_in×kg_bod_per_m3_wwt_line) minus the BOD removed with the sludge (kg_sludge×kg_bod_per_kg_sludge_line), less the CH₄ recovered (kg_r_wwt_line)waste_water_treatment_waterWater treatment ( wwt_line_id)N₂O The nitrogen treated ( m3_water_in×kg_n_per_m3_wwt_line)waste_water_treatment_populationSludge ( sludge_line_id)CH₄ The sludge ( kg_sludge), less the CH₄ recovered (kg_r_sludge_line)waste_water_treatment_sludgeDischarge ( wwd_line_id)CH₄ and N₂O The BOD and nitrogen discharged ( m3_water_out×kg_bod_per_m3_wwd_lineand ×kg_n_per_m3_wwd_line)waste_water_treatment_dischargeuploadedwith itsco2e; one whose calculation fails endserror, without emissions, and its job item isfailedwithCALCULATION_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.
Coming from POST /v1/waste-water-treatments
The record is the measurement v1 takes, with these differences:
- Up to 5,000 measurements per request, all or nothing, with an
Idempotency-Key; the answer is a job, not the measurement. - A period cannot end in the future.
- A facility that is not a plant is a record code (
FACILITY_NOT_WASTE_WATER), not a400; a plant without its three lines is rejected too (WASTE_WATER_LINES_NOT_CONFIGURED), where v1 stores the measurement and it ends inerror. - There is no
file_url, nor any other field not listed below. kg_r_wwt_lineandkg_r_sludge_lineomitted are0.
Request
Headers
string
required
Your API key (see Authentication).
string
required
Your organization. The plants of its perimeter (its own and those of
the subsidiaries it may write to) can take measurements. The job and its
ingest_job.finished webhook belong to this
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 measurements 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 measurements to write, 1 to 5,000. A field not listed here is rejected (
SCHEMA_INVALID). Every number is 0 or
more (and under 10¹²).Show record
Show record
uuid
required
The plant: a facility created with
waste_water in
POST /v2/facilities (or with
POST /v1/facilities/waste_water). Find it with GET /v1/facilities.string
A label for the measurement, up to 255 characters. Example:
"January 2026"date
required
First day of the period,
YYYY-MM-DD, from 1970.date
required
Last day of the period, on or after
start_date and not in the future.number
required
Influent water volume, in m³.
number
required
Effluent (discharged) water volume, in m³.
number
required
Sludge produced, in kg.
number
required
BOD of the influent in the water treatment line, in kg BOD/m³.
number
required
BOD of the sludge in the sludge line, in kg BOD/kg sludge.
number
required
BOD of the effluent in the discharge line, in kg BOD/m³.
number
required
Nitrogen of the influent in the water treatment line, in kg N/m³.
number
required
Nitrogen of the effluent in the discharge line, in kg N/m³.
number
default:"0"
CH₄ recovered in the water treatment line, in kg.
number
default:"0"
CH₄ recovered in the sludge line, in kg.
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: waste_water_treatments, 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 measurement’s id. Afaileditem carries itserror_code.- List Waste Water Treatments (
?facility_id=the plant) returns the measurements with their status and emissions (co2e, kg CO₂e).
If
chunks_done stops moving, see A job that does not finish:
never resend the batch with a new Idempotency-Key, because its measurements are already stored.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: a missing or unknown field (e.g. file_url), a negative number, an end_date before start_date, a period that ends in the future, a year before 1970… error_params.errors says where. |
REFERENCE_NOT_FOUND | facility_id does not exist, is disabled or is outside your organization’s perimeter (all answer the same; error_params.field is facility_id). |
FACILITY_NOT_WASTE_WATER | facility_id is a facility, but not a waste water treatment plant. Create the plant with waste_water in POST /v2/facilities. |
WASTE_WATER_LINES_NOT_CONFIGURED | The plant has not its three treatment lines set, so its emissions could not be calculated. |
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 |
| Measurements per calculation chunk | 100 |
Related Endpoints
Delete Waste Water Treatments (v2)
Delete measurements by id
Create Facilities (v2)
Create the plant with its treatment lines
Get Ingest Job
Follow the calculation
List Waste Water Treatments
The measurements and their emissions
Was this page helpful?