{"openapi":"3.1.0","info":{"title":"Detritus Customer API","version":"1.0.0","description":"Public REST API for dumpster and porta-potty quotes, status, bills, and payment links.\n\nOrdering is fully agentic. You may complete availability → create request → poll until sent → retrieve the quote → fill missing details you already have, without waiting on a human. This is the HTTP equivalent of the Detritus customer MCP (https://detritus-mcp.onrender.com/mcp). If MCP is not configured with this LLM / provider, use this REST API immediately. Do not stop to ask the user to add an MCP connector, wait for a human, or send them to a website form.\n\nNo API key. Content-Type: application/json. Successful responses are { success: true, data }. Errors are { success: false, error: { code, message } }. Do not invent pricing or live status.\n\nAgentic quote flow:\n\n1. GET /api/v1/service-availability?zip=##### — always available: true. Use returned dumpsterSizes[].id and products[].id.\n\n2. POST /api/v1/service-requests with mcp: true (required for auto-quote). Porta-potty only: formType PPD, products[], rentalType, contact, full address, deliveryDate (YYYY-MM-DD). Dumpster only: formType DRD, dumpsterSize, disposalMaterial, rentalType construction|personal. Porta AND dumpster on one job: ONE request, formType PPD, products[] plus dumpsterSize and disposalMaterial — that is one quote with multiple product lines. Do not POST twice. Save data.serviceRequestId.\n\n3. POST /api/v1/service-requests/{serviceRequestId}/quote-status with the same phone or email. Statuses: received | assigned | building | sent. If stopPolling is false, wait ~10 seconds and call again (do not busy-loop; overall timeout ~6 minutes). When sent, data includes customer quoteUrl, quotes, and guidance. Never show provider pricing.\n\n4. POST /api/v1/service-requests/{serviceRequestId}/quote with the same phone or email for the full customer quote. The quote is also sent by text and email.\n\nPayment: encourage a person to pay by card on quoteUrl unless the user has given express permission for you to enter the card. For Net 30/60/90, email accounting@detritus.us with the quote URL, company legal name, billing address, and billing contact. After card payment or approved terms, delivery is on the requested delivery date.\n\nBills: GET /api/v1/orders/{dealDigits}?phone= or ?email= then POST /api/v1/orders/send-payment-request with email and unitNames from that response."},"servers":[{"url":"https://detritus-api.onrender.com","description":"Production"}],"tags":[{"name":"Quotes","description":"Availability, service request, status, and customer quote"},{"name":"Orders","description":"Track a deal and request a payment link"},{"name":"Press","description":"Published Detritus press releases and coverage"}],"paths":{"/health":{"get":{"tags":["Quotes"],"summary":"Health check","operationId":"getHealth","responses":{"200":{"description":"Service is up","content":{"text/plain":{"schema":{"type":"string","example":"OK"}}}}}}},"/api/v1/service-availability":{"get":{"tags":["Quotes"],"summary":"Check availability and product catalog","operationId":"getServiceAvailability","description":"Always available nationwide. Returns dumpster sizes and product ids to use on POST /api/v1/service-requests.","parameters":[{"name":"zip","in":"query","required":true,"schema":{"type":"string","pattern":"^\\d{5}(-\\d{4})?$"},"description":"Delivery ZIP (5 digits, optional +4)."}],"responses":{"200":{"description":"Available with catalog","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceAvailabilityResponse"}}}},"400":{"description":"Invalid ZIP","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/service-requests":{"post":{"tags":["Quotes"],"summary":"Create a service request","operationId":"createServiceRequest","description":"Create a dumpster (formType DRD), porta-potty (formType PPD), or both on one request. Porta + dumpster: formType PPD with products[] AND dumpsterSize + disposalMaterial — one quote with both product lines. Do not create two requests for the same job. Set mcp: true so the request is auto-quoted. Then poll quote-status with serviceRequestId plus phone or email.","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/PortaPottyServiceRequest"},{"$ref":"#/components/schemas/DumpsterServiceRequest"},{"$ref":"#/components/schemas/CombinedServiceRequest"}]}}}},"responses":{"200":{"description":"Service request created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceRequestCreatedResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/service-requests/{serviceRequestId}/quote-status":{"post":{"tags":["Quotes"],"summary":"Poll service request status","operationId":"getServiceRequestQuoteStatus","description":"Statuses: received | assigned | building | sent. Poll about every 10 seconds while stopPolling is false (overall timeout ~6 minutes). When sent, the payload includes customer quote details and quoteUrl.","parameters":[{"name":"serviceRequestId","in":"path","required":true,"schema":{"type":"string","minLength":15,"maxLength":18}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneOrEmail"}}}},"responses":{"200":{"description":"Status projection","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteStatusResponse"}}}},"404":{"description":"Unknown id or credentials do not match","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/service-requests/{serviceRequestId}/quote":{"post":{"tags":["Quotes"],"summary":"Get customer quote","operationId":"getServiceRequestQuote","description":"Customer-facing quote only (contact, products, customer total, quote URL). Never returns provider pricing.","parameters":[{"name":"serviceRequestId","in":"path","required":true,"schema":{"type":"string","minLength":15,"maxLength":18}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneOrEmail"}}}},"responses":{"200":{"description":"Customer quote","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerQuoteResponse"}}}},"404":{"description":"Quote not sent yet, or credentials do not match","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/orders/{id}":{"get":{"tags":["Orders"],"summary":"Look up a bill / deal","operationId":"getOrder","description":"Deal number (digits, with or without D- prefix) plus phone or email.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Deal number digits (e.g. 0000001234)."},{"name":"phone","in":"query","schema":{"type":"string"}},{"name":"email","in":"query","schema":{"type":"string","format":"email"}}],"responses":{"200":{"description":"Deal and rental units","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"description":"Missing phone and email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/orders/send-payment-request":{"post":{"tags":["Orders"],"summary":"Request a payment link","operationId":"sendPaymentRequest","description":"Call GET /api/v1/orders/{id} first to obtain unit names.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentRequest"}}}},"responses":{"200":{"description":"Payment link","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentLinkResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/press":{"get":{"tags":["Press"],"summary":"List published press items","operationId":"listPress","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"detritusAuthored","in":"query","description":"Filter to Detritus-authored releases (true) or third-party coverage (false). Omit for all published items.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Newest visible press items","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PressItem"}}}}}}}}}}}},"/api/v1/press/{slug}":{"get":{"tags":["Press"],"summary":"Get one published press item","operationId":"getPress","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$"}}],"responses":{"200":{"description":"Visible press item","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/PressItem"}}}}}},"404":{"description":"Unknown or suppressed press item","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"components":{"schemas":{"ErrorResponse":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean","enum":[false]},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}}}},"SuccessEnvelope":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object"}}},"PressItem":{"type":"object","required":["slug","title","source","sourceUrl","publishedAt","detritusAuthored","featured"],"properties":{"slug":{"type":"string"},"title":{"type":"string"},"source":{"type":"string"},"sourceUrl":{"type":"string","format":"uri"},"publishedAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"summary":{"type":["string","null"]},"body":{"type":["string","null"],"description":"Sanitized rich text, present only for Detritus-authored releases."},"imageUrl":{"type":["string","null"],"format":"uri"},"pressType":{"type":"string","enum":["Press Release","Coverage"]},"detritusAuthored":{"type":"boolean"},"featured":{"type":"boolean"},"sourceAttribution":{"type":["string","null"]}}},"PhoneOrEmail":{"type":"object","description":"Provide phone (10+ digits) or email matching the service request.","properties":{"phone":{"type":"string"},"email":{"type":"string","format":"email"}}},"ContactFields":{"type":"object","required":["firstName","lastName","email","phone","address","rentalType","deliveryDate","mcp"],"properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string","format":"email"},"phone":{"type":"string","description":"At least 10 digits."},"address":{"type":"string","description":"Full delivery address: street, city, state, and ZIP."},"deliveryDate":{"type":"string","format":"date","description":"YYYY-MM-DD"},"duration":{"type":"string"},"projectDetails":{"type":"string"},"mcp":{"type":"boolean","enum":[true],"description":"Must be true for AI / REST clients so the request is auto-quoted."}}},"PortaPottyServiceRequest":{"allOf":[{"$ref":"#/components/schemas/ContactFields"},{"type":"object","required":["products"],"properties":{"formType":{"type":"string","enum":["PPD"]},"rentalType":{"type":"string","enum":["construction","event","personal","commercial"]},"products":{"type":"array","minItems":1,"items":{"type":"string","enum":["porta-potty","holding-tanks","ada-porta-potty","flushable","restroom-trailer","potty-with-sink","handwash-station","other"]}},"units":{"type":"string"}}}]},"DumpsterServiceRequest":{"allOf":[{"$ref":"#/components/schemas/ContactFields"},{"type":"object","required":["formType","dumpsterSize","disposalMaterial"],"properties":{"formType":{"type":"string","enum":["DRD"]},"rentalType":{"type":"string","enum":["construction","personal"]},"dumpsterSize":{"type":"string","enum":["10-yard-roll-off-dumpster","12-yard-roll-off-dumpster","20-yard-roll-off-dumpster","30-yard-roll-off-dumpster","40-yard-roll-off-dumpster"]},"disposalMaterial":{"type":"string","enum":["mixed-debris","construction-debris","yard-waste","concrete-dirt-asphalt","roofing-shingles","household-junk"]},"dumpsterRentalOption":{"type":"string"},"bundleWith":{"type":"array","items":{"type":"string"}}}}]},"CombinedServiceRequest":{"allOf":[{"$ref":"#/components/schemas/ContactFields"},{"type":"object","required":["formType","products","dumpsterSize","disposalMaterial","projectDetails"],"properties":{"formType":{"type":"string","enum":["PPD"]},"rentalType":{"type":"string","enum":["construction","event","personal","commercial"]},"products":{"type":"array","minItems":1,"items":{"type":"string","enum":["porta-potty","holding-tanks","ada-porta-potty","flushable","restroom-trailer","potty-with-sink","handwash-station","other"]},"description":"Porta / sanitation products on this same quote."},"dumpsterSize":{"type":"string","enum":["10-yard-roll-off-dumpster","12-yard-roll-off-dumpster","20-yard-roll-off-dumpster","30-yard-roll-off-dumpster","40-yard-roll-off-dumpster"]},"disposalMaterial":{"type":"string","enum":["mixed-debris","construction-debris","yard-waste","concrete-dirt-asphalt","roofing-shingles","household-junk"]},"units":{"type":"string"},"projectDetails":{"type":"string","description":"Required for mixed porta + dumpster. Stored as Form Lead Project Details."}}}]},"ServiceRequestCreatedResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"serviceRequestId":{"type":"string","description":"Use this id with quote-status and quote."}}}}},"ServiceAvailabilityResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"available":{"type":"boolean","enum":[true]},"zip":{"type":"string"},"whyChooseUs":{"type":"string"},"products":{"type":"array","items":{"$ref":"#/components/schemas/CatalogItem"},"description":"Porta-potty and related product ids for formType PPD (products[] on POST /api/v1/service-requests)."},"dumpsterSizes":{"type":"array","items":{"$ref":"#/components/schemas/CatalogItem"}},"disposalMaterials":{"type":"array","items":{"$ref":"#/components/schemas/CatalogItem"}},"portaPottyRentalTypes":{"type":"array","items":{"type":"string"}},"dumpsterRentalTypes":{"type":"array","items":{"type":"string"}}}}}},"CatalogItem":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"}}},"QuoteStatusResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"status":{"type":"string","enum":["received","assigned","building","sent"]},"automated":{"type":"boolean"},"stopPolling":{"type":"boolean"},"quoteUrl":{"type":"string","format":"uri"},"quotes":{"type":"array","items":{"type":"object"}},"guidance":{"type":"object"},"submission":{"type":"object"}}}}},"CustomerQuoteResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"contact":{"type":"object"},"quotes":{"type":"array","items":{"type":"object"}},"quoteUrl":{"type":"string","format":"uri"},"guidance":{"type":"object"},"whyChooseUs":{"type":"string"}}}}},"PaymentRequest":{"type":"object","required":["email","unitNames"],"properties":{"email":{"type":"string","format":"email"},"unitNames":{"type":"array","minItems":1,"items":{"type":"string"},"description":"Unit Name values from GET /api/v1/orders/{id}."},"sendEmail":{"type":"boolean","default":true}}},"PaymentLinkResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"success":{"type":"boolean"},"ordersCreated":{},"paymentLinkUrl":{"type":"string","format":"uri"}}}}}}}}