WTNcloud REST API
Create waste transfer notes (WTNs) and Hazardous Waste Consignment Notes (HWCNs), and check their status programmatically. Integrate WTNcloud with Google Sheets, your own CRM, or any system that can make HTTP requests.
Authentication
All API requests require a Bearer token in the Authorization header. Your API key is available on the API Settings page in your dashboard (Enterprise plan only).
Authorization: Bearer YOUR_API_KEY
Keep your API key secret. Do not expose it in client-side code or public repositories.
Base URL
https://wtncloud.co.uk/api/external
/wtn/createCreate one or more waste transfer notes in bulk. Each WTN is filed under one of your saved customers — the one you name with customerId, or one matched from the name and address you send, saved for you if nothing matches (see “Matching notes to customers” below). Optionally assign to a driver by email.
Request Body
Send a JSON object with a wtns array (1–100 items).
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Required | Customer name |
| address | string | Required | Customer address as one line, ending in the postcode (e.g. 12 High Street, Bristol, BS1 2AB). The postcode is what matching and a newly saved customer take it from. |
| customerId | string | Optional | File the note under this saved customer instead of matching one: the customerId a previous note came back with. Must be one of your customers, or the whole batch is refused with 400 CUSTOMER_NOT_FOUND. name and address are still required — they are what this note records. See “Matching notes to customers” below. |
| string | Optional | Customer email | |
| phone | string | Optional | Customer phone number |
| driverEmail | string | Optional | Assign to a driver by their email address |
| dateOfTransfer | string | Optional | ISO date (e.g. 2026-04-15). Defaults to today |
| ewcCode | string | Optional | European Waste Catalogue code |
| wasteDescription | string | Optional | Description of the waste |
| quantity | number | Optional | Amount of waste |
| unit | string | Optional | Unit of measurement (kg, tonnes, litres, etc.). Omit it and the note records kg — fine on a draft, where the driver sets the real figure on site. Required on a note you sign through the API, and there it must be one we can weigh: tonnes, kg, litres or m3. Common spellings (tonne, t, kilograms, litre, l, cubic metres) are accepted and stored as the canonical value. See “Signatures” below. |
| containmentType | string | Optional | How the waste is contained (bags, skip, drums, etc.) |
| additionalWasteLines | array | Optional | Extra waste streams on the same transfer — the fields above are line 1. Omit (or send null) for a single-waste note. Per entry: ewcCode, wasteDescription, quantity, unit and containmentType are required; containmentOther, physicalForm (Gas | Liquid | Solid | Powder | Sludge | Mixed), numberOfContainers (default 1) and containsPops (default false) are optional. containsPops is answered PER LINE — the note-level POPs details apply to whichever lines declare it. Max 50 extra lines; hazardous EWC codes are rejected on every line, not just the first. |
| saveCustomer | boolean | Optional | When the note is filed under a saved customer, update that customer’s email and phone from this item. Their name and address are never changed. A customer that matches nothing is saved either way. |
| transferorSignature | string | Optional | The producer's signature as a PNG data URL (data:image/png;base64,…), max 1,500,000 decoded bytes — captured in your own software. (Decimal, not 1,572,864: size your export against 1,500,000 and you are safe either way.) The only signature image the API accepts; see “Signatures” below. Requires transferorSignerName, and requires the note to describe its waste (ewcCode, wasteDescription, quantity above zero, unit, containmentType). |
| transferorSignerName | string | Optional | Who signed for the producer. Required with transferorSignature, and rejected without it. |
| transferorSignerCompany | string | Optional | The producer's company, when the signatory signs for one. |
| applyCarrierSignature | boolean | Optional | Fill Section C from your account's saved carrier signature. Defaults to true when transferorSignature is supplied, false otherwise. Send false to leave Section C for the driver to sign at collection; send true on its own to have WTNcloud email the producer a signing link. |
Matching notes to customers
Every note is filed under one of your saved customers, so a customer’s notes stay together in the dashboard, the customer portal and your reports. For each item, the first of these that finds a customer wins:
- customerId — the saved customer with that id. An id that isn’t one of your customers refuses the whole batch with
400 CUSTOMER_NOT_FOUND(listing the ids incustomerIds) and creates nothing. - Name and address. The name ignoring case and punctuation (
Acme LtdandACME-LTD.are one name); the address ignoring case, spacing and punctuation, with or without the postcode on the end. So12 High Street, Bristol, BS1 2ABfinds a customer saved in the dashboard as12 High Street, Bristolwith the postcodeBS1 2AB. - Name and postcode, when your address line has a postcode in it. The same customer with the street spelled differently (
High St) is still the same customer. - Email, compared ignoring case.
- Nothing matched — a new customer is saved from the item: the postcode found at the end of the address line as their postcode, and the rest of the line as their address, the way a customer added in the dashboard is saved.
The API never changes a saved customer’s name or address. When a note is filed by customerId or by name and postcode, and the name or address you sent differs from the saved one, the note records what you sent — it prints on that note’s PDF and the driver is sent there — and the saved customer stays as it was. A note matched by email alone prints the saved customer’s details.
Every item in the response carries the customerId it was filed under. Keep it against the customer in your own system and send it with their next note: matching then has nothing to guess, and a change of address on your side can’t save them a second time.
Name and postcode are treated as one customer’s identity, the same rule the dashboard uses when it offers to save a one-off customer. Two different customers with the same name at the same postcode are rare, but if you have them, send customerId to keep them apart.
Signatures
You only ever send the producer’s signature. The carrier half of a note your account raises is your own signature, which WTNcloud already holds — the one you saved under Settings → Notes, and the one the dashboard’s “Apply My Signature” button uses. It is resolved server-side from your account, so the API neither needs nor accepts a carrier signature image: nobody can upload one on your behalf.
What an item carries decides where the note lands:
- Neither field —
DRAFTorASSIGNED, unsigned, for the driver to complete on site. This is the original behaviour and is unchanged. - transferorSignature — both halves signed,
COMPLETE. The PDF is rendered and the customer is sent their copy if you have auto-send on. - applyCarrierSignature: true on its own — carrier-signed and
AWAITING_TRANSFEROR_SIGNATURE; WTNcloud emails or texts the producer a signing link, and the note completes when they sign it.
Every signature is written to the note’s immutable audit trail with how it was captured: the image you push is recorded as API (drawn in your software, attested by your key), your carrier signature as SAVED_SIGNATURE.
Two guards worth knowing before you wire this up. A producer signature over a shell — no EWC code, no description, zero quantity, no unit — is refused with a 400: the unsigned path exists for shells, and a signed note has to say what was transferred. unit is in that list for a reason. Everywhere else an omitted unit falls back to kilograms and the driver corrects it on site; on a note the producer has already signed there is no on-site correction, so a bare 4 meant as tonnes would stand as 4 kg on a duty-of-care record. For the same reason a signed note’s unit has to be one we can weigh — tonnes, kg, litres or m3, common spellings accepted — because an unrecognised one is treated as kilograms downstream, which is the same mistake in a different hat. And asking for a carrier signature your account has never saved refuses the whole batch with CARRIER_SIGNATURE_NOT_SAVED rather than creating half-signed notes.
Example Request
curl -X POST https://wtncloud.co.uk/api/external/wtn/create \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"wtns": [
{
"name": "Acme Construction Ltd",
"address": "12 High Street, Bristol, BS1 2AB",
"email": "[email protected]",
"driverEmail": "[email protected]",
"ewcCode": "17 01 01",
"wasteDescription": "Concrete rubble",
"quantity": 500,
"unit": "kg",
"containmentType": "Skip",
"transferorSignature": "data:image/png;base64,iVBORw0KGgo...",
"transferorSignerName": "Jane Doe",
"transferorSignerCompany": "Acme Construction Ltd",
"additionalWasteLines": [
{
"ewcCode": "17 04 07",
"wasteDescription": "Mixed metals",
"quantity": 300,
"unit": "kg",
"containmentType": "Skip",
"physicalForm": "Solid",
"numberOfContainers": 1,
"containsPops": false
}
],
"saveCustomer": true
}
]
}'Response
{
"created": [
{
"wtnReference": "WTN-00042",
"wtnId": "a1b2c3d4e5f6...",
"driverEmail": "[email protected]",
"status": "COMPLETE",
"signatures": { "transferor": true, "carrier": true },
"pdf": "https://wtncloud.co.uk/api/external/wtn/a1b2c3d4e5f6/pdf",
"publicPdf": "https://wtncloud.co.uk/s/Ab3dEf7h",
"customerId": "cmtyitafh0003c5pbq5nhn900"
}
]
}On an unsigned item, status is "ASSIGNED" if a matching active driver was found, or "DRAFT" if no driver was specified or matched. Send signatures and it is "COMPLETE", "AWAITING_TRANSFEROR_SIGNATURE" or "AWAITING_TRANSFEREE_SIGNATURE" instead — the signatures object says which halves landed.
signatures, pdf, publicPdf and customerId are on every item, always — on an unsigned draft as much as a finished note, and on an idempotent replay of a batch created before any of them existed. You do not need to code defensively around their absence. driverEmail is the one field that comes and goes: it is echoed back only when you sent one.
pdf is the note’s permanent address, not a promise that a document is waiting there. The PDF is rendered once the note is signed, so the link answers 404 PDF_NOT_READY for a few seconds after a signed create, and for as long as an unsigned draft stays unsigned. See GET /wtn/{id}/pdf below.
publicPdf is the link to give your customer. It needs no API key and opens in any browser — it is the same short link WTNcloud texts a customer when a note completes. Until the PDF exists it shows a “not ready yet” page, and once the note is signed the same link opens the document, so you can send it straight after the create. Treat it like the document itself: anyone who has the link can read the note. It stops working while the note is deleted, and works again if the note is restored.
/wtn/statusCheck the status of your waste transfer notes. Query by WTN reference numbers or fetch all WTNs created since a given date.
Query Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| references | string | Optional | Comma-separated WTN references (e.g. WTN-00042,WTN-00043) |
| since | string | Optional | ISO date — returns WTNs created on or after this date (max 100 results) |
Provide either references or since, not both. Soft-deleted notes are excluded, the same as /hwcn/status: a note in your deleted items is not a record that stands, and its pdf link would not resolve.
Example Request
curl "https://wtncloud.co.uk/api/external/wtn/status?references=WTN-00042" \ -H "Authorization: Bearer YOUR_API_KEY"
Response
{
"wtns": [
{
"wtnReference": "WTN-00042",
"wtnId": "a1b2c3d4e5f6...",
"status": "COMPLETED",
"assignmentStatus": "COMPLETED",
"driverName": "John Smith",
"driverEmail": "[email protected]",
"pdfUrl": true,
"pdf": "https://wtncloud.co.uk/api/external/wtn/a1b2c3d4e5f6/pdf",
"publicPdf": "https://wtncloud.co.uk/s/Ab3dEf7h",
"createdAt": "2026-04-15T10:30:00.000Z",
"updatedAt": "2026-04-15T14:22:00.000Z"
}
]
}pdfUrl is a boolean, despite the name — whether a document has been rendered yet. The link is pdf, and it is the same address whether or not the render has happened. Poll pdfUrl and fetch when it turns true, or just fetch and honour Retry-After on a 404 PDF_NOT_READY. publicPdf is the no-key link to the same document, as on POST /wtn/create; a note created before public links existed is given one the first time you look it up here.
/wtn/{id}/pdfDownloads the PDF of one of your own transfer notes — the document itself (application/pdf), streamed back through the API. Every download re-checks the key asking for it, so this is for your own systems; to give someone the document without your key, hand them publicPdf instead. Takes the wtnId from a create or status response, and needs only the read scope every key already carries.
curl "https://wtncloud.co.uk/api/external/wtn/a1b2c3d4e5f6/pdf" \ -H "Authorization: Bearer YOUR_API_KEY" -o WTN-00042.pdf
A PDF is rendered once a note is signed, so a poll that beats the render gets 404 PDF_NOT_READY with a Retry-After header — retry rather than treating it as missing. An unsigned draft answers the same way until someone signs it. A load you logged from a paper note answers 404 NO_PDF_FOR_CAPTURE: nothing rendered one and nothing will, and the retained original is the record. A note belonging to another account is a plain 404, the same answer an id that doesn’t exist gets.
HWCN endpoints
Hazardous Waste Consignment Notes (HWCNs) for England (SI 2005/894) and Wales (WSI 2005/1806). The endpoints follow the same shape as the WTN ones above — Bearer-token auth, bulk envelope, ApiLog tracking — but differ in two important ways:
- Drafts are free.
POST /hwcn/createlands rows asDRAFTwith no charge. The free-WTN quota is only touched when a dashboard user (or driver app) signs Part D and completes the note. This matches the dashboard HWCN flow — see the FAQ on /tools/hwcn-generator. - Country-specific format checks. Each item is validated against the same Schedule 4 rules the dashboard form applies — England rejects premises codes (abolished 2016), Wales requires either a registered NRW premises code or the
EXEMPTsentinel, SIC 2003 only accepted on Welsh notes, etc. - Scotland and Northern Ireland aren't supported via the API today. Those use regulator-issued consignment codes (SEPA, NIEA) that the producer can't generate locally. A batch holding one is refused whole with
400 COUNTRY_NOT_SUPPORTEDand nothing in it is created: remove the items the response lists and resend.
/hwcn/createBulk-create HWCN drafts. Each item gets a unique producer-generated consignment code on creation; the row stays in DRAFT until an operator completes Part D from the dashboard.No billing on this endpoint.
Request Body
Send a JSON object with an hwcns array (1–100 items). Each item is the same Parts A–C shape the dashboard form collects, minus the signed declaration (which is added when the operator completes the draft).
| Field | Type | Required | Description |
|---|---|---|---|
| country | "england" | "wales" | Required | Country the waste is being collected from. Drives statute citations and code format. |
| consignor | object | Required | Your company. companyName / contactName / address / email / sicCode required; phone optional. Wales: walesPremisesKind ('registered' | 'exempt') and either a 6-char NRW premises code (AAAnnn) or 'EXEMPT'. |
| producer | object | Optional | Original waste producer if different from consignor. companyName / address required when present. |
| consignee | object | Required | Receiving facility. companyName / address / permitOrLicenceNumber required. Set operatesUnderExemption + exemptionNumber together if applicable. |
| multipleConsignments | object | Optional | Succession of multiple notes. Set isFirstInSuccession=true plus consignmentCount, frequency, durationMonths. |
| waste | object | Required | EWC code (must be hazardous, ends in *), HP codes (HP1–HP15 or POP, ≥1), processGivingRiseToWaste, description, physicalForm, quantityKg, containerType, containerCount. Optional ADR sub-section. |
| carrier | object | Required | Registered waste carrier details — companyName, address, registrationNumber (CB-prefixed), vehicleRegistration, collectionDate (ISO), carrierCertificateAccepted: true. |
| customerId | string | Optional | Link the consignment to this saved customer instead of matching one from the consignor: the customerId an earlier consignment or note came back with. Must be one of your customers, or the whole batch is refused with 400 CUSTOMER_NOT_FOUND. The consignor block is still what the consignment records. |
Example Request
curl -X POST https://wtncloud.co.uk/api/external/hwcn/create \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"hwcns": [
{
"country": "england",
"consignor": {
"companyName": "Acme Waste Ltd",
"contactName": "Jane Operator",
"address": { "line1": "1 High Street", "city": "London", "postcode": "SW1A 1AA" },
"email": "[email protected]",
"sicCode": "38120",
"sicVersion": "SIC_2007"
},
"consignee": {
"companyName": "Disposal Site PLC",
"address": { "line1": "10 Industrial Way", "city": "Reading", "postcode": "RG1 1AA" },
"permitOrLicenceNumber": "EPR/AB1234CD",
"operatesUnderExemption": false
},
"waste": {
"processGivingRiseToWaste": "Vehicle workshop oil change",
"description": "Used engine oil",
"ewcCode": "13 02 05*",
"physicalForm": "liquid",
"quantityKg": 200,
"hpCodes": ["HP3", "HP14"],
"containerType": "drum",
"containerCount": 1
},
"carrier": {
"companyName": "Hauliers Ltd",
"address": { "line1": "Yard 4", "city": "Slough", "postcode": "SL1 1AA" },
"registrationNumber": "CB/AB1234",
"vehicleRegistration": "AB12 CDE",
"collectionDate": "2026-04-30",
"carrierCertificateAccepted": true
}
}
]
}'Response
{
"created": [
{
"consignmentCode": "ACMEWA/AB23F",
"hwcnId": "a1b2c3d4e5f6...",
"status": "DRAFT",
"customerId": null,
"pdf": "https://wtncloud.co.uk/api/external/hwcn/a1b2c3d4e5f6/pdf",
"publicPdf": "https://wtncloud.co.uk/s/Hw4Cn5Xy"
}
]
}Code format. England returns XXXXXX/YYYYY (six chars from your company name, padded with Q; five-char suffix). Wales registered: AAAnnn/XXXXX. Wales exempt: EXE/XXXXXXXX. Codes use a confusable-free alphabet (no 0/O/1/I/L/5/S) so they read aloud cleanly.
customerId is the saved customer the consignment is linked to, so it shows in that customer’s history and portal. It is the one you named with customerId, or else one matched from the consignor the way /wtn/create matches a note (“Matching notes to customers” above): the company name with the address first, then the name with the postcode, then the email. The address is read as line 1, line 2, city and postcode, and finds a customer saved with or without the postcode, or saved with line 1 alone. It is null when nothing matches: this endpoint never saves a customer, and never changes one. The consignor block you send is what the consignment records, whichever customer it is linked to. Once a consignment or a note comes back with a customerId, send it with that consignor’s next consignment, and there is nothing to match.
pdf is where the consignment note will be downloadable from. Everything created here is a DRAFT and an HWCN is only rendered when Part D is signed, so expect 404 PDF_NOT_READY from it until that happens — see GET /hwcn/{id}/pdf below.
publicPdf is a link to the same document that needs no API key — the one to send the consignor, or anyone else who should have a copy. It shows a “not ready yet” page while the note is a draft and opens the PDF once Part D is signed, without the link changing. Anyone who has it can read the note; it stops working while the HWCN is deleted, and works again if it is restored.
/hwcn/statusLook up HWCNs by consignment code or fetch all HWCNs created since a given date. Soft-deleted rows are excluded.
Query Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| references | string | Optional | Comma-separated consignment codes (e.g. ACMEWA/AB23F,ABC123/0001A). |
| since | string | Optional | ISO date — returns HWCNs created on or after this date (max 100 results, newest first). |
Provide either references or since, not both.
Example Request
curl "https://wtncloud.co.uk/api/external/hwcn/status?references=ACMEWA%2FAB23F" \ -H "Authorization: Bearer YOUR_API_KEY"
Note the URL-encoded slash (%2F) inside the consignment code — every HWCN code contains a slash separator.
Response
{
"hwcns": [
{
"consignmentCode": "ACMEWA/AB23F",
"hwcnId": "a1b2c3d4e5f6...",
"jurisdiction": "england",
"status": "COMPLETED",
"pdfUrl": true,
"pdf": "https://wtncloud.co.uk/api/external/hwcn/a1b2c3d4e5f6/pdf",
"publicPdf": "https://wtncloud.co.uk/s/Hw4Cn5Xy",
"completedAt": "2026-04-30T10:15:00.000Z",
"createdAt": "2026-04-26T22:00:00.000Z",
"updatedAt": "2026-04-30T10:15:00.000Z"
}
]
}status is one of DRAFT, COMPLETED, CANCELLED.pdfUrl is a boolean indicating whether a PDF has been generated for this HWCN, and pdf is where to fetch it with your API key. publicPdf is the no-key link to the same document, as on POST /hwcn/create; an HWCN created before public links existed is given one the first time you look it up here.
/hwcn/{id}/pdfThe hazardous twin of /wtn/{id}/pdf: downloads the consignment note for one of your own HWCNs, using the hwcnId from a create or status response. read scope; the file comes back as application/pdf.
curl "https://wtncloud.co.uk/api/external/hwcn/a1b2c3d4e5f6/pdf" \ -H "Authorization: Bearer YOUR_API_KEY" -o consignment.pdf
An HWCN is rendered when Part D is signed, so a note still sitting as a DRAFT answers 404 PDF_NOT_READY — which is the normal state of anything POST /hwcn/create has just made. Poll /hwcn/status for status: "COMPLETED" rather than hammering this endpoint.
Receiver API
ENTERPRISEFor permitted receiving sites that want their own software to see what is arriving. One API key addresses exactly one permitted site — a multi-site operator runs one account and one key per site, which is what keeps DEFRA reporting unambiguous.
WTNcloud is the registered Digital Waste Tracking software supplier and files every receipt with DEFRA on your behalf, so your integration never needs its own DEFRA registration or credentials. These endpoints require the receiver:read and receiver:write scopes, which you switch on yourself under Settings → API in your dashboard. Your account also needs the receiver role and its site permit — both your own settings too, and the API names whichever is missing.
Two kinds of paperwork, two lists. Non-hazardous loads arrive on a waste transfer note and are resolved by accepting or rejecting them. Hazardous loads move under a consignment note (HWCN) and are resolved by signing Part E.
The two listing endpoints — /receiver/inbound and /receiver/movements — return each kind under its own key: inbound / movements for transfer notes, and consignments for hazardous. That is what lets an integration adopt the hazardous side when it is ready without its existing code changing behaviour. The write endpoints act on one record and return that record, not a list — a transfer note answers with wtnId, a consignment with hwcnId and its consignment code.
When a DEFRA receive is filed. A rejection never files one — no waste was received at your site. An accepted or partially accepted Part E always files. Accepting a transfer note files only when you give a recoveryDisposalCode; omit it and the receipt is logged without filing, which is the contract the dashboard has for a site not yet ready to declare a recovery or disposal route. Every response says which happened in dwt.filed.
/receiver/inboundTransfer notes carriers have fired to your site on arrival, and hazardous consignment notes addressed to it — the latter are raised by the producer and travel with the load rather than being fired. Defaults to whatever is still awaiting a decision: an accept or reject for a transfer note, a Part E for a consignment. Your to-do list.
| Field | Type | Required | Description |
|---|---|---|---|
| status | string | Optional | pending (default), resolved, or all. |
| since | string | Optional | ISO date. Filters inbound transfer notes by when they were FIRED to you, and consignments by when the note was RAISED — a consignment note is never fired, it is raised by the producer and arrives with the load. Both lists then read oldest-first so a backlog can be paged. |
| limit | number | Optional | Page size, default 50, maximum 100. |
Ordering: the pending worklist and any since query return oldest first, so you can work through a backlog larger than one page and advance a since cursor. Browsing resolved or all without a since returns newest first.
Example Request
curl "https://wtncloud.co.uk/api/external/receiver/inbound?status=pending" \ -H "Authorization: Bearer YOUR_API_KEY"
Response
{
"inbound": [
{
"wtnId": "a1b2c3d4e5f6...",
"wtnReference": "WTN-00042",
"firedByCompany": "Acme Haulage Ltd",
"carrierCompany": null,
"producerCompany": "Riverside Joinery",
"vehicleRegistration": "AB12 CDE",
"siteName": "Northside Transfer Station",
"firedAt": "2026-07-24T09:12:00.000Z",
"dateOfTransfer": "2026-07-24T00:00:00.000Z",
"batchId": null,
"resolution": "pending",
"receivedAt": null,
"waste": { "ewcCode": "17 09 04", "description": "Mixed C&D waste", "quantity": 12.5, "unit": "tonnes" },
"acceptedQuantity": null,
"additionalWasteLines": [],
"recoveryDisposalCode": null,
"hasPdf": true
}
],
"consignments": [
{
"hwcnId": "b2c3d4e5f6a1...",
"consignmentCode": "ACMEWA/AB23F",
"consignmentCodeMissingReason": null,
"jurisdiction": "england",
"raisedByCompany": "Acme Waste Ltd",
"consignorCompany": "Riverside Joinery",
"carrierCompany": "Hauliers Ltd",
"vehicleRegistration": "AB12 CDE",
"siteName": "Northside Transfer Station",
"createdAt": "2026-07-24T09:00:00.000Z",
"resolution": "pending",
"partE": "awaiting",
"receivedAt": null,
"acceptedQuantityKg": null,
"recoveryDisposalCode": null,
"containsPops": false,
"waste": {
"ewcCode": "13 02 05*",
"description": "Used engine oil",
"quantityKg": 200,
"physicalForm": "liquid",
"hpCodes": ["HP_3", "HP_14"],
"containerType": "drum",
"containerCount": 1,
"specialHandling": null
},
"additionalWasteLines": [],
"hasPdf": true
}
]
}A transfer note carrying several waste lines lists the ones after the first (which is waste) in additionalWasteLines, each with its ewcCode, description, declared quantity and unit, and the acceptedQuantity you recorded for that line once it is resolved (0 on a rejection, null while pending). The same list comes back on /receiver/movements.
consignments are hazardous loads awaiting your Part E. status filters them the same way (pending = Part E not yet signed), and since reads the note's creation — nobody "fires" a consignment note, the producer raises it and the load turns up with it. limit applies to each list independently, so one kind can never hide behind the other. A consignment carrying several EWC-coded streams under one code lists the extras in additionalWasteLines, each with its own hazard codes and weight.
/receiver/movementsLoads you have actually taken in — both notes fired to you and your own captures of paper or emailed notes — with their DEFRA tracking state.
| Field | Type | Required | Description |
|---|---|---|---|
| references | string | Optional | Comma-separated WTN references AND/OR HWCN consignment codes (max 100 in total). Each is matched against its own list, so one request can reconcile both kinds. |
| since | string | Optional | ISO date — movements received on or after this date. |
| limit | number | Optional | Page size, default 50, maximum 100. |
Provide either references or since. More than 100 references is rejected rather than truncated, so a reconciliation run can never mistake a trimmed response for a complete one. Ordering: a since sync returns oldest first so you can page the whole window; reference lookups return newest first.
Response
{
"movements": [
{
"wtnId": "a1b2c3d4e5f6...",
"wtnReference": "WTN-00042",
"firedByCompany": "Acme Haulage Ltd",
"siteName": "Northside Transfer Station",
"dateOfTransfer": "2026-07-24T00:00:00.000Z",
"receivedAt": "2026-07-24T10:02:00.000Z",
"resolution": "accepted",
"origin": "fired",
"source": "DIGITAL",
"waste": { "ewcCode": "17 09 04", "description": "Mixed C&D waste", "quantity": 12.5, "unit": "tonnes" },
"acceptedQuantity": 12.1,
"additionalWasteLines": [],
"recoveryDisposalCode": "R13",
"hasPdf": true,
"dwt": {
"wasteTrackingId": "2026ABC123",
"status": "SUBMITTED",
"env": "production",
"submittedAt": "2026-07-24T10:02:05.000Z",
"lastError": null
}
}
],
"consignments": [
{
"hwcnId": "b2c3d4e5f6a1...",
"consignmentCode": "ACMEWA/AB23F",
"jurisdiction": "england",
"raisedByCompany": "Acme Waste Ltd",
"carrierCompany": "Hauliers Ltd",
"siteName": "Northside Transfer Station",
"origin": "consigned",
"receivedAt": "2026-07-25",
"receivedTime": "10:02",
"resolution": "accepted",
"acceptedQuantityKg": 195,
"recoveryDisposalCode": "D10",
"containsPops": false,
"waste": { "ewcCode": "13 02 05*", "description": "Used engine oil", "quantityKg": 200 },
"additionalWasteLines": [],
"hasPdf": true,
"dwt": {
"wasteTrackingId": "2026DEF456",
"status": "SUBMITTED",
"env": "production",
"submittedAt": "2026-07-25T10:02:09.000Z",
"lastError": null
}
}
]
}Hazardous receipts carry the same dwt block, because a hazardous receive is filed with DEFRA exactly like a non-hazardous one — that is what makes them reconcilable. references matches consignment codes, and since filters on when the receipt completed. origin is consigned (a note raised by someone else and receipted here) or capture (your own log of a paper note that arrived with a load).
dwt.status is one of PENDING, SUBMITTED, FAILED, AMENDED, NOT_REQUIRED, or null when no submission has been attempted. Filing happens asynchronously after receipt, so a movement read immediately may still show PENDING. origin is fired (a note sent to you digitally) or capture (your own scan of a paper or emailed note).
/receiver/wtn/{id}/pdfDownloads the PDF for a transfer note at your site. Returns the file itself (application/pdf), not a URL. For a hazardous consignment note, use /receiver/hwcn/{id}/pdf below.
curl "https://wtncloud.co.uk/api/external/receiver/wtn/a1b2c3d4e5f6/pdf" \ -H "Authorization: Bearer YOUR_API_KEY" -o consignment.pdf
A PDF is rendered shortly after a note is finalised, so polling too quickly can return 404 PDF_NOT_READY with a Retry-After header — retry rather than treating it as missing. A load you logged yourself from a paper note answers 404 NO_PDF_FOR_CAPTURE instead: nothing generated a PDF and nothing will, so stop polling. A transfer note that is not addressed to your site returns a plain 404.
/receiver/hwcn/{id}/receiptHAZARDOUSRecord Part E — the consignee receipt — on a hazardous consignment addressed to your site. This is the hazardous equivalent of accepting a fired transfer note: what a receiving site does with a consignment note is declare what it actually took in and what it did with it, and that declaration is what DEFRA's receive is built from. Needs the receiver:write scope.
| Field | Type | Required | Description |
|---|---|---|---|
| dateReceived | string | Required | The date the waste was received (YYYY-MM-DD), as a UK calendar date. |
| timeReceived | string | Optional | UK wall-clock time of receipt (HH:MM, 24-hour). DEFRA asks for the exact time; without it the filing can only report midnight. |
| quantityReceivedKg | number | Optional | What you took in of the first waste line, in kilograms. Required unless you send receivedLines, which supplies it. |
| receivedLines | array | Optional | Part E per EWC code, as the statutory form asks it: one { ewcCode, quantityReceivedKg, accepted } per waste line, line 1 first, in the consignment's order; each code's accepted is accepted or rejected, never partially_accepted (the guidance has no partial acceptance of one waste). When sent, accepted and quantityReceivedKg are derived from it, each accepted code is filed with DEFRA at its own weight, and a rejected code isn't filed. Codes that no longer match the consignment's lines are refused 409 LINES_CHANGED. |
| vehicleRegistration | string | Optional | The vehicle that delivered the load (or the mode of transport, if not road). Filed as the carrier's vehicle in place of the plate on Part C. |
| rejectionDetails | string | Optional | Where waste is rejected: what was turned away and why. Printed in Part E. Required when receivedLines turns any code away. |
| quantityReceivedIsEstimate | boolean | Optional | Whether that figure was gauged rather than weighed. Send it either way when you know: an explicit false is what stops a weighed receipt being filed under the consignment's own estimated declaration. |
| accepted | "accepted" | "partially_accepted" | "rejected" | Optional | Defaults to accepted. A rejection files nothing with DEFRA — no waste was received at your site. |
| rdCode | string | Optional | The recovery or disposal operation applied at your site (e.g. D10). Required unless every code is rejected: waste you turn away gets no operation at your site, so a rejected code has no R/D code. |
| rdCodesByLine | array | Optional | Per-waste-line R/D codes for a multi-stream consignment, indexed 0 = the primary line, 1..n = additionalWasteLines. Omit unless a line went somewhere other than rdCode — solvent to R2, contaminated soil to D1. A code rejected in receivedLines keeps no R/D code, whatever is sent for it. |
| sitePermitOrExemption | string | Optional | Your permit or exemption, when it differs from the one printed on the note. Filed as DEFRA's receiver.authorisationNumber. |
| signatoryName | string | Optional | Who signed for the load. Defaults to your company name. |
| certificateConfirmed | true | Required | Explicit confirmation of the Part E certificate. Part E is a statutory declaration, so an integration has to make it deliberately rather than by omission. |
Example Request
curl -X POST "https://wtncloud.co.uk/api/external/receiver/hwcn/b2c3d4e5f6a1/receipt" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"dateReceived": "2026-07-25",
"timeReceived": "10:02",
"quantityReceivedIsEstimate": false,
"rdCode": "D10",
"receivedLines": [
{ "ewcCode": "13 02 05*", "quantityReceivedKg": 195, "accepted": "accepted" },
{ "ewcCode": "16 01 07*", "quantityReceivedKg": 40, "accepted": "rejected" }
],
"vehicleRegistration": "AB12 CDE",
"rejectionDetails": "Oil filters in a split drum.",
"certificateConfirmed": true
}'Response
{
"ok": true,
"hwcnId": "b2c3d4e5f6a1...",
"consignmentCode": "ACMEWA/AB23F",
"partE": "signed",
"dwt": {
"filed": true,
"reason": "A DEFRA receive was triggered. Poll /receiver/movements for its status and wasteTrackingId."
}
}No signature image, and no Idempotency-Key. There is no canvas over an API, so the authenticated call is the signature — the same reading the transfer-note accept endpoint takes. The consignment id is a natural key, so a second call is refused with 409 and a retry after a lost response is safe. A consignment not addressed to your site returns a plain 404.
/receiver/hwcn/{id}/pdfHAZARDOUSDownloads the consignment note itself (application/pdf), for a consignment at your site.
curl "https://wtncloud.co.uk/api/external/receiver/hwcn/b2c3d4e5f6a1/pdf" \ -H "Authorization: Bearer YOUR_API_KEY" -o consignment-note.pdf
The PDF is re-rendered on each signature, so a note still being signed can return 404 PDF_NOT_READY — honour Retry-After. A consignment you logged yourself from a paper note returns 404 NO_PDF_FOR_CAPTURE: WTNcloud generated no document for it, because the original that arrived with the load is the legal record and is held in your dashboard.
Recording receipts
ENTERPRISEThese endpoints record what happened to a load at your site, and are what cause a Digital Waste Tracking receipt to be filed with DEFRA on your behalf. They need the receiver:write scope.
Filing happens in the background once the receipt is recorded, so the response tells you whether a submission was triggered, not its outcome. Poll /receiver/movements for the wasteTrackingId.
/receiver/inbound/{id}/acceptAccept a transfer note fired to your site, in full or in part. The hazardous equivalent is Part E — see /receiver/hwcn/{id}/receipt.
| Field | Type | Required | Description |
|---|---|---|---|
| recoveryDisposalCode | string | Optional | R/D code applied at your site (e.g. R13). Omit to log the receipt WITHOUT filing a DEFRA receive. |
| vehicleRegistration | string | Optional | The plate of the lorry that arrived, when it isn't the one on the note. Recorded on your receipt and named on the DEFRA receive; the carrier's note is left as written. Omit when the lorry was the note's. |
| addedLines | array | Optional | Wastes you received that the note doesn't list, e.g. a load written as general that came in as general and timber: [{ ewcCode, wasteDescription, quantity, unit }], unit one of tonnes, kg, litres or m3. Send a figure for every line the note has beside them (lineQuantities; on a one-line note acceptedQuantity alone will do). Each is filed as its own waste item and counted; the carrier's note is left as written, and the status is the load's as a whole. |
| acceptedQuantity | number | Optional | Confirmed (e.g. weighbridge) quantity, in the note's unit. Required when partial is true, unless lineQuantities is sent. On a note with several waste lines it is the FIRST line's alone: send lineQuantities for the rest. |
| lineQuantities | array | Optional | What you accepted of EACH waste line, first line first, each a number in that line's own unit (the note's unit and additionalWasteLines[].unit in /receiver/inbound). How a transfer note carrying several waste lines is received: every line is recorded and filed with DEFRA at its own figure, and the status follows the lines (PARTIAL when any line is below what it declared). Send one number per line, 0 for a line you took none of. Without it, acceptedQuantity is the first line's and every other line is received as declared. |
| lineKeys | array | Optional | Every waste line of the note as you read it, first line first: one { ewcCode, unit } per line, exactly as /receiver/inbound listed them (waste, then each of additionalWasteLines; other fields on an entry are ignored). Name every line, whether you send lineQuantities or a single acceptedQuantity (on a note with several lines that figure is the first line's and the others are received as declared, so you are confirming those lines too): one key for a note with two lines says you read a one-line note, and is refused. If the carrier has changed the note's lines since you read them (a line's EWC code or unit, their order, or how many there are), the request is refused with 409 LINES_CHANGED and nothing is recorded, rather than a figure being filed with DEFRA against the wrong waste. Without it, each figure is taken against the line at its position when the receipt is recorded. |
| partial | boolean | Optional | Record a partial acceptance rather than accepting the full stated load. With lineQuantities the lines decide it. |
| receivedAt | string | Optional | ISO datetime of receipt. Defaults to now. |
| signatoryName | string | Optional | Who confirmed receipt. Defaults to your company name. |
Example Request
curl -X POST "https://wtncloud.co.uk/api/external/receiver/inbound/a1b2c3d4e5f6/accept" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "recoveryDisposalCode": "R13", "acceptedQuantity": 12.1 }'Response
{
"ok": true,
"wtnId": "a1b2c3d4e5f6...",
"status": "ACCEPTED",
"dwt": {
"filed": true,
"reason": "A DEFRA receive was triggered. Poll /receiver/movements for its status and wasteTrackingId."
}
}A transfer note can only be resolved once — accepting or rejecting an already-processed one returns 409, which makes a retry after a lost response safe. A note listing several waste lines (its additionalWasteLines in /receiver/inbound) is one receipt with a figure per line: send "lineQuantities": [6, 1.5] for a 6 t + 2 t load of which 1.5 t of the second line was taken. A weighbridge weighs the lorry as a whole, so split its net across the lines rather than sending it as the first line's figure. Send every line you read beside the figures as lineKeys, and a note the carrier has edited since is refused with 409 LINES_CHANGED instead of your figures landing on different waste: read it again from /receiver/inbound and resend.
/receiver/inbound/{id}/rejectReject a transfer note. Nothing is filed with DEFRA — no waste was received at your site, so there is no receive movement to report.
| Field | Type | Required | Description |
|---|---|---|---|
| reason | string | Required | Why the load was rejected. Shown to the carrier and kept on the audit record. |
| receivedAt | string | Optional | ISO datetime. Defaults to now. |
| signatoryName | string | Optional | Defaults to your company name. |
/receiver/receiptsRecord a load that never came through WTNcloud — a paper note handed over at the gate. Creates the movement and files its DEFRA receive.
Hazardous loads go here too. If the EWC code is hazardous, supply hazardousDetails and the load is kept as a consignment note rather than a transfer note — the record an operator and an EA inspection look for, in your HWCN register, list and CSV. The response says which you got in kind. Which one it is is decided from the EWC catalogue server-side, never from a flag you send, so the record kept can never disagree with the code declared.
| Field | Type | Required | Description |
|---|---|---|---|
| carrierId | string | Required | The carrier that delivered. Must be one of your own carrier records. |
| ewcCode | string | Required | European Waste Catalogue code. |
| quantity | number | Required | Quantity received. |
| recoveryDisposalCode | string | Required | R/D code applied at your site. Required here — a receipt with no R/D can't be filed. |
| producerName | string | Optional | Producer named on the note (also producerAddress, producerPostcode). |
| wasteDescription | string | Optional | Description of the waste. |
| unit | string | Optional | Unit for quantity (e.g. tonnes). Omit it and the receipt is filed in TONNES — note that this differs from /wtn/create, where an omitted unit means kilograms. Send it explicitly: this endpoint files with DEFRA immediately, so there is no draft stage in which a wrong unit gets corrected. |
| dateOfTransfer | string | Optional | ISO datetime of the transfer. |
| vehicleReg | string | Optional | Vehicle that delivered (also meansOfTransport, physicalForm, containmentType, numberOfContainers). |
| containsPops | boolean | Optional | Whether the load was declared to contain POPs (with popsDetails). |
| hazardousDetails | object | Optional | REQUIRED when the EWC code is hazardous, and rejected when it isn't. Either consignmentCode (the code off the note that travelled with the load) or consignmentCodeMissingReason (NO_DOC_WITH_WASTE | HWRC_RECEIPT), never both; hpCodes (at least one of HP_1–HP_15 or HP_POP; loose spellings like "HP4" are normalised); sourceOfComponents (PROVIDED_WITH_WASTE | NOT_PROVIDED | GUIDANCE | OWN_TESTING); and components as [{ name, concentration }] in mg/kg — required for GUIDANCE or OWN_TESTING, and refused for NOT_PROVIDED. |
| specialHandlingRequirements | string | Optional | Handling instructions for waste with harmful characteristics — DEFRA's specialHandlingRequirements. |
| theirReference | string | Optional | The carrier's own note or ticket number, filed alongside ours as a DEFRA otherReferencesForMovement entry. |
| additionalWasteLines | array | Optional | Extra waste streams on the same transfer note — the fields above are line 1. Omit (or send null) when one stream arrived. Per entry: ewcCode, wasteDescription, quantity, unit and containmentType are required; containmentOther, physicalForm, numberOfContainers (default 1) and containsPops (default false) are optional. Each line may also carry its OWN full details, all optional and falling back to the load's: recoveryDisposalCode (what you did with that stream — DEFRA takes it per waste item, so a load you part-recover and part-dispose is one receipt), hazardousDetails ({hpCodes, sourceOfComponents, components} — stream-level, with no consignment code, which belongs to the note) and popsDetails ({sourceOfComponents, pollutants}). Max 50 extra lines, each filed as its own DEFRA wasteItem and counted separately against your permit. Every line must be the same kind of waste as the first: a transfer note lists non-hazardous codes, a consignment note hazardous ones, and neither carries the other's. A line whose class differs is refused with WASTE_LINE_CLASS_MISMATCH — send that waste as a receipt of its own. |
wasteDescription is required for a hazardous load — a consignment note has to describe the waste.
A delivery carrying several EWC codes of the same kind is one receipt with several lines — put them in additionalWasteLines rather than sending a receipt each. That holds for both kinds: a transfer note may list several non-hazardous wastes, and a consignment note several hazardous ones. A delivery carrying both is two receipts, because the two move under different documents — send the hazardous part on its own and it comes back as kind: “hwcn”.
An Idempotency-Key header is required: this creates a brand-new record with no natural unique key, so without one a retry after a lost response would file a second DEFRA receive for the same physical load.
Example Request
curl -X POST "https://wtncloud.co.uk/api/external/receiver/receipts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: gate-ticket-88213" \
-H "Content-Type: application/json" \
-d '{
"carrierId": "clx123carrier",
"ewcCode": "17 09 04",
"quantity": 8.4,
"unit": "tonnes",
"recoveryDisposalCode": "R13",
"producerName": "Riverside Joinery",
"vehicleReg": "AB12 CDE"
}'Response
{
"ok": true,
"wtnId": "b2c3d4e5f6a1...",
"wtnReference": "WTN-00187",
"kind": "wtn",
"dwt": {
"filed": true,
"reason": "A DEFRA receive was triggered. Poll /receiver/movements for its status and wasteTrackingId."
}
}Example — a hazardous load
curl -X POST "https://wtncloud.co.uk/api/external/receiver/receipts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: gate-ticket-88214" \
-H "Content-Type: application/json" \
-d '{
"carrierId": "clx123carrier",
"ewcCode": "13 02 05*",
"wasteDescription": "Used engine oil",
"quantity": 200,
"unit": "kg",
"recoveryDisposalCode": "D10",
"producerName": "Riverside Joinery",
"vehicleReg": "AB12 CDE",
"hazardousDetails": {
"consignmentCode": "ACMEWA/AB23F",
"hpCodes": ["HP_3", "HP_14"],
"sourceOfComponents": "OWN_TESTING",
"components": [{ "name": "Mineral oil", "concentration": 900 }]
}
}'{
"ok": true,
"kind": "hwcn",
"hwcnId": "b2c3d4e5f6a1...",
"consignmentCode": "ACMEWA/AB23F",
"dwt": {
"filed": true,
"reason": "A DEFRA receive was triggered. Poll /receiver/movements for its status and wasteTrackingId."
}
}A load that arrived with no note at all is logged with consignmentCodeMissingReason instead of a code; we store a placeholder that could not be mistaken for a real consignment code, and DEFRA is sent the reason rather than the placeholder. Logging the same consignment code twice returns 409 CONSIGNMENT_ALREADY_LOGGED with the id of the note you already have — one physical movement, one record.
Webhooks
ENTERPRISERather than polling, you can have us POST an event to your server as things happen. Add up to three endpoints under Settings → API in your dashboard, choose which events each one receives, and send yourself a test delivery before going live.
Endpoints must be https and publicly reachable. Redirects are not followed — point the webhook at its final URL.
Events
| Event | Fires when |
|---|---|
inbound.fired | A carrier sends a transfer note to your receiving site. It is now in your inbox awaiting an accept or reject. Transfer notes only — a consignment note is raised by the producer, not fired. |
movement.received | A consignee receipt was recorded — the load was accepted in full or in part. Fires for a transfer-note receipt AND for Part E on a hazardous consignment. |
movement.rejected | A consignee receipt was recorded as a rejection. Nothing is filed with DEFRA. Fires for both kinds. |
dwt.submitted | DEFRA accepted the receive. The payload carries the wasteTrackingId. |
dwt.failed | A DEFRA receive failed, or was blocked before sending because the data wouldn't pass validation. The payload carries the error. |
Events reach every party to the movement that has subscribed — so a carrier and a receiving site can each run their own integration against the same consignment.
Which kind of note an event is about. A hazardous event carries data.hwcnId and data.consignmentCode where a transfer note carries data.wtnId; the dwt.* events additionally carry data.kind of "wtn" or "hwcn". Every event also carries data.reference — the WTN number or the consignment code — so a consumer that only needs to identify the movement can read one field for both.
Payload
Every delivery has the same envelope. Route on event, dedupe on id — that value is constant across retries of the same event, so a redelivery is easy to spot.
{
"id": "0f2c9f4a-4f4e-4c1a-9f3e-7b2d9c4a1e55",
"event": "movement.received",
"createdAt": "2026-07-25T09:14:22.418Z",
"data": {
"wtnId": "a1b2c3d4e5f6...",
"reference": "WTN-00187",
"receivingSitePermit": "EPR/AB1234CD",
"receivedAt": "2026-07-25T09:14:00.000Z",
"acceptanceStatus": "ACCEPTED",
"acceptedQuantity": 12.1,
"recoveryDisposalCode": "R13",
"rejectionReason": null,
"signatoryName": "Acme Recycling Ltd",
"dwtFiling": true
}
}On a transfer note of several waste lines, acceptedQuantity is the first line's and additionalLines lists what was accepted of each of the others, each with its ewcCode, unit and acceptedQuantity. It is absent when the receipt took one figure for the load, and then every other line was received as declared. A line the site added at receipt, a waste the note doesn't list, carries added: true, a quantity of 0 and what was received of it in acceptedQuantity.
Headers
| Header | Value |
|---|---|
X-WTNCloud-Signature | Hex HMAC-SHA256 of `${timestamp}.${rawBody}`, keyed with your endpoint's signing secret. |
X-WTNCloud-Timestamp | Unix seconds. Reject anything more than 5 minutes old — it's inside the signed string, so it can't be altered. |
X-WTNCloud-Event | The event name, so you can route before parsing the body. |
X-WTNCloud-Delivery | The delivery id — the same value as `id` in the body. |
Verifying a delivery
Sign the raw request body, byte for byte. Parsing the JSON and re-serialising it produces a different string and the signature will not match.
import { createHmac, timingSafeEqual } from "crypto";
// `rawBody` must be the unparsed request body (e.g. express.raw()).
function verify(rawBody, headers, secret) {
const timestamp = headers["x-wtncloud-timestamp"];
const signature = headers["x-wtncloud-signature"];
if (!timestamp || !signature) return false;
// Replay guard: the timestamp is inside the signed string, so an attacker
// can't re-date a captured delivery without breaking the signature.
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!Number.isFinite(age) || age > 300) return false;
const expected = createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`, "utf8")
.digest("hex");
const a = Buffer.from(expected, "utf8");
const b = Buffer.from(signature, "utf8");
return a.length === b.length && timingSafeEqual(a, b);
}Delivery and retries
- Reply 2xx to acknowledge. Anything else — including a redirect — counts as a failure. Reply first and do your processing afterwards; we allow 10 seconds.
- Retries — five attempts in total, spaced 1, 5, 15 and 60 minutes apart, so a delivery survives about 81 minutes of downtime. Each attempt carries a fresh timestamp and signature.
- Ordering isn't guaranteed. A retried
movement.receivedcan arrive after thedwt.submittedthat followed it. Treat each event as a fact about a point in time, and use the API to read current state. - Persistent failure switches an endpoint off. After 20 consecutive events that exhaust every retry, we deactivate it and email the account owner. Nothing is lost: accepted movements and DEFRA submissions stay readable through
/receiver/movements.
Error Codes
| Status | Meaning | What to do |
|---|---|---|
| 400 | Bad Request | Check your request body matches the schema above. The response includes validation details. |
| 400 | Bad Request | INVALID_EWC_CODE — /receiver/receipts: the code isn't in the European Waste Catalogue. It's refused rather than guessed at, because an unrecognised code can't be shown to be non-hazardous. |
| 400 | Bad Request | WASTE_LINE_CLASS_MISMATCH — /receiver/receipts: a line in additionalWasteLines is a different kind of waste from the first (one hazardous, one not). A transfer note and a consignment note are different documents and neither carries the other's waste — send that line as a receipt of its own. |
| 400 | Bad Request | HAZARDOUS_DETAILS_REQUIRED / HAZARDOUS_DETAILS_NOT_APPLICABLE — /receiver/receipts: the hazardousDetails block has to match the EWC code. A hazardous code without it under-declares the load; a non-hazardous code with it files a contradiction. |
| 400 | Bad Request | CUSTOMER_NOT_FOUND — /wtn/create and /hwcn/create: a customerId isn't one of your customers; the response lists them in customerIds. Nothing in the batch was created. Send the customerId a previous note or consignment came back with, or leave it out to match on name and address. |
| 400 | Bad Request | COUNTRY_NOT_SUPPORTED — /hwcn/create: an item's country is scotland or northern_ireland, whose consignment codes the regulator issues. Nothing from the batch was created. items lists each one as { index, country }, index counting from 0 in hwcns; enter those through the dashboard and resend the rest. |
| 401 | Unauthorized | Your API key is missing or invalid. Check the Authorization header. |
| 402 | Payment Required | FREE accounts: the free WTN allowance is used up — the account needs a subscription to create more. (Does not apply to /hwcn/create — drafts are free.) |
| 403 | Forbidden | Your account has been suspended. Contact [email protected]. |
| 403 | Forbidden | API_ACCESS_TIER_REQUIRED — the external API is included with the Enterprise plan. Upgrade in your dashboard billing settings, or contact [email protected]. |
| 403 | Forbidden | INSUFFICIENT_SCOPE — your key doesn't carry the scope this endpoint needs; the response names it. For receiver:read / receiver:write, turn the Receiver API on under Settings → API in your dashboard. read and carrier:write are on every key by default, so if one of those is missing it was revoked — contact [email protected]. |
| 403 | Forbidden | RECEIVER_ROLE_REQUIRED / RECEIVER_TIER_REQUIRED / RECEIVER_NOT_CONFIGURED — receiver endpoints need the receiver role, an Enterprise plan, and a receiving-site permit on your account. |
| 404 | Not Found | The record doesn't exist, isn't yours, or isn't addressed to your site — all three answer the same way, so a key can't probe for other accounts' ids. PDF_NOT_READY means the document hasn't been rendered yet (a PDF follows the signatures, so an unsigned note keeps answering this) — honour Retry-After and try again. NO_PDF_FOR_CAPTURE means the load was logged from a paper note, so no PDF was ever generated: the retained original in your dashboard is the record. |
| 409 | Conflict | The consignment was already accepted or rejected, or hasn't been fired to you yet. Part E: already signed, or the note is no longer awaiting a signature. IDEMPOTENCY_IN_PROGRESS means an identical request is still running — retry shortly. |
| 409 | Conflict | CONSIGNMENT_ALREADY_LOGGED / CONSIGNMENT_IN_DELETED_ITEMS — /receiver/receipts: you have already logged a consignment with this note code (the response names it), or a note with that code is in your deleted items and has to be restored or purged first. |
| 409 | Conflict | LINES_CHANGED — /receiver/inbound/{id}/accept: the note's waste lines are no longer the lineKeys you sent (the carrier changed a line's EWC code or unit, their order, or how many there are, after you read it). Nothing was recorded. Read the note again from /receiver/inbound and resend your figures for its lines as they are now. |
| 422 | Unprocessable | IDEMPOTENCY_KEY_REUSED — that Idempotency-Key was already used with a different request body. Use a fresh key for a new load. On Part E, a 422 means the stored consignment fails validation and can't be completed until the producer corrects it; the response lists the issues. |
| 429 | Too Many Requests | Slow down — the response carries a Retry-After header telling you how long to wait. |
| 502 | Bad Gateway | PDF_FETCH_FAILED — the document exists but couldn't be retrieved from storage. Retry; if it persists, contact support. |
| 504 | Gateway Timeout | PDF_FETCH_TIMEOUT — retrieving the document from storage took too long. Honour Retry-After and try again. |
| 500 | Server Error | Something went wrong on our end. Retry after a few seconds. If it persists, contact support. |
| 503 | Service Unavailable | /hwcn/create only — failed to allocate a unique consignment code after 5 attempts (vanishingly rare). The response includes any items that were created before the collision; retry with the rest. |
Rate Limits
API requests are rate limited to prevent abuse. Each request can include up to 100 WTNs in a single batch. The PDF download endpoints get their own budget of 60 requests a minute, separate from the JSON reads, because each one streams a file. If you need higher limits, contact us at [email protected].
Ready to integrate?
Sign up for an Enterprise plan to get your API key and start creating WTNs programmatically.