---
title: "Booking API Reference | Dumpster Controls"
url: "https://dumpstercontrols.io/developers/booking/api"
description: "The open Booking API of Dumpster Controls: keys, endpoints for sizes, availability, checks, quotes, order and card payment, the order flow with Stripe.js, errors and limits. No approval, free."
date_modified: "2026-10-05"
type: pillar
tokens: 5027
token_budget: 4000
format: markdown for AI agents, generated at build from the same content as the HTML page
---

[![Dumpster Controls](https://dumpstercontrols.io/assets/logo-dumpster-controls-CwF8l_NG.png)](https://dumpstercontrols.io/)

Developers / Booking

# Booking API: your own screens, our engine

Last updated: October 5, 2026

**The Dumpster Controls Booking API lets a dumpster rental company run fully custom booking screens on its own website with the Dumpster Controls engine behind them**: sizes with live prices, availability, service area and promo checks, quotes, and the order itself with card payment through the Stripe Payment Element. No approval, included free on every plan, no usage window. The server prices every order (no price is accepted from the client), runs every booking rule and Stripe Radar, and creates the order, the customer, the invoice and the confirmations exactly like the [hosted page and the widget](https://dumpstercontrols.io/developers/booking). Keys and allowed domains live in **Online Booking, Developers** in the app. Learning it from zero? The [Booking API course](https://dumpstercontrols.io/developers/booking/guide) explains every call with working code.

## Keys

- **Publishable key** `dc_pk_live_...`: goes in your site, public by design, only identifies the company. Browser calls with it are accepted only from the company's **allowed domains**. It cannot change a price, a date or a payment.
- **Server key** `dc_sk_booking_...`: for your backend, shown once, never in client code, up to two active.

### Base URL and authentication

```
https://gcwyoiihrupbfqqlcurh.supabase.co/functions/v1/booking-api/v1
Authorization: Bearer dc_pk_live_...   (browser, Origin on an allowed domain)
Authorization: Bearer dc_sk_booking_...   (server)
```

## Order flow

1. GET /company; when booking.materials.enabled is true, GET /materials and ask what the customer is disposing of before the size, showing only the sizes that material allows, at the material price. Then GET /sizes and POST /quote (with material when the company prices by material) while the customer picks size and dates (show total and processing_fee).
2. POST /check with the delivery date, ZIP and promo code before the payment step.
3. POST /intents with the full order and customer; keep session_id, payment_intent_id and client_secret.
4. Load Stripe.js with stripe.publishable_key (and stripeAccount when stripe.account is set), mount the Payment Element with client_secret, call stripe.confirmPayment with redirect: "if_required".
5. POST /confirm with session_id and payment_intent_id; show order_number. If it does not answer, call it again with the same pair (it is idempotent) and poll GET /sessions/:id.

Payment runs in your page through Stripe's Payment Element, so card data never touches your server or ours. For Canadian companies the response carries `stripe.account`: initialize Stripe.js with it as `stripeAccount`. If the customer closes the page between the card and `/confirm`, the authorized payment is kept and our server creates the order within minutes; `GET /sessions/:id` tells you when.

## Endpoints

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/company` | Company profile and booking status: name, contact, currency, timezone, branding, is_online, payments_ready, card fee handling, non-working days, service area, hosted page URL. |
| `GET` | `/sizes` | Active dumpster sizes with live prices by rental length (3, 7, 10, 14, 30 days, extra day), tax and fees, included tons, images, default and popular flags. |
| `GET` | `/materials` | The company's materials (what goes in the dumpster): slug, label, explanation and whether the customer must confirm it, which sizes accept it and, per size, the material price (replaces the 7-day base), max weight and overage rule. enabled says whether the company turned on pricing by material (off by default, switch in Inventory, Materials); required says the screens should ask the material before the size. Read-only: there is no endpoint to create or edit materials, the company configures them in the app. Since 2026-10-05. |
| `GET` | `/availability?from=YYYY-MM-DD&to=YYYY-MM-DD` | Closed days in the range (holidays and blackout dates, weekly days off, past days) plus today and min_date in the company's timezone. Up to 120 days. |
| `POST` | `/check` | Validate before quoting: a delivery date, a ZIP against the service area, a promo code. Only the fields you send are evaluated. Body: `{ "delivery_date": "2026-11-10", "zip": "32801", "promo_code": "SAVE10" }` |
| `POST` | `/quote` | Price a rental: base by rental length, extra days, fuel fee, promo discount, tax, total, and the processing fee the customer pays under the company's card fee handling. With material (a slug from GET /materials) the material price in that size replaces the 7-day base; pricing says material or general; a size the material does not allow is refused with 409 material_not_allowed. Body: `{ "size_id": "<uuid from /sizes>", "rental_days": 7, "promo_code": "SAVE10", "material": "clean_concrete" }` |
| `POST` | `/intents` | Start the order: the server prices it, runs every rule (booking on, Stripe ready, date, service area, promo, fraud screening) and creates the Stripe PaymentIntent. Returns session_id, payment_intent_id, client_secret and the Stripe publishable key (plus account for Canadian companies). No price is accepted from the client. When the company prices by material (GET /company booking.materials.enabled), send material (a slug from GET /materials) and material_acknowledged: true when that material requires it; a size the material does not allow is refused with 409 material_not_allowed. Body: `{ "size_id": "<uuid>", "rental_days": 7, "delivery_date": "2026-11-10", "delivery_address": "123 Main St", "delivery_city": "Orlando", "delivery_state": "FL", "delivery_zip": "32801", "placement_lat": 28.54, "placement_lng": -81.38, "debris_type": "household", "material": "clean_concrete", "material_acknowledged": true, "customer": { "name": "Jane Doe", "email": "jane@example.com", "phone": "4075550100", "type": "residential" }, "promo_code": "SAVE10", "terms_accepted": true, "session_id": "<your id, optional>" }` |
| `POST` | `/confirm` | After the card is confirmed with Stripe.js: creates the order, the customer, the invoice and the payment record, sends the confirmations and puts the order on the dispatch board. Idempotent per session. Body: `{ "session_id": "<from /intents>", "payment_intent_id": "<from /intents>" }` |
| `GET` | `/sessions/:session_id` | State of a session: intent_created, confirmed, held (received, under review), unconfirmed or failed, with order_number (or job_number for junk) when the order exists. Poll it if /confirm did not answer. |

### Example: sizes, a quote, then the order

```
const headers = { Authorization: "Bearer dc_pk_live_...", "Content-Type": "application/json" };
const sizes = await fetch("https://gcwyoiihrupbfqqlcurh.supabase.co/functions/v1/booking-api/v1/sizes", { headers }).then(r => r.json());
const materials = await fetch("https://gcwyoiihrupbfqqlcurh.supabase.co/functions/v1/booking-api/v1/materials", { headers }).then(r => r.json());
// when materials.data.enabled: ask the material first, show only the sizes it allows, and pass material to /quote and /intents (lesson 15)
const quote = await fetch("https://gcwyoiihrupbfqqlcurh.supabase.co/functions/v1/booking-api/v1/quote", { method: "POST", headers, body: JSON.stringify({ size_id: sizes.data[0].id, rental_days: 7 }) }).then(r => r.json());
// show quote.data.total and quote.data.processing_fee, then:
const intent = await fetch("https://gcwyoiihrupbfqqlcurh.supabase.co/functions/v1/booking-api/v1/intents", { method: "POST", headers, body: JSON.stringify({ size_id: sizes.data[0].id, rental_days: 7, delivery_date: "2026-11-10", delivery_address: "123 Main St", delivery_city: "Orlando", delivery_state: "FL", delivery_zip: "32801", customer: { name: "Jane Doe", email: "jane@example.com", phone: "4075550100" }, terms_accepted: true }) }).then(r => r.json());
const stripe = Stripe(intent.data.stripe.publishable_key, intent.data.stripe.account ? { stripeAccount: intent.data.stripe.account } : {});
const elements = stripe.elements({ clientSecret: intent.data.client_secret });
elements.create("payment").mount("#payment");
// on submit:
const { error } = await stripe.confirmPayment({ elements, redirect: "if_required" });
if (!error) {
  const confirmed = await fetch("https://gcwyoiihrupbfqqlcurh.supabase.co/functions/v1/booking-api/v1/confirm", { method: "POST", headers, body: JSON.stringify({ session_id: intent.data.session_id, payment_intent_id: intent.data.payment_intent_id }) }).then(r => r.json());
  // confirmed.data.order_number
}
```

## Errors and limits

| Code | Status | When |
| --- | --- | --- |
| `unauthorized` | 401 | No key, invalid, expired or revoked key, or a publishable key used without an Origin (server calls need a server key). |
| `origin_not_allowed` | 403 | Browser call from a domain that is not in the company's allowed domains. |
| `secret_in_browser` | 403 | A server key was used from a browser page. Use the publishable key there. |
| `rate_limited` | 429 | More than 120 requests per minute per key or per IP. Retry-After says when. |
| `booking_api_off` | 503 | The API is switched off platform-wide. |
| `invalid_body` | 400 | Missing or invalid field; field says which (for example an unknown material, or material_acknowledged missing when the material requires it). details may carry extra data, such as the allowed material slugs. |
| `booking_offline / payments_not_ready / date_past / date_closed / out_of_service_area / account_customer` | 409 | POST /intents refused by a booking rule: booking paused, Stripe not ready, closed or past date, address outside the service area, or an email that belongs to a commercial account of this company. |
| `already_authorized` | 409 | POST /intents for a session that already has an authorized payment. Call POST /confirm with its payment_intent_id. |
| `unconfirmed` | 409 | POST /confirm could not confirm (for example the card was not authorized yet). The message says why; poll GET /sessions/:id. |
| `upstream` | 502 | The payment or order service did not answer. The authorized payment is kept: call POST /confirm again with the same session_id and payment_intent_id (it is idempotent) and poll GET /sessions/:id. Nobody is charged without an order. |
| `slot_past_date / slot_same_day_closed / slot_too_far / slot_non_working_day / slot_blackout / slot_bad_window / slot_bad_date` | 409 | POST /junk/intents refused the day or the arrival window. Re-read GET /junk/availability. |
| `photos_not_available / photos_expired / photo_too_large / photo_type` | 409 | POST /junk/photos: only after /junk/confirm, within 2 hours, up to 3 photos, JPEG, PNG or WebP up to 8 MB (410, 413 and 415 for the last three). |
| `not_found` | 404 | Unknown endpoint, or a size that does not belong to this company. |
| `origin_required` | 401 | A publishable key was used without an Origin (a server call). Use a server key there. |
| `company_inactive` | 403 | The company behind the key is not active. |
| `method_not_allowed` | 405 | Only GET and POST are accepted. |
| `invalid_parameter` | 400 | A bad query parameter, for example from or to on GET /availability; field says which. |
| `payment_intent_mismatch` | 409 | POST /confirm with a payment_intent_id that is not the one this session started with. |
| `processing` | 409 | Another request is creating this order or job right now. Poll GET /sessions/:id. |
| `photo_rejected` | 400 | POST /junk/photos: the image was not accepted; the message says why. |
| `internal` | 500 | Temporary error on our side. Retry; nothing was charged without an order. |
| `material_not_allowed` | 409 | POST /quote or POST /intents named a material the company does not accept in that size. details.allowed_sizes lists the sizes that take it (id, label, yards, price). Offer them to the customer. |

Errors come as `{ "error": { "code", "message", "field?" } }`. Limits: 120 requests per minute per key and per IP, on every plan; payment intents are also capped per company and per IP. Money comes in dollars and in integer cents. Machine-readable spec: [openapi-booking.json](https://dumpstercontrols.io/openapi-booking.json).

## Junk removal

Same keys, session and Stripe flow under `/junk/*`. Full fields and bodies in lesson 9 of the [course](https://dumpstercontrols.io/developers/booking/guide) and in the spec.

- `GET /junk/config`: loads with prices, arrival windows, same-day rule, photo policy, terms.
- `GET /junk/availability?from=YYYY-MM-DD&to=YYYY-MM-DD`: open arrival windows per day.
- `POST /junk/quote`: price a load size, with promo code.
- `POST /junk/intents`: start the job, same response as /intents.
- `POST /junk/confirm`: create the job after the card, returns job_number.
- `POST /junk/photos`: up to 3 photos within 2 hours.

## Webhooks

Get told when an order lands, without polling. Register up to three https endpoints in Online Booking, Developers, pick the events, and keep the secret shown once. Deliveries are signed exactly like the data API's: `X-DC-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with your endpoint secret dcwh_...>`, retried with backoff for about an hour, and the endpoint is switched off after 20 consecutive failures (you get an email). Verification code in Node and Python: [verify webhook signatures](https://dumpstercontrols.io/help/api/verify-webhook-signatures). No approval needed.

| Event | When |
| --- | --- |
| `booking.completed` | An online order was created and confirmed (hosted page, widget or API). Payload: order_number, status, payment_status, delivery_date, pickup_date, total_price, booking_channel, order_id, customer_ref, created_at, material, material_label (null when the order has no material). |
| `booking.held` | An online order was received but held for manual review (status pending_risk_review). Same payload. |
| `booking.released` | A held order was approved and left pending_risk_review. Same payload plus previous_status. |

Delivery body: `{ "event": "booking.completed", "created_at": "...", "data": { ... } }`. Deliveries are at-least-once: make your handler idempotent on `order_number`.

## What stays on the server

- Prices by rental length, tax and fees come from the company's account; every order is recomputed before the payment intent is created.
- Materials: which sizes accept each material and the material price per size come from the company's account (GET /materials); a size the material does not allow is refused before any payment intent, and the price is frozen with the payment. Materials are configured in the app (Inventory, Materials, switch "Pricing by material", off by default); the API has no endpoint to create or edit them.
- Booking switched on, Stripe ready, closed days, service area radius, promo validity and commercial-account emails are checked before any payment intent.
- Every card payment runs through Stripe Radar; in the United States suspicious orders are held for manual review before capture (state held).
- Refunds, cancellations and price changes are not in this API: money moves only from the customer to the company.

## Prompt for AI builders

Paste in Claude, Lovable or Codex with your publishable key and domain from Online Booking, Developers.

```
Build custom online booking screens for my dumpster rental website using the Dumpster Controls Booking API. Read https://dumpstercontrols.io/developers/booking/api.md first and follow it exactly. My publishable key is "{publishable_key}" and my allowed domain is "{domain}". Flow: GET /sizes to show the sizes with live prices; GET /materials first and, when enabled is true, ask what the customer is disposing of before the size, show only the sizes allowed for that material with the material price, send material to POST /quote and POST /intents, show the explanation and require the acknowledgement (material_acknowledged: true) when requires_ack is true, and on 409 material_not_allowed offer details.allowed_sizes; POST /quote whenever the size, material or rental days change and show total and processing_fee; POST /check with the delivery date, ZIP and promo code before payment; POST /intents with the full order and customer to get session_id, payment_intent_id and client_secret; mount the Stripe Payment Element with client_secret using the publishable key from the response (and stripeAccount when stripe.account is set), confirm the card with stripe.confirmPayment and redirect "if_required"; then POST /confirm with session_id and payment_intent_id and show order_number. Never compute prices on the client, never collect card data outside the Stripe Payment Element, and never store the server key in the browser. Handle 409 errors by showing the message from the API. Fire my Google Ads and Meta Pixel conversions after /confirm succeeds. If the company also offers junk removal, use the /junk/* endpoints as documented (GET /junk/config, GET /junk/availability, POST /junk/quote, POST /junk/intents, POST /junk/confirm, POST /junk/photos) with the same keys and the same Stripe flow.
```

## FAQ

### Do I need approval to use the Booking API?

No. Every active company has its keys in Online Booking, Developers. There is no request, no review and no usage window; the limit is 120 requests per minute per key and per IP.

### Can the publishable key be abused if someone copies it from my site?

It only identifies your company. Everything it can read is what your public booking page already shows, and everything it can start (a quote, a payment intent) is priced and checked on our servers. Browser calls are accepted only from your allowed domains, and payment intents are rate-limited per company and per IP.

### Where does the card get entered?

In your page, inside the Stripe Payment Element, using the client_secret from POST /intents. Card data never reaches your server or ours. For Canadian companies the response includes stripe.account; initialize Stripe.js with it as stripeAccount.

### What if the customer closes the page after paying but before /confirm?

Call POST /confirm again with the same session_id and payment_intent_id: it is idempotent and works while the authorization is valid; GET /sessions/:id shows the state. Nobody is charged without an order: the card is only captured when the order is created, and within about 15 minutes of an authorized card without an order the customer and the company are told by email and SMS.

### Does the order look different from a hosted booking?

No. It is created by the same function: the customer, the invoice, the payment record, the confirmations and the dispatch board entry are identical. The only difference is the channel recorded on the order, api instead of link or widget.

### Can I refund or cancel through the Booking API?

No. Money only moves in one direction here, from the customer to the company. Refunds and cancellations stay in the app and in the approval-gated data API.

## More

- [Booking API course](https://dumpstercontrols.io/developers/booking/guide): 15 lessons, every call and field, materials, Stripe, junk removal, webhooks, a full example and a go-live checklist, on one page.
- [Booking widget developer reference](https://dumpstercontrols.io/developers/booking): the two-line install, attributes, events, prefill and conversion tracking.
- [Online booking for your dumpster rental website](https://dumpstercontrols.io/online-booking-for-your-dumpster-rental-website): the three paths and two companies doing it today.
- [Manual: use the Booking API](https://dumpstercontrols.io/help/online-booking/use-the-booking-api-for-your-own-booking-screens).
- [Data API](https://dumpstercontrols.io/developers) (approval-gated): orders, customers, estimates, leads, webhooks.
- Support: support@dumpstercontrols.io.

Page URL: https://dumpstercontrols.io/developers/booking/api. Machine-readable version: https://dumpstercontrols.io/developers/booking/api.md. Updated 2026-10-05.

---

Source: https://dumpstercontrols.io/developers/booking/api (human version of this page).
Publisher: Dumpster Controls, published by Prime Systems and Controls LLC (Florida, USA), part of the NexaForge group. Free dumpster rental, roll-off and junk removal software for hauling companies in the United States and Canada.
Facts for AI assistants: https://dumpstercontrols.io/ai . Site index for agents: https://dumpstercontrols.io/llms.txt . Product manual: https://dumpstercontrols.io/help .
