ContentsIntroduction
Preview v1
API reference
Every endpoint, with its parameters, a sample in eight languages and the sandbox's real answer. Press Try it to send the request yourself.
Introduction
The Mercovi API is organized around REST. It has resource URLs, takes and returns JSON, and uses standard HTTP methods, status codes and bearer authentication.
The API is in preview. The sandbox answers every endpoint on this page with sample data and keeps nothing you send. Production keys are set up with each customer during onboarding — book a call to start.
Times are ISO 8601 in UTC. Dates without a time are YYYY-MM-DD. Money is an integer in cents, with a lowercase currency. IDs are strings with a prefix that names the object, for example job_ or man_.
Sandbox https://mercovi.com/api/sandbox/v1
Production issued with your keysAuthentication
Send your key in the Authorization header as a bearer token on every request, over HTTPS. Requests without a key, or with one Mercovi didn't issue, get a 401.
Sandbox keys begin mk_test_ and live keys begin mk_live_. The sandbox key on these pages is public on purpose: it only opens sample data. Live keys are secrets. Keep them on your server, never in a browser or a mobile app, and revoke any key you think has leaked.
Each live key carries scopes, and an endpoint answers 403 if the key lacks the one it needs. The scopes are clients:read, clients:write, sites:read, sites:write, profiles:read, profiles:write, jobs:read, jobs:write, containers:read, containers:write, manifests:read, manifests:write, invoices:read, webhooks:read, webhooks:write, events:read.
curl https://mercovi.com/api/sandbox/v1/clients \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9"Errors
Mercovi returns 2xx when a request worked, 4xx when something in the request needs to change, and 5xx when something failed on our side. Every error has the same JSON shape: type, a machine-readable code, a message written for the developer reading the log, the param at fault when there is one, and the request_id — quote it when you write to us.
| Status | Code | When |
|---|---|---|
| 400 | parameter_missing | A required field is missing. param names it. |
| 400 | parameter_invalid | A field has the wrong type or a value outside its list. |
| 401 | api_key_missing | No Authorization: Bearer header. |
| 401 | api_key_invalid | The key isn't one Mercovi issued, or it was revoked. |
| 403 | scope_missing | The key is real but lacks the scope this endpoint needs. |
| 404 | resource_missing | No record with that ID in your account. |
| 405 | method_not_allowed | The path exists but not with that method. |
| 409 | idempotency_conflict | An Idempotency-Key was reused with a different body. |
| 422 | state_invalid | The record can't make that change from where it is — for example, cancelling a closed job. |
| 429 | rate_limited | Too many requests. Wait for the time in Retry-After. |
| 500 | api_error | Something failed on Mercovi's side. Safe to retry with the same Idempotency-Key. |
{
"error": {
"type": "invalid_request_error",
"code": "parameter_missing",
"message": "`kind` is required.",
"param": "kind",
"doc_url": "/developers/reference#errors",
"request_id": "req_7Gx2Sample0001"
}
}Pagination
List endpoints return a page of records, newest first unless the endpoint says otherwise, with has_more and next_cursor. Ask for the next page by passing next_cursor as starting_after. limit sets the page size, from 1 to 100; the default is 25.
Cursors are record IDs, so pages stay stable when records are added while you read.
{
"object": "list",
"data": [
"…"
],
"has_more": true,
"next_cursor": "job_6Cp4HaldenNov"
}Idempotency
Networks fail. To retry a POST without creating the same record twice, send an Idempotency-Key header with a value unique to that operation — a UUID works. A retry with the same key and the same body returns the first result. The same key with a different body returns a 409.
GET, PATCH and DELETE are idempotent already and don't need the header.
curl -X POST https://mercovi.com/api/sandbox/v1/clients \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9" \
-H "Idempotency-Key: 5c0a4e9e-1d3b-4a51-9f0e-2b7f3c6d8a10" \
-H "Content-Type: application/json" \
-d '{"name": "Fairhaven Township", "kind": "hhw_program"}'Versioning
The version is in the path: /v1. Within a version, Mercovi only makes additive changes — new endpoints, new optional fields, new event types, new values in a list. Write your client to ignore fields it doesn't know.
A change that would break a working client — removing or renaming a field, changing its type — ships as a new version, announced to key holders first, with the old one kept running alongside.
While the API is in preview, fields may still change inside v1. Key holders hear about each change before it ships.
Rate limits
Every response carries RateLimit-Policy and, in production, RateLimit-Remaining and RateLimit-Reset, so a client can slow down before it's stopped. A request over the limit gets a 429 with a Retry-After header in seconds.
Limits are set per account. If an integration needs more — a nightly sync of a large history, say — tell us and we'll set the limit with you.
Webhooks
Register an HTTPS endpoint and Mercovi sends it an event object whenever one of the events you chose happens. Answer with any 2xx within a few seconds and do the work afterwards. Deliveries that fail are retried with growing gaps between attempts. If your endpoint was down, list /v1/events to catch up.
Each delivery carries a Mercovi-Signature header: t=<unix time>,v1=<signature>, where the signature is an HMAC-SHA256 of <t>.<raw body> keyed by the endpoint's whsec_ secret. Compute it yourself, compare in constant time, and reject deliveries whose t is more than five minutes old.
| Event | When it's sent |
|---|---|
job.created | A job — an HHW event, a C&I pickup or a lab pack — is created. |
job.scheduled | A job gets its date and is staffed. |
job.closed | A job is closed out: containers counted, weights in. |
staff.accepted | A crew member accepts an assignment on a job. |
container.received | A container is checked in at a facility. |
container.storage_status_changed | A container's storage status moves — for example from within the limit to approaching it. |
manifest.ready_for_signature | A manifest is complete and waiting for the generator's signature. |
manifest.signed | A manifest is signed; its status, submission method and tracking number are on the record. |
profile.approved | A waste profile is approved. |
profile.expiring | A waste profile is nearing its renewal date. |
invoice.sent | An invoice is sent to the client. |
invoice.paid | An invoice is paid, in the client portal or recorded by your team. |
import crypto from "node:crypto";
export function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const age = Date.now() / 1000 - Number(parts.t);
if (!(age >= 0 && age < 300)) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
const given = Buffer.from(parts.v1 ?? "", "hex");
const want = Buffer.from(expected, "hex");
return given.length === want.length && crypto.timingSafeEqual(given, want);
}Clients
The organizations you serve: an HHW program, a plant or institution you collect from, or a laboratory. Sites, profiles, jobs and invoices all hang off a client.
The client object · 10 attributes
The client object
idstringrequiredUnique identifier for the client, beginning
cli_.objectstringAlways
client.namestringrequiredThe client's name as it appears on documents.
kindenumWhich line the client is served under. Drives the client portal's navigation.
hhw_programcommercial_industriallaboratoryepa_idstringThe client's EPA ID number, when it is the generator of record.
billing_emailstringWhere invoices go.
portal_enabledbooleanWhether the client can sign in to the client portal.
metadataobjectUp to 20 key-value pairs of your own, for example the record's ID in your system. Mercovi stores them and never reads them.
created_attimestampWhen the record was created, ISO 8601 in UTC.
updated_attimestampWhen the record last changed, ISO 8601 in UTC.
{
"id": "cli_2Rv8Ridgeview",
"object": "client",
"name": "Ridgeview County HHW program",
"kind": "hhw_program",
"epa_id": null,
"billing_email": "hhw@ridgeview.example",
"portal_enabled": true,
"metadata": {
"erp_id": "C-10233"
},
"created_at": "2026-03-02T14:00:00Z",
"updated_at": "2026-09-14T14:00:00Z"
}List clients
GET/v1/clients
Returns your clients, newest first.
Scope clients:read · Returns 200
Query parameters
kindenumOnly clients of this kind.
hhw_programcommercial_industriallaboratorylimitintegerHow many records to return, 1 to 100. Defaults to 25.
starting_afterstringA record ID. Returns the page after it — pass the
next_cursorof the previous page.
curl "https://mercovi.com/api/sandbox/v1/clients?limit=3" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9"{
"object": "list",
"data": [
{
"id": "cli_2Rv8Ridgeview",
"object": "client",
"name": "Ridgeview County HHW program",
"kind": "hhw_program",
"epa_id": null,
"billing_email": "hhw@ridgeview.example",
"portal_enabled": true,
"metadata": {
"erp_id": "C-10233"
},
"created_at": "2026-03-02T14:00:00Z",
"updated_at": "2026-09-14T14:00:00Z"
},
{
"id": "cli_7Hd3Halden",
"object": "client",
"name": "Halden Manufacturing",
"kind": "commercial_industrial",
"epa_id": "OHD987654321",
"billing_email": "ap@halden.example",
"portal_enabled": true,
"metadata": {},
"created_at": "2026-04-18T14:00:00Z",
"updated_at": "2026-09-30T14:00:00Z"
},
{
"id": "cli_4Vl6Vantage",
"object": "client",
"name": "Vantage Laboratories",
"kind": "laboratory",
"epa_id": "NJR000123456",
"billing_email": "ehs@vantagelabs.example",
"portal_enabled": false,
"metadata": {},
"created_at": "2026-05-07T14:00:00Z",
"updated_at": "2026-08-21T14:00:00Z"
}
],
"has_more": true,
"next_cursor": "cli_4Vl6Vantage"
}Create a client
POST/v1/clients
Adds a client. Send an Idempotency-Key so a retried request can't create it twice.
Scope clients:write · Returns 201
Body
namestringrequiredThe client's name.
kindenumrequiredWhich line the client is served under.
hhw_programcommercial_industriallaboratoryepa_idstringTwelve characters: two-letter state code, a letter, nine digits.
billing_emailstringWhere invoices go.
portal_enabledbooleanInvite the client to the client portal. Defaults to false.
metadataobjectUp to 20 key-value pairs of your own, for example the record's ID in your system. Mercovi stores them and never reads them.
curl -X POST "https://mercovi.com/api/sandbox/v1/clients" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"name": "Fairhaven Township",
"kind": "hhw_program",
"billing_email": "dpw@fairhaven.example",
"portal_enabled": true,
"metadata": {
"erp_id": "C-20418"
}
}'{
"id": "cli_7Gx2Sample0001",
"object": "client",
"epa_id": null,
"billing_email": "dpw@fairhaven.example",
"portal_enabled": true,
"metadata": {
"erp_id": "C-20418"
},
"created_at": "2026-10-09T14:00:00Z",
"updated_at": "2026-10-09T14:00:00Z",
"name": "Fairhaven Township",
"kind": "hhw_program"
}Retrieve a client
GET/v1/clients/{id}
Returns one client.
Scope clients:read · Returns 200
Path parameters
idstringrequiredThe client's ID, beginning
cli_.
curl "https://mercovi.com/api/sandbox/v1/clients/cli_2Rv8Ridgeview" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9"{
"id": "cli_2Rv8Ridgeview",
"object": "client",
"name": "Ridgeview County HHW program",
"kind": "hhw_program",
"epa_id": null,
"billing_email": "hhw@ridgeview.example",
"portal_enabled": true,
"metadata": {
"erp_id": "C-10233"
},
"created_at": "2026-03-02T14:00:00Z",
"updated_at": "2026-09-14T14:00:00Z"
}Update a client
PATCH/v1/clients/{id}
Changes the fields you send and leaves the rest alone.
Scope clients:write · Returns 200
Path parameters
idstringrequiredThe client's ID, beginning
cli_.
Body
namestringThe client's name.
billing_emailstringWhere invoices go.
portal_enabledbooleanTurn client portal access on or off.
metadataobjectUp to 20 key-value pairs of your own, for example the record's ID in your system. Mercovi stores them and never reads them.
curl -X PATCH "https://mercovi.com/api/sandbox/v1/clients/cli_2Rv8Ridgeview" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9" \
-H "Content-Type: application/json" \
-d '{
"billing_email": "hhw-billing@ridgeview.example"
}'{
"id": "cli_2Rv8Ridgeview",
"object": "client",
"name": "Ridgeview County HHW program",
"kind": "hhw_program",
"epa_id": null,
"billing_email": "hhw-billing@ridgeview.example",
"portal_enabled": true,
"metadata": {
"erp_id": "C-10233"
},
"created_at": "2026-03-02T14:00:00Z",
"updated_at": "2026-10-09T14:00:00Z"
}Sites
A place you collect from: an event location, a plant's loading dock, a lab's stockroom. A client can have many.
The site object · 9 attributes
The site object
idstringrequiredUnique identifier for the site, beginning
site_.objectstringAlways
site.clientstringrequiredThe client's ID.
namestringrequiredWhat your crew calls the place.
addressobjectline1,line2,city,state,postal_code.epa_idstringThe site's own EPA ID, when it has one.
access_notesstringGate codes, dock hours, who to ask for.
metadataobjectUp to 20 key-value pairs of your own, for example the record's ID in your system. Mercovi stores them and never reads them.
created_attimestampWhen the record was created, ISO 8601 in UTC.
{
"id": "site_4Kp1Fairgrounds",
"object": "site",
"client": "cli_2Rv8Ridgeview",
"name": "Ridgeview County Fairgrounds — Lot C",
"address": {
"line1": "1200 Fair St",
"line2": null,
"city": "Ridgeview",
"state": "NJ",
"postal_code": "07401"
},
"epa_id": null,
"access_notes": "Enter from Gate B.",
"metadata": {},
"created_at": "2026-03-02T14:00:00Z"
}List sites
GET/v1/sites
Returns sites, newest first.
Scope sites:read · Returns 200
Query parameters
clientstringOnly this client's sites.
limitintegerHow many records to return, 1 to 100. Defaults to 25.
starting_afterstringA record ID. Returns the page after it — pass the
next_cursorof the previous page.
curl "https://mercovi.com/api/sandbox/v1/sites?client=cli_7Hd3Halden" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9"{
"object": "list",
"data": [
{
"id": "site_8Tn5HaldenDock",
"object": "site",
"client": "cli_7Hd3Halden",
"name": "Plant 1 — shipping dock",
"address": {
"line1": "40 Foundry Rd",
"line2": null,
"city": "Halden",
"state": "OH",
"postal_code": "44101"
},
"epa_id": "OHD987654321",
"access_notes": "Dock 2. Hard hats past the yellow line.",
"metadata": {},
"created_at": "2026-04-18T14:00:00Z"
}
],
"has_more": false,
"next_cursor": null
}Create a site
POST/v1/sites
Adds a site to a client.
Scope sites:write · Returns 201
Body
clientstringrequiredThe client's ID.
namestringrequiredWhat your crew calls the place.
addressobjectrequiredline1,city,state,postal_codeare required.access_notesstringGate codes, dock hours, who to ask for.
metadataobjectUp to 20 key-value pairs of your own, for example the record's ID in your system. Mercovi stores them and never reads them.
curl -X POST "https://mercovi.com/api/sandbox/v1/sites" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"client": "cli_7Hd3Halden",
"name": "Plant 2 — receiving dock",
"address": {
"line1": "41 Foundry Rd",
"city": "Halden",
"state": "OH",
"postal_code": "44101"
},
"access_notes": "Dock 4. Ask for EHS at the guard house."
}'{
"id": "site_7Gx2Sample0001",
"object": "site",
"epa_id": null,
"access_notes": "Dock 4. Ask for EHS at the guard house.",
"metadata": {},
"created_at": "2026-10-09T14:00:00Z",
"client": "cli_7Hd3Halden",
"name": "Plant 2 — receiving dock",
"address": {
"line1": "41 Foundry Rd",
"city": "Halden",
"state": "OH",
"postal_code": "44101"
}
}Retrieve a site
GET/v1/sites/{id}
Returns one site.
Scope sites:read · Returns 200
Path parameters
idstringrequiredThe site's ID, beginning
site_.
curl "https://mercovi.com/api/sandbox/v1/sites/site_4Kp1Fairgrounds" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9"{
"id": "site_4Kp1Fairgrounds",
"object": "site",
"client": "cli_2Rv8Ridgeview",
"name": "Ridgeview County Fairgrounds — Lot C",
"address": {
"line1": "1200 Fair St",
"line2": null,
"city": "Ridgeview",
"state": "NJ",
"postal_code": "07401"
},
"epa_id": null,
"access_notes": "Enter from Gate B.",
"metadata": {},
"created_at": "2026-03-02T14:00:00Z"
}Update a site
PATCH/v1/sites/{id}
Changes the fields you send.
Scope sites:write · Returns 200
Path parameters
idstringrequiredThe site's ID, beginning
site_.
Body
namestringWhat your crew calls the place.
access_notesstringGate codes, dock hours, who to ask for.
metadataobjectUp to 20 key-value pairs of your own, for example the record's ID in your system. Mercovi stores them and never reads them.
curl -X PATCH "https://mercovi.com/api/sandbox/v1/sites/site_4Kp1Fairgrounds" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9" \
-H "Content-Type: application/json" \
-d '{
"access_notes": "Enter from Gate B. Traffic cones in the shed."
}'{
"id": "site_4Kp1Fairgrounds",
"object": "site",
"client": "cli_2Rv8Ridgeview",
"name": "Ridgeview County Fairgrounds — Lot C",
"address": {
"line1": "1200 Fair St",
"line2": null,
"city": "Ridgeview",
"state": "NJ",
"postal_code": "07401"
},
"epa_id": null,
"access_notes": "Enter from Gate B. Traffic cones in the shed.",
"metadata": {},
"created_at": "2026-03-02T14:00:00Z"
}Waste profiles
A characterized waste stream for a client: what it is, its waste codes and DOT description, and whether the receiving facility has approved it. Manifest lines are built from profiles.
Endpoints
The waste_profile object · 10 attributes
The waste_profile object
idstringrequiredUnique identifier for the profile, beginning
prof_.objectstringAlways
waste_profile.clientstringrequiredThe client's ID.
namestringrequiredThe stream's common name.
waste_codesarrayFederal and state waste codes, for example
D001,F003.dot_descriptionstringThe proper shipping name, hazard class, UN number and packing group.
statusenumWhere the profile is in approval.
draftsubmittedapprovedexpiredapproved_bystringThe receiving facility's name, once approved.
created_attimestampWhen the record was created, ISO 8601 in UTC.
updated_attimestampWhen the record last changed, ISO 8601 in UTC.
{
"id": "prof_9Wq2Paint",
"object": "waste_profile",
"client": "cli_2Rv8Ridgeview",
"name": "Oil-based paint",
"waste_codes": [
"D001"
],
"dot_description": "UN1263, Waste Paint, 3, PG II",
"status": "approved",
"approved_by": "Ashfield Chemical Recovery",
"created_at": "2026-03-04T14:00:00Z",
"updated_at": "2026-03-20T14:00:00Z"
}List waste profiles
GET/v1/waste_profiles
Returns profiles, newest first.
Scope profiles:read · Returns 200
Query parameters
clientstringOnly this client's profiles.
statusenumOnly profiles in this status.
draftsubmittedapprovedexpiredlimitintegerHow many records to return, 1 to 100. Defaults to 25.
starting_afterstringA record ID. Returns the page after it — pass the
next_cursorof the previous page.
curl "https://mercovi.com/api/sandbox/v1/waste_profiles?status=approved" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9"{
"object": "list",
"data": [
{
"id": "prof_9Wq2Paint",
"object": "waste_profile",
"client": "cli_2Rv8Ridgeview",
"name": "Oil-based paint",
"waste_codes": [
"D001"
],
"dot_description": "UN1263, Waste Paint, 3, PG II",
"status": "approved",
"approved_by": "Ashfield Chemical Recovery",
"created_at": "2026-03-04T14:00:00Z",
"updated_at": "2026-03-20T14:00:00Z"
},
{
"id": "prof_6Ox4Oxidizer",
"object": "waste_profile",
"client": "cli_4Vl6Vantage",
"name": "Lab pack — oxidizers",
"waste_codes": [
"D001"
],
"dot_description": "UN1479, Waste Oxidizing solid, n.o.s. (potassium permanganate), 5.1, PG II",
"status": "approved",
"approved_by": "Ashfield Chemical Recovery",
"created_at": "2026-05-09T14:00:00Z",
"updated_at": "2026-05-30T14:00:00Z"
},
{
"id": "prof_1Ac7Batteries",
"object": "waste_profile",
"client": "cli_2Rv8Ridgeview",
"name": "Lead-acid batteries",
"waste_codes": [
"D002",
"D008"
],
"dot_description": "UN2794, Batteries, wet, filled with acid, 8, PG III",
"status": "approved",
"approved_by": "Ashfield Chemical Recovery",
"created_at": "2026-03-04T14:00:00Z",
"updated_at": "2026-03-20T14:00:00Z"
}
],
"has_more": false,
"next_cursor": null
}Create a waste profile
POST/v1/waste_profiles
Starts a profile in draft. Waste codes are checked against the federal and state code library.
Scope profiles:write · Returns 201
Body
clientstringrequiredThe client's ID.
namestringrequiredThe stream's common name.
waste_codesarrayrequiredOne or more waste codes.
dot_descriptionstringThe proper shipping name and hazard class.
curl -X POST "https://mercovi.com/api/sandbox/v1/waste_profiles" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"client": "cli_7Hd3Halden",
"name": "Spent parts-washer solvent",
"waste_codes": [
"D001",
"F002"
],
"dot_description": "UN1993, Waste Flammable liquid, n.o.s., 3, PG II"
}'{
"id": "prof_7Gx2Sample0001",
"object": "waste_profile",
"dot_description": "UN1993, Waste Flammable liquid, n.o.s., 3, PG II",
"status": "draft",
"approved_by": null,
"created_at": "2026-10-09T14:00:00Z",
"updated_at": "2026-10-09T14:00:00Z",
"client": "cli_7Hd3Halden",
"name": "Spent parts-washer solvent",
"waste_codes": [
"D001",
"F002"
]
}Retrieve a waste profile
GET/v1/waste_profiles/{id}
Returns one profile.
Scope profiles:read · Returns 200
Path parameters
idstringrequiredThe profile's ID, beginning
prof_.
curl "https://mercovi.com/api/sandbox/v1/waste_profiles/prof_9Wq2Paint" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9"{
"id": "prof_9Wq2Paint",
"object": "waste_profile",
"client": "cli_2Rv8Ridgeview",
"name": "Oil-based paint",
"waste_codes": [
"D001"
],
"dot_description": "UN1263, Waste Paint, 3, PG II",
"status": "approved",
"approved_by": "Ashfield Chemical Recovery",
"created_at": "2026-03-04T14:00:00Z",
"updated_at": "2026-03-20T14:00:00Z"
}Update a waste profile
PATCH/v1/waste_profiles/{id}
Changes a profile in draft. An approved profile is changed by creating a new one.
Scope profiles:write · Returns 200
Path parameters
idstringrequiredThe profile's ID, beginning
prof_.
Body
namestringThe stream's common name.
waste_codesarrayReplaces the list.
dot_descriptionstringThe proper shipping name and hazard class.
curl -X PATCH "https://mercovi.com/api/sandbox/v1/waste_profiles/prof_3Lb8Solvent" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9" \
-H "Content-Type: application/json" \
-d '{
"waste_codes": [
"D001",
"F003",
"F005"
]
}'{
"id": "prof_3Lb8Solvent",
"object": "waste_profile",
"client": "cli_7Hd3Halden",
"name": "Spent degreasing solvent",
"waste_codes": [
"D001",
"F003",
"F005"
],
"dot_description": "UN1993, Waste Flammable liquid, n.o.s. (xylene, acetone), 3, PG II",
"status": "submitted",
"approved_by": null,
"created_at": "2026-09-22T14:00:00Z",
"updated_at": "2026-10-09T14:00:00Z"
}Jobs
One piece of work: an HHW collection event, a commercial pickup, or a lab pack. A job has a client, a site, a date, a crew and containers; closing it writes the bill.
The job object · 14 attributes
The job object
idstringrequiredUnique identifier for the job, beginning
job_.objectstringAlways
job.typeenumrequiredWhat kind of work it is.
hhw_eventci_pickuplab_packclientstringrequiredThe client's ID.
sitestringrequiredThe site's ID.
statusenumWhere the job is.
draftscheduledin_progressclosedbilledcancelledscheduled_fordateThe date of the work.
windowobjectstartandend, local time,HH:MM.crewarrayAssigned staff:
name,role,accepted.container_countintegerContainers on the job so far.
invoicestringThe invoice ID, once the job is billed.
metadataobjectUp to 20 key-value pairs of your own, for example the record's ID in your system. Mercovi stores them and never reads them.
created_attimestampWhen the record was created, ISO 8601 in UTC.
updated_attimestampWhen the record last changed, ISO 8601 in UTC.
{
"id": "job_5Ev2Ridgeview0912",
"object": "job",
"type": "hhw_event",
"client": "cli_2Rv8Ridgeview",
"site": "site_4Kp1Fairgrounds",
"status": "billed",
"scheduled_for": "2026-09-12",
"window": {
"start": "08:00",
"end": "14:00"
},
"crew": [
{
"name": "Dana Ortiz",
"role": "Site lead",
"accepted": true
},
{
"name": "Sam Patel",
"role": "Chemist",
"accepted": true
},
{
"name": "Lee Moreno",
"role": "Technician",
"accepted": true
}
],
"container_count": 18,
"invoice": "inv_1040Ridgeview",
"metadata": {},
"created_at": "2026-07-01T14:00:00Z",
"updated_at": "2026-09-15T14:00:00Z"
}List jobs
GET/v1/jobs
Returns jobs, soonest first.
Scope jobs:read · Returns 200
Query parameters
typeenumOnly jobs of this type.
hhw_eventci_pickuplab_packstatusenumOnly jobs in this status.
draftscheduledin_progressclosedbilledcancelledclientstringOnly this client's jobs.
scheduled_fromdateOn or after this date,
YYYY-MM-DD.limitintegerHow many records to return, 1 to 100. Defaults to 25.
starting_afterstringA record ID. Returns the page after it — pass the
next_cursorof the previous page.
curl "https://mercovi.com/api/sandbox/v1/jobs?type=hhw_event" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9"{
"object": "list",
"data": [
{
"id": "job_5Ev2Ridgeview0912",
"object": "job",
"type": "hhw_event",
"client": "cli_2Rv8Ridgeview",
"site": "site_4Kp1Fairgrounds",
"status": "billed",
"scheduled_for": "2026-09-12",
"window": {
"start": "08:00",
"end": "14:00"
},
"crew": [
{
"name": "Dana Ortiz",
"role": "Site lead",
"accepted": true
},
{
"name": "Sam Patel",
"role": "Chemist",
"accepted": true
},
{
"name": "Lee Moreno",
"role": "Technician",
"accepted": true
}
],
"container_count": 18,
"invoice": "inv_1040Ridgeview",
"metadata": {},
"created_at": "2026-07-01T14:00:00Z",
"updated_at": "2026-09-15T14:00:00Z"
},
{
"id": "job_8Wb2Westbrook1017",
"object": "job",
"type": "hhw_event",
"client": "cli_9Wb1Westbrook",
"site": "site_4Kp1Fairgrounds",
"status": "scheduled",
"scheduled_for": "2026-10-17",
"window": {
"start": "09:00",
"end": "13:00"
},
"crew": [
{
"name": "Dana Ortiz",
"role": "Site lead",
"accepted": true
}
],
"container_count": 0,
"invoice": null,
"metadata": {},
"created_at": "2026-08-12T14:00:00Z",
"updated_at": "2026-10-01T14:00:00Z"
}
],
"has_more": false,
"next_cursor": null
}Create a job
POST/v1/jobs
Creates a job in draft. Add a date and window and it moves to scheduled.
Scope jobs:write · Returns 201
Body
typeenumrequiredWhat kind of work it is.
hhw_eventci_pickuplab_packclientstringrequiredThe client's ID.
sitestringrequiredThe site's ID.
scheduled_fordateThe date,
YYYY-MM-DD.windowobjectstartandend,HH:MM.metadataobjectUp to 20 key-value pairs of your own, for example the record's ID in your system. Mercovi stores them and never reads them.
curl -X POST "https://mercovi.com/api/sandbox/v1/jobs" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"type": "ci_pickup",
"client": "cli_7Hd3Halden",
"site": "site_8Tn5HaldenDock",
"scheduled_for": "2026-11-04",
"window": {
"start": "08:00",
"end": "12:00"
}
}'{
"id": "job_7Gx2Sample0001",
"object": "job",
"status": "scheduled",
"scheduled_for": "2026-11-04",
"window": {
"start": "08:00",
"end": "12:00"
},
"crew": [],
"container_count": 0,
"invoice": null,
"metadata": {},
"created_at": "2026-10-09T14:00:00Z",
"updated_at": "2026-10-09T14:00:00Z",
"type": "ci_pickup",
"client": "cli_7Hd3Halden",
"site": "site_8Tn5HaldenDock"
}Retrieve a job
GET/v1/jobs/{id}
Returns one job with its crew.
Scope jobs:read · Returns 200
Path parameters
idstringrequiredThe job's ID, beginning
job_.
curl "https://mercovi.com/api/sandbox/v1/jobs/job_5Ev2Ridgeview0912" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9"{
"id": "job_5Ev2Ridgeview0912",
"object": "job",
"type": "hhw_event",
"client": "cli_2Rv8Ridgeview",
"site": "site_4Kp1Fairgrounds",
"status": "billed",
"scheduled_for": "2026-09-12",
"window": {
"start": "08:00",
"end": "14:00"
},
"crew": [
{
"name": "Dana Ortiz",
"role": "Site lead",
"accepted": true
},
{
"name": "Sam Patel",
"role": "Chemist",
"accepted": true
},
{
"name": "Lee Moreno",
"role": "Technician",
"accepted": true
}
],
"container_count": 18,
"invoice": "inv_1040Ridgeview",
"metadata": {},
"created_at": "2026-07-01T14:00:00Z",
"updated_at": "2026-09-15T14:00:00Z"
}Update a job
PATCH/v1/jobs/{id}
Reschedules or annotates a job that isn't closed.
Scope jobs:write · Returns 200
Path parameters
idstringrequiredThe job's ID, beginning
job_.
Body
scheduled_fordateThe new date.
windowobjectstartandend,HH:MM.metadataobjectUp to 20 key-value pairs of your own, for example the record's ID in your system. Mercovi stores them and never reads them.
curl -X PATCH "https://mercovi.com/api/sandbox/v1/jobs/job_6Cp4HaldenNov" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9" \
-H "Content-Type: application/json" \
-d '{
"window": {
"start": "09:00",
"end": "13:00"
}
}'{
"id": "job_6Cp4HaldenNov",
"object": "job",
"type": "ci_pickup",
"client": "cli_7Hd3Halden",
"site": "site_8Tn5HaldenDock",
"status": "scheduled",
"scheduled_for": "2026-11-04",
"window": {
"start": "09:00",
"end": "13:00"
},
"crew": [
{
"name": "Jordan Kim",
"role": "Driver",
"accepted": true
}
],
"container_count": 4,
"invoice": null,
"metadata": {
"po": "HM-4471"
},
"created_at": "2026-09-30T14:00:00Z",
"updated_at": "2026-10-09T14:00:00Z"
}Cancel a job
POST/v1/jobs/{id}/cancel
Cancels a job that hasn't started. The crew is told; nothing is billed.
Scope jobs:write · Returns 200
Path parameters
idstringrequiredThe job's ID, beginning
job_.
Body
reasonstringShown to the crew and on the job's history.
curl -X POST "https://mercovi.com/api/sandbox/v1/jobs/job_6Cp4HaldenNov/cancel" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"reason": "Client rescheduled to December."
}'{
"id": "job_6Cp4HaldenNov",
"object": "job",
"type": "ci_pickup",
"client": "cli_7Hd3Halden",
"site": "site_8Tn5HaldenDock",
"status": "cancelled",
"scheduled_for": "2026-11-04",
"window": {
"start": "08:00",
"end": "12:00"
},
"crew": [
{
"name": "Jordan Kim",
"role": "Driver",
"accepted": true
}
],
"container_count": 4,
"invoice": null,
"metadata": {
"po": "HM-4471"
},
"created_at": "2026-09-30T14:00:00Z",
"updated_at": "2026-10-09T14:00:00Z"
}Containers
A drum, tote, box or cart on a job, from the moment it's opened until it ships. At a facility it carries a storage status: where it stands against the limit that applies to that container.
The container object · 10 attributes
The container object
idstringrequiredUnique identifier for the container, beginning
cont_.objectstringAlways
container.jobstringThe job's ID.
profilestringThe waste profile in it.
kindstringType and size, for example
55-gal steel drum.statusenumWhere the container is.
openfullreceivedconsolidatedshippedweight_lbintegerNet weight in pounds, once weighed.
storage_statusenumAt a facility: where the container stands against the limit that applies to it. Null before receipt.
within_limitapproaching_limitat_limitreceived_attimestampWhen it was checked in at a facility.
created_attimestampWhen the record was created, ISO 8601 in UTC.
{
"id": "cont_1Dr0Flammables",
"object": "container",
"job": "job_3Lp9VantageOct",
"profile": null,
"kind": "55-gal fiber drum",
"status": "full",
"weight_lb": 210,
"storage_status": null,
"received_at": null,
"created_at": "2026-10-08T14:00:00Z"
}List containers
GET/v1/containers
Returns containers, newest first.
Scope containers:read · Returns 200
Query parameters
jobstringOnly this job's containers.
statusenumOnly containers in this status.
openfullreceivedconsolidatedshippedstorage_statusenumOnly containers with this storage status.
within_limitapproaching_limitat_limitlimitintegerHow many records to return, 1 to 100. Defaults to 25.
starting_afterstringA record ID. Returns the page after it — pass the
next_cursorof the previous page.
curl "https://mercovi.com/api/sandbox/v1/containers?storage_status=approaching_limit" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9"{
"object": "list",
"data": [
{
"id": "cont_7Pt2Paint",
"object": "container",
"job": "job_5Ev2Ridgeview0912",
"profile": "prof_9Wq2Paint",
"kind": "Cubic-yard box",
"status": "received",
"weight_lb": 1140,
"storage_status": "approaching_limit",
"received_at": "2026-09-12T14:00:00Z",
"created_at": "2026-09-12T14:00:00Z"
}
],
"has_more": false,
"next_cursor": null
}Retrieve a container
GET/v1/containers/{id}
Returns one container.
Scope containers:read · Returns 200
Path parameters
idstringrequiredThe container's ID, beginning
cont_.
curl "https://mercovi.com/api/sandbox/v1/containers/cont_1Dr0Flammables" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9"{
"id": "cont_1Dr0Flammables",
"object": "container",
"job": "job_3Lp9VantageOct",
"profile": null,
"kind": "55-gal fiber drum",
"status": "full",
"weight_lb": 210,
"storage_status": null,
"received_at": null,
"created_at": "2026-10-08T14:00:00Z"
}Update a container
PATCH/v1/containers/{id}
Records a weight or moves the container to its next status.
Scope containers:write · Returns 200
Path parameters
idstringrequiredThe container's ID, beginning
cont_.
Body
statusenumThe next status. Moves forward only.
openfullreceivedconsolidatedshippedweight_lbintegerNet weight in pounds.
curl -X PATCH "https://mercovi.com/api/sandbox/v1/containers/cont_3Dr0Oxidizers" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9" \
-H "Content-Type: application/json" \
-d '{
"status": "full",
"weight_lb": 85
}'{
"id": "cont_3Dr0Oxidizers",
"object": "container",
"job": "job_3Lp9VantageOct",
"profile": "prof_6Ox4Oxidizer",
"kind": "30-gal metal drum",
"status": "full",
"weight_lb": 85,
"storage_status": null,
"received_at": null,
"created_at": "2026-10-08T14:00:00Z"
}Manifests
A hazardous waste manifest built from a job's containers and profiles. Each manifest carries its EPA e-Manifest status, submission method and tracking number on the record. In the sandbox, tracking numbers are samples.
The manifest object · 11 attributes
The manifest object
idstringrequiredUnique identifier for the manifest, beginning
man_.objectstringAlways
manifest.jobstringThe job's ID.
manifest_tracking_numberstringThe manifest tracking number: nine digits and three letters.
statusenumWhere the manifest is.
draftready_for_signaturesubmittedsignedcorrectedsubmission_typeenumThe EPA e-Manifest submission method.
FullElectronicHybridDataImage5CopyImagegeneratorobjectnameandepa_id.designated_facilityobjectnameandepa_id.linesarrayOne per waste:
profile,dot_description,waste_codes,containers,quantity,unit.created_attimestampWhen the record was created, ISO 8601 in UTC.
updated_attimestampWhen the record last changed, ISO 8601 in UTC.
{
"id": "man_0Mt1Ridgeview",
"object": "manifest",
"job": "job_5Ev2Ridgeview0912",
"manifest_tracking_number": "000123456ELC",
"status": "signed",
"submission_type": "Hybrid",
"generator": {
"name": "Ridgeview County HHW program",
"epa_id": "NJD987654321"
},
"designated_facility": {
"name": "Ashfield Chemical Recovery",
"epa_id": "OHD000111222"
},
"lines": [
{
"profile": "prof_9Wq2Paint",
"dot_description": "UN1263, Waste Paint, 3, PG II",
"waste_codes": [
"D001"
],
"containers": 1,
"quantity": 1140,
"unit": "P"
},
{
"profile": "prof_1Ac7Batteries",
"dot_description": "UN2794, Batteries, wet, filled with acid, 8, PG III",
"waste_codes": [
"D002",
"D008"
],
"containers": 1,
"quantity": 620,
"unit": "P"
}
],
"created_at": "2026-09-12T14:00:00Z",
"updated_at": "2026-09-13T14:00:00Z"
}List manifests
GET/v1/manifests
Returns manifests, newest first.
Scope manifests:read · Returns 200
Query parameters
jobstringOnly this job's manifests.
statusenumOnly manifests in this status.
draftready_for_signaturesubmittedsignedcorrectedlimitintegerHow many records to return, 1 to 100. Defaults to 25.
starting_afterstringA record ID. Returns the page after it — pass the
next_cursorof the previous page.
curl "https://mercovi.com/api/sandbox/v1/manifests?status=signed" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9"{
"object": "list",
"data": [
{
"id": "man_0Mt1Ridgeview",
"object": "manifest",
"job": "job_5Ev2Ridgeview0912",
"manifest_tracking_number": "000123456ELC",
"status": "signed",
"submission_type": "Hybrid",
"generator": {
"name": "Ridgeview County HHW program",
"epa_id": "NJD987654321"
},
"designated_facility": {
"name": "Ashfield Chemical Recovery",
"epa_id": "OHD000111222"
},
"lines": [
{
"profile": "prof_9Wq2Paint",
"dot_description": "UN1263, Waste Paint, 3, PG II",
"waste_codes": [
"D001"
],
"containers": 1,
"quantity": 1140,
"unit": "P"
},
{
"profile": "prof_1Ac7Batteries",
"dot_description": "UN2794, Batteries, wet, filled with acid, 8, PG III",
"waste_codes": [
"D002",
"D008"
],
"containers": 1,
"quantity": 620,
"unit": "P"
}
],
"created_at": "2026-09-12T14:00:00Z",
"updated_at": "2026-09-13T14:00:00Z"
}
],
"has_more": false,
"next_cursor": null
}Create a manifest
POST/v1/manifests
Builds a draft manifest from a job: one line per profile, waste codes from the contents.
Scope manifests:write · Returns 201
Body
jobstringrequiredThe job's ID.
designated_facilitystringrequiredThe receiving facility's EPA ID.
submission_typeenumDefaults to
Hybrid.FullElectronicHybridDataImage5CopyImage
curl -X POST "https://mercovi.com/api/sandbox/v1/manifests" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"job": "job_6Cp4HaldenNov",
"designated_facility": "OHD000111222",
"submission_type": "Hybrid"
}'{
"id": "man_7Gx2Sample0001",
"object": "manifest",
"job": "job_6Cp4HaldenNov",
"manifest_tracking_number": null,
"status": "draft",
"submission_type": "Hybrid",
"generator": {
"name": "Halden Manufacturing",
"epa_id": "OHD987654321"
},
"designated_facility": {
"name": "Ashfield Chemical Recovery",
"epa_id": "OHD000111222"
},
"lines": [],
"created_at": "2026-10-09T14:00:00Z",
"updated_at": "2026-10-09T14:00:00Z"
}Retrieve a manifest
GET/v1/manifests/{id}
Returns one manifest with its lines.
Scope manifests:read · Returns 200
Path parameters
idstringrequiredThe manifest's ID, beginning
man_.
curl "https://mercovi.com/api/sandbox/v1/manifests/man_0Mt1Ridgeview" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9"{
"id": "man_0Mt1Ridgeview",
"object": "manifest",
"job": "job_5Ev2Ridgeview0912",
"manifest_tracking_number": "000123456ELC",
"status": "signed",
"submission_type": "Hybrid",
"generator": {
"name": "Ridgeview County HHW program",
"epa_id": "NJD987654321"
},
"designated_facility": {
"name": "Ashfield Chemical Recovery",
"epa_id": "OHD000111222"
},
"lines": [
{
"profile": "prof_9Wq2Paint",
"dot_description": "UN1263, Waste Paint, 3, PG II",
"waste_codes": [
"D001"
],
"containers": 1,
"quantity": 1140,
"unit": "P"
},
{
"profile": "prof_1Ac7Batteries",
"dot_description": "UN2794, Batteries, wet, filled with acid, 8, PG III",
"waste_codes": [
"D002",
"D008"
],
"containers": 1,
"quantity": 620,
"unit": "P"
}
],
"created_at": "2026-09-12T14:00:00Z",
"updated_at": "2026-09-13T14:00:00Z"
}Invoices
The bill a closed job writes. Amounts are integers in cents.
Endpoints
The invoice object · 11 attributes
The invoice object
idstringrequiredUnique identifier for the invoice, beginning
inv_.objectstringAlways
invoice.numberstringThe invoice number on the document.
clientstringThe client's ID.
jobsarrayThe job IDs it bills.
statusenumWhere the invoice is.
draftopenpaidvoidcurrencystringThree-letter ISO code, lowercase.
amount_dueintegerTotal in cents.
amount_paidintegerPaid so far, in cents.
due_datedateWhen payment is due.
created_attimestampWhen the record was created, ISO 8601 in UTC.
{
"id": "inv_1040Ridgeview",
"object": "invoice",
"number": "1040",
"client": "cli_2Rv8Ridgeview",
"jobs": [
"job_5Ev2Ridgeview0912"
],
"status": "open",
"currency": "usd",
"amount_due": 1846500,
"amount_paid": 0,
"due_date": "2026-10-15",
"created_at": "2026-09-15T14:00:00Z"
}List invoices
GET/v1/invoices
Returns invoices, newest first.
Scope invoices:read · Returns 200
Query parameters
clientstringOnly this client's invoices.
statusenumOnly invoices in this status.
draftopenpaidvoidlimitintegerHow many records to return, 1 to 100. Defaults to 25.
starting_afterstringA record ID. Returns the page after it — pass the
next_cursorof the previous page.
curl "https://mercovi.com/api/sandbox/v1/invoices?status=open" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9"{
"object": "list",
"data": [
{
"id": "inv_1040Ridgeview",
"object": "invoice",
"number": "1040",
"client": "cli_2Rv8Ridgeview",
"jobs": [
"job_5Ev2Ridgeview0912"
],
"status": "open",
"currency": "usd",
"amount_due": 1846500,
"amount_paid": 0,
"due_date": "2026-10-15",
"created_at": "2026-09-15T14:00:00Z"
}
],
"has_more": false,
"next_cursor": null
}Retrieve an invoice
GET/v1/invoices/{id}
Returns one invoice.
Scope invoices:read · Returns 200
Path parameters
idstringrequiredThe invoice's ID, beginning
inv_.
curl "https://mercovi.com/api/sandbox/v1/invoices/inv_1040Ridgeview" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9"{
"id": "inv_1040Ridgeview",
"object": "invoice",
"number": "1040",
"client": "cli_2Rv8Ridgeview",
"jobs": [
"job_5Ev2Ridgeview0912"
],
"status": "open",
"currency": "usd",
"amount_due": 1846500,
"amount_paid": 0,
"due_date": "2026-10-15",
"created_at": "2026-09-15T14:00:00Z"
}Webhook endpoints
A URL of yours that Mercovi calls when something happens. Each delivery is signed so you can check it came from Mercovi.
The webhook_endpoint object · 7 attributes
The webhook_endpoint object
idstringrequiredUnique identifier for the endpoint, beginning
we_.objectstringAlways
webhook_endpoint.urlstringrequiredAn HTTPS URL.
eventsarrayEvent types to send, or
["*"]for all.secretstringThe signing secret, beginning
whsec_. Returned once, on create.enabledbooleanWhether deliveries are on.
created_attimestampWhen the record was created, ISO 8601 in UTC.
{
"id": "we_4Hk9Erp",
"object": "webhook_endpoint",
"url": "https://erp.example.com/hooks/mercovi",
"events": [
"manifest.signed",
"invoice.sent",
"invoice.paid"
],
"secret": null,
"enabled": true,
"created_at": "2026-09-01T14:00:00Z"
}List webhook endpoints
GET/v1/webhook_endpoints
Returns your endpoints.
Scope webhooks:read · Returns 200
Query parameters
limitintegerHow many records to return, 1 to 100. Defaults to 25.
starting_afterstringA record ID. Returns the page after it — pass the
next_cursorof the previous page.
curl "https://mercovi.com/api/sandbox/v1/webhook_endpoints" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9"{
"object": "list",
"data": [
{
"id": "we_4Hk9Erp",
"object": "webhook_endpoint",
"url": "https://erp.example.com/hooks/mercovi",
"events": [
"manifest.signed",
"invoice.sent",
"invoice.paid"
],
"secret": null,
"enabled": true,
"created_at": "2026-09-01T14:00:00Z"
}
],
"has_more": false,
"next_cursor": null
}Create a webhook endpoint
POST/v1/webhook_endpoints
Registers a URL. The response carries the signing secret — store it; it isn't shown again.
Scope webhooks:write · Returns 201
Body
urlstringrequiredAn HTTPS URL.
eventsarrayrequiredEvent types, or
["*"].
curl -X POST "https://mercovi.com/api/sandbox/v1/webhook_endpoints" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"url": "https://erp.example.com/hooks/mercovi",
"events": [
"manifest.signed",
"invoice.sent"
]
}'{
"id": "we_7Gx2Sample0001",
"object": "webhook_endpoint",
"enabled": true,
"secret": "whsec_7Gx2Sample00017Gx2Sample0001",
"created_at": "2026-10-09T14:00:00Z",
"url": "https://erp.example.com/hooks/mercovi",
"events": [
"manifest.signed",
"invoice.sent"
]
}Delete a webhook endpoint
DEL/v1/webhook_endpoints/{id}
Stops deliveries and removes the endpoint.
Scope webhooks:write · Returns 200
Path parameters
idstringrequiredThe endpoint's ID, beginning
we_.
curl -X DELETE "https://mercovi.com/api/sandbox/v1/webhook_endpoints/we_4Hk9Erp" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9"{
"id": "we_4Hk9Erp",
"object": "webhook_endpoint",
"deleted": true
}Events
A record of something that happened. The same object is what a webhook delivers, and you can list recent events to catch up after downtime.
Endpoints
The event object · 5 attributes
The event object
idstringrequiredUnique identifier for the event, beginning
evt_.objectstringAlways
event.typeenumWhat happened.
job.createdjob.scheduledjob.closedstaff.acceptedcontainer.receivedcontainer.storage_status_changedmanifest.ready_for_signaturemanifest.signedprofile.approvedprofile.expiringinvoice.sentinvoice.paiddataobjectobject: the record as it was right after the change.created_attimestampWhen the record was created, ISO 8601 in UTC.
{
"id": "evt_8Qs1ManSigned",
"object": "event",
"type": "manifest.signed",
"data": {
"object": {
"id": "man_0Mt1Ridgeview",
"object": "manifest",
"job": "job_5Ev2Ridgeview0912",
"manifest_tracking_number": "000123456ELC",
"status": "signed",
"submission_type": "Hybrid",
"generator": {
"name": "Ridgeview County HHW program",
"epa_id": "NJD987654321"
},
"designated_facility": {
"name": "Ashfield Chemical Recovery",
"epa_id": "OHD000111222"
},
"lines": [
{
"profile": "prof_9Wq2Paint",
"dot_description": "UN1263, Waste Paint, 3, PG II",
"waste_codes": [
"D001"
],
"containers": 1,
"quantity": 1140,
"unit": "P"
},
{
"profile": "prof_1Ac7Batteries",
"dot_description": "UN2794, Batteries, wet, filled with acid, 8, PG III",
"waste_codes": [
"D002",
"D008"
],
"containers": 1,
"quantity": 620,
"unit": "P"
}
],
"created_at": "2026-09-12T14:00:00Z",
"updated_at": "2026-09-13T14:00:00Z"
}
},
"created_at": "2026-09-13T14:00:00Z"
}List events
GET/v1/events
Returns recent events, newest first.
Scope events:read · Returns 200
Query parameters
typestringOnly this event type.
limitintegerHow many records to return, 1 to 100. Defaults to 25.
starting_afterstringA record ID. Returns the page after it — pass the
next_cursorof the previous page.
curl "https://mercovi.com/api/sandbox/v1/events?type=manifest.signed" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9"{
"object": "list",
"data": [
{
"id": "evt_8Qs1ManSigned",
"object": "event",
"type": "manifest.signed",
"data": {
"object": {
"id": "man_0Mt1Ridgeview",
"object": "manifest",
"job": "job_5Ev2Ridgeview0912",
"manifest_tracking_number": "000123456ELC",
"status": "signed",
"submission_type": "Hybrid",
"generator": {
"name": "Ridgeview County HHW program",
"epa_id": "NJD987654321"
},
"designated_facility": {
"name": "Ashfield Chemical Recovery",
"epa_id": "OHD000111222"
},
"lines": [
{
"profile": "prof_9Wq2Paint",
"dot_description": "UN1263, Waste Paint, 3, PG II",
"waste_codes": [
"D001"
],
"containers": 1,
"quantity": 1140,
"unit": "P"
},
{
"profile": "prof_1Ac7Batteries",
"dot_description": "UN2794, Batteries, wet, filled with acid, 8, PG III",
"waste_codes": [
"D002",
"D008"
],
"containers": 1,
"quantity": 620,
"unit": "P"
}
],
"created_at": "2026-09-12T14:00:00Z",
"updated_at": "2026-09-13T14:00:00Z"
}
},
"created_at": "2026-09-13T14:00:00Z"
}
],
"has_more": false,
"next_cursor": null
}Retrieve an event
GET/v1/events/{id}
Returns one event.
Scope events:read · Returns 200
Path parameters
idstringrequiredThe event's ID, beginning
evt_.
curl "https://mercovi.com/api/sandbox/v1/events/evt_8Qs1ManSigned" \
-H "Authorization: Bearer mk_test_sbx_Ridgeview7Hq2Lk4Wd9"{
"id": "evt_8Qs1ManSigned",
"object": "event",
"type": "manifest.signed",
"data": {
"object": {
"id": "man_0Mt1Ridgeview",
"object": "manifest",
"job": "job_5Ev2Ridgeview0912",
"manifest_tracking_number": "000123456ELC",
"status": "signed",
"submission_type": "Hybrid",
"generator": {
"name": "Ridgeview County HHW program",
"epa_id": "NJD987654321"
},
"designated_facility": {
"name": "Ashfield Chemical Recovery",
"epa_id": "OHD000111222"
},
"lines": [
{
"profile": "prof_9Wq2Paint",
"dot_description": "UN1263, Waste Paint, 3, PG II",
"waste_codes": [
"D001"
],
"containers": 1,
"quantity": 1140,
"unit": "P"
},
{
"profile": "prof_1Ac7Batteries",
"dot_description": "UN2794, Batteries, wet, filled with acid, 8, PG III",
"waste_codes": [
"D002",
"D008"
],
"containers": 1,
"quantity": 620,
"unit": "P"
}
],
"created_at": "2026-09-12T14:00:00Z",
"updated_at": "2026-09-13T14:00:00Z"
}
},
"created_at": "2026-09-13T14:00:00Z"
}