Agent API
CommonTape lets an AI agent discover, quote, hold, book, modify, cancel and check the status of a home-services job on a customer’s behalf, against the rules each business sets. It is free to use, and payment-agnostic: the customer pays the business directly. CommonTape is pre-launch, so real merchants are limited for now.
Connect
- REST:
POST https://commontape.com/api/v1/<operation>with a JSON body. Spec: /api/v1/openapi.json. - MCP:
https://commontape.com/api/mcp(Streamable HTTP, stateless). Same eight tools and the same key. - Auth:
Authorization: Bearer <key>. Keys are issued by CommonTape; to request one, email hello@commontape.com.
Try it with the demo key
You can try the whole path, from search to booked, without writing to us. The demo key reaches only CommonTape Demo Cleaning (commontape-demo-cleaning), a pretend business in ZIP 10001. It never reaches a real business, and nobody is sent anything.
- Demo key:
ct_demo_nQz4WaXqJvMoL8A6FYCaRtpJnsF7UrZh - Search ZIP 10001. Search and quote show the demo business’s services, prices and open times.
- A plain job books at once. A job that needs the business’s yes (a special request, or a task it asks about first) waits; it says yes the first time you check status.
- Use made-up names, phone numbers and addresses. Demo jobs are deleted after about a day.
- Everyone shares this key: 30 calls a minute, 20 open holds and 200 bookings a day across all users. Use a new UUID as the Idempotency-Key for every write. For your own key, email hello@commontape.com.
curl -X POST https://commontape.com/api/v1/search \
-H "Authorization: Bearer ct_demo_nQz4WaXqJvMoL8A6FYCaRtpJnsF7UrZh" \
-H "Content-Type: application/json" \
-d '{"zip":"10001","inputs":{"bedrooms":2,"bathrooms":1}}'curl -X POST https://commontape.com/api/v1/hold \
-H "Authorization: Bearer ct_demo_nQz4WaXqJvMoL8A6FYCaRtpJnsF7UrZh" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"quote_token":"<a match's quote.quote_token>",
"allocation_id":"<one of its available_windows[].allocation_id>"}'Operations
search— start here. Describe the job once and get the businesses that can do it:matches, each with one exact price, aquote_tokenand bookable windows;needs_more, the businesses that take the job but whose price needs something the job did not state (each with aneedslist: thefield, a plainwhy, thereasonit is needed (required_for_pricing,required_for_eligibilityorrequired_for_crew) and thepathto send it at, e.g.property.spaces.space.bedroom); andnear_misseswith thereasonsthey missed, each a plain code and a one-line label. Body:zip, optionalservice_key, the home inproperty, price inputs such as hours ininputs, and words from the word list inmust_have,nice_to_haveandmust_not. Anything not on the word list goes inspecial_requestsand always needs the owner’s approval. A business is never assumed to do something it has not answered. The answer reads the job back in plain lines (job.read_back), so you can check what it was matched and priced on. Every answer carriestaxonomy_version, the word list it was made under. A bare appliance word ("fridge", "oven") is refused with the question to ask: inside, outside or both.owner_must_quotelists the businesses that take the job but whose owner quotes it (a plainwhy, no price, nothing to hold).- Which rooms — optional
scopeon search and quote: either{"leave_out": ["space.office"]}or{"clean_only": ["space.kitchen", "space.bathroom.full"]}, never both, withspace.*keys from the word list. Under aby_roomsprice a room left out comes off the price (its line is gone); a fixed price, and every other price, does not change. It is signed into the quote, kept with the job and shown to the owner ("Leave out: Office"). Refused: a room the home has none of, and a must-have task in a room left out. - The home — state it once, in
property, with the words underpropertyin the word list:type(kind of home, e.g.property.apartment),occupancy(occupied or vacant),furnishing,floor_level(the street-level floor is 1),floorsinside the home,size(square_feet, withapproximate: truefor a rough guess) andspaces: how many of each kind of room, e.g.{"space.bedroom": 2, "space.bathroom.full": 2, "space.bathroom.half": 1, "space.family_room": 1, "space.formal_living": 1}. Every kind of home and room has one fixed meaning: a half bathroom (powder room) has no shower or bath; a TV lounge is a family room; a drawing room is a formal living room. A room that exists is a fact about the home, never a request to clean it: cleaning goes inmust_haveas a task word. A misspelled word is refused with the word probably meant, an unclear one ("bathroom", "house") with the question to ask, and nothing is ever dropped or guessed. Bedrooms, square feet and (when the home has no half bathroom) bathrooms feed every price that uses them, so there is no need to repeat them ininputs; a business that charges per bathroom asks for the bathroom count when the home has a half bathroom. A business pricedby_rooms(a base price plus a price per kind of room:space.bedroom,space.bathroom.full,space.bathroom.half,space.living_area= living, family and formal living rooms together,space.kitchen,space.balcony,space.garage) reads the counts fromspacesonly and answers one price line per kind, keyed by the space key; a kind the home did not state is a need on that key (state 0 when there is none). A fixed price may say what it covers (covers: most bedrooms, full and half bathrooms, living areas, square feet, rooms in total): a home over a limit, or a service pricedowner_quotes, answersowner_must_quotewith a plain why — the owner must quote it; no amount, no token, nothing to hold. A business priced bypackagesoffers 2 to 5 fixed prices, each for a size of home (the same limits, and optionally the kinds of home it is for);discoverlists them. The price is the cheapest package that fits the whole home, never the job’sscope. A package may also include tasks (includes): a must-have task (inputs.addons) the business does not offer on its own is quoted only in a package that includes it, packages are compared by price plus the extra-cost tasks each leaves out, and a task the picked package includes is not charged. When no package with the task fits the home, the business is a near-miss withtask_not_offerednaming the package; a limited fact the home did not state is a need, and a home no package covers isowner_must_quote. A ready quote names itspackage(key and the owner’s label). On search these businesses are listed underowner_must_quote, never as a match or a near-miss. The home rides with the job to the booking. discover— a merchant’s services, each with itspricing_model(a service pricedby_sqftneeds the home’ssquare_feet, a whole number from 100 to 10,000, as the customer states it) and the exactinputsit needs, plus add-ons, the businesstimezone,area(ZIPs, with town and state inarea_details) and hours in that time zone (aclose_timeearlier thanopen_timeends the next day). Each service hasbooking_hours, the length of one booking. Call this first. Body:merchant_slug.quote— eitherstatus: "ready"— one exact price, an itemizedbreakdown,expires_atand a signedquote_token— orstatus: "incomplete"with aneedslist of exactly which inputs are still missing. Never a range. Inputs:bedrooms,bathrooms,square_feet,hours,people,quantities,frequency(once, weekly, biweekly, monthly),addons,conditions,customer_zip, and the home inpropertyas on search (answered back ashome). Unknown inputs are rejected. Bookable windows (allocation_id) come with either answer.- Job conditions — one fixed list, each with one meaning (the same words as the word list):
- When the job is
cond.same_day— Same-day job: The job starts on the same calendar day it is booked, in the business time zone. (word list 2)cond.next_day— Next-day job: The job starts on the calendar day after it is booked, in the business time zone. (word list 2)cond.weekend— Weekend job: The job starts on a Saturday or Sunday, in the business time zone. (word list 2)cond.evening— Evening job: The job starts at 6:00 pm or later, in the business time zone. (word list 2)cond.holiday— Holiday job: The job starts on a US federal holiday (the eleven official days), on the date it is observed, in the business time zone. A holiday that lands on a Saturday is observed the Friday before; on a Sunday, the Monday after. (word list 2)
- Getting in and getting there
cond.no_free_parking— No free parking: There is no free place to park within a short walk of the home, or parking must be paid for. (word list 2)cond.stairs_no_lift— Stairs with no lift: The home is on the third floor or higher and the building has no lift. (word list 2)cond.gated_entry— Gated or front-desk entry: A gate, doorman, concierge or front desk must let the cleaner in. (word list 4)cond.lockbox_entry— Key, code or lockbox entry: The cleaner lets themselves in with a key, door code or lockbox left for them. (word list 4)cond.difficult_access— Hard to get to: Getting the cleaner and their kit to the door is hard for a reason other than stairs, parking or a gate: a long walk from the nearest place to stop, a steep path or many outside steps, or a narrow or blocked way in. (word list 5)
- Who and what is in the home
cond.pets— Pets in the home: A dog or cat lives in the home, whether or not it is there during the job. (word list 2)cond.nobody_home— Nobody home: No one will be there during the job. (word list 2)cond.infestation— Pests in the home: Insects, rodents or bed bugs are present, or the home was treated for them in the last month. The customer says so before booking. (word list 4)cond.smoking_household— Smoking household: Someone smokes or vapes indoors, so surfaces carry smoke residue or smell. (word list 5)cond.excess_pet_hair— Lots of pet hair: Pet hair covers floors, furniture or bedding throughout the home, more than one normal vacuum pass removes. (word list 5)cond.occupied_home— Someone home during the job: People will be in the home during the job, so the cleaner works around them. (word list 5)
- How dirty the home is
cond.very_dirty— Very dirty home: The home has not been cleaned in three months or more, or has heavy build-up. The customer says so before booking. (word list 2)cond.mould— Mould in the home: Visible mould on walls, ceilings, grout or around fittings that the customer wants dealt with, beyond a wipe-down. (word list 4)cond.post_construction— After building work: Dust and debris left by building or renovation work, beyond ordinary dirt. (word list 4)cond.moderately_dirty— Moderately dirty home: The home was last cleaned more than four weeks and less than three months ago, or has build-up beyond a regular clean but less than very dirty. The customer says so before booking. (word list 5)cond.heavy_grease— Heavy kitchen grease: The kitchen has thick, sticky grease on the stovetop, hood, cabinets or walls that a normal wipe does not lift. (word list 5)
- Health and safety
cond.bodily_fluids— Bodily fluids: The job includes cleaning up blood, vomit, urine or faeces beyond a normal bathroom clean, at household scale (an illness, a pet or toilet accident), that ordinary disinfectant and gloves can deal with. (word list 5)cond.biohazard— Biohazard: The home has material that can spread disease and needs protective gear and regulated disposal: large amounts of blood or bodily fluids (after a death, a serious injury or a crime), used needles or sharps, or human or animal waste built up over time (hoarding). State biohazard alone for this; bodily fluids is for household-scale mess. (word list 5)cond.hazardous_waste— Hazardous waste: The job includes handling, moving or disposing of things that may not go in household trash and need special handling or a licensed remover: paint, solvents, pool or garden chemicals, pesticides, car fluids, batteries, broken fluorescent bulbs, or material that may contain asbestos or lead. (word list 5)
inputs.conditions. Two that cannot both be true are refused together (cond.nobody_homewithcond.occupied_home;cond.very_dirtywithcond.moderately_dirty): ask the customer which it is. Each business answers no extra charge, an extra charge (its own line in the breakdown), ask first (the booking waits for the owner) or not taken (the business is a near-miss, reasoncondition_not_taken; a direct quote is refused). Day and hour conditions are checked against the window athold: each window lists the ones the quote must state. Anything not on the list is never priced; send it as a special request. - When the job is
- What a business takes — the kinds of home it cleans and the largest home it takes (bedrooms, square feet, bathrooms, floors, garages, balconies, kitchens, rooms in total), shown by
discoverastakesandcrew.max_bedrooms(null= not answered, no limit applied). A job outside them is a near-miss on search with every failed limit listed (home_kind_not_taken,too_many_bedrooms,home_too_large,too_many_bathrooms,too_many_floors, and the most garages, balconies, kitchens and rooms in total it takes:too_many_garages,too_many_balconies,too_many_kitchens,too_many_rooms) and is refused on a direct quote. Reason codes are plain and each carries a label:task_not_offered,task_not_confirmed,kit_not_available,kit_not_confirmed,product_not_available,product_not_confirmed,payment_not_accepted,payment_not_confirmed,payment_only_option,crew_not_available,crew_not_confirmed,crew_only_option, the nine above,condition_not_takenandno_open_slots. A*_not_confirmedcode means the owner has not answered, never a no. Gone: the old codesnot_offered,not_confirmed,not_available,only_option,too_large,no_availabilityandcannot_price(a price failure now lists underneeds_more). - Per service (M-21):
discoverlistsservices[].takes, the limits that apply to that service — the business-widetakesandcrew.max_bedroomswith the service’s own answers on top, so a move-out clean may take bigger homes than a standard clean. Judge a job by them. New limits:min_bedrooms(too_few_bedrooms),max_full_bathrooms(too_many_full_bathrooms),occupancy(occupancy_not_taken) andfurnishing(furnishing_not_taken). The fewest bedrooms, occupancy and furnishing left out of the job are a need; a bare bathroom total above the most full bathrooms asks for the full count; the older limits left out add nothing.ask_first_above(bedrooms, square feet, bathrooms): a bigger home, or one that leaves the size out, books only with the owner’s yes (approval reasonask_first_limit). How not sure reads: a limit intakes.unsureis ask first for every home, never a miss. A service whose task list the owner has not confirmed hasmatchable: falseandreason: scope_not_confirmed: a near miss on search, left out of plain search, and refused on a direct quote (SERVICE_UNKNOWN,reason: scope_not_confirmed). - When a business takes jobs (M-21):
discovercarries a top-levelwhenwith only its plain Nos —when.min_days_ahead(1 = no same-day jobs; 2 = no same-day or next-day jobs, in the business’s calendar days),weekends,evening_starts(6:00 pm or later) andholidays(the eleven US federal holidays, as observed) set tofalse;null= not answered. Such times are never offered. Ahold, or amodifyto a new time, that is too soon answersINSUFFICIENT_NOTICE(422, the samenext_actionsasOUTSIDE_HOURS) witherror.earliest: no start before it is taken (a later time may still be closed, so pick from the quote’s windows); a weekend, evening or holiday No staysOUTSIDE_HOURS. A search where every open time is too soon lists the business as a near missinsufficient_notice. hold— reserve a window from a quote: the quoted job becomesheld, sametransaction_id. Holds expire (hold_expires_at). A quote that ran out, or one replaced by a newer quote of the same job, answersQUOTE_EXPIRED. A price from search can be held directly; it becomes a record when held.book— commit a hold with the customer’s name, phone, service address, optional email and optionalaccess_instructions(a gate or lockbox code, up to 500 characters). The owner sees the address and the code only once the job is theirs, andstatusnever returns them. Returnsbooked, orpending_approvalwith adecide_bytime if the merchant must approve (pollstatus), ordeclined/expired.modify— commit a change to a booked job: quote it again with itstransaction_id(its next version), then modify with that quote, optionally moving it to another window. The job staysbooked; the old version, quote and price are kept. Refused when the new quote changes what the owner must approve, the ZIP, thescope, drops the home (leaves out its kind, size or a room count the booked job stated; new values are allowed; the refusal’s next steps are cancel, then search), leaves out a task the booked job asked for (send every booked task again ininputs.addons), or does not state the window’s day and hour conditions. The owner’s yes covers one version: a change that would not book on its own may not cost more, nor move a job the owner said yes to into another window (cancel and book anew). Every change is a fresh quote of the whole job, so it is judged and priced again against the business’s current offering. The answer, and every refusal, carrieschange: itskinds(a set ofcapacity: the time or the cleaners;commercial: the price or anything priced;eligibility: the home, the ZIP or the conditions;approval: something the owner must say yes to;non_commercial: none of these),rechecked, andpricefrom and to. A change that needs the owner’s yes answersNEEDS_OWNER_APPROVAL. A refused change leaves the booked job exactly as it was; a new time and a new price commit together or not at all. The owner is emailed once about a change they can see. Check the new quote’s price against your customer’s limit before you modify.cancel— cancel your own held, waiting (pending_approval) or booked job. The slot is released. Say why with an optionalreason(customer_changed_plans,customer_booked_elsewhere,booked_in_error,customer_other); the answer and status carrycancellation(who, when, why,fee,policy,capacity_released). CommonTape takes no cancellation fee; any charge is between the customer and the business. A job a day past its window is completed first, so it can no longer be cancelled.status— the authoritative state of one of your jobs and everything needed to act on it: the job (its current version), price, quote, window, approval (what needs the owner’s yes, the deadline, what happens at it), the decline reason and the next open time, how it was finished, the versions it was priced under, andallowed_actions/next_actions. Once a job has a time, status (andbook) carries areceipt: what was booked, from the record (business, service, the window in the business’s time zone, price, the home, and the scope: included, extras, not doing, conditions, what the owner agreed to), withtext, a short plain confirmation to show your customer. A business changing its prices, limits or name later never changes it.
What each tool does to data
Read tools change nothing. Write tools record or hold something that runs out or can be replaced. Sensitive writes commit the customer or cannot be undone: confirm the details with the customer every time before calling one.
search— Read. Finds the businesses that can do a job, with prices and open times. Changes nothing.discover— Read. Reads one business's services, prices and rules. Changes nothing.status— Read. Reads a job's current state and receipt. With the demo key only, a demo job that waits is accepted on this read.taxonomy— Read. Reads the fixed word list. Public, no key.quote— Write. Records a priced job (the quote) so it can be held and booked later. Takes no time slot.hold— Write. Holds one time slot for a few minutes. It runs out on its own if not booked.book— Sensitive write. Commits the customer to the job and sends their name, phone and address to the business. Ask the customer first.modify— Sensitive write. Changes a booked job (time, scope or price). Ask the customer first.cancel— Sensitive write. Cancels a job. It cannot be undone. Ask the customer first.
A job is one record, in one of ten states
From the moment you call quote, the job is one record with a transaction_id that never changes. The job itself is kept as numbered versions: every change (quoting it again with its transaction_id) is a new version beside the old one, never written over. Everything the customer asked for (must-haves, nice-to-haves, must-nots, rooms left out, special requests) is kept with each version and carried to every later step. A search result is not a promise: prices and windows are as of availability_checked_at, and only a booked job is committed. Every quote and booking is stamped with the business’s versions (passport, eligibility, pricing, booking authority, word list).
Every answer says the job’s state, its allowed actions (allowed_actions) and what to do next (next_actions):
quote_incomplete— Not priced yet: the job needs more facts. needs says which. Nothing is held. Allowed actions:quote.quoted— An exact price for one exact job version, until expires_at. Nothing is held: the time is not reserved yet. Allowed actions:hold,quote.held— A time is reserved for this job until hold_expires_at. Book it, or cancel to let it go. Allowed actions:book,cancel.pending_approval— Asked to book; the owner must say yes by approval.deadline. The time stays reserved while waiting. No answer by then and the job expires and the time is released. Allowed actions:status,cancel.booked— The owner is committed to do this job at this price and time. Allowed actions:status,quote,modify,cancel.completed— The job was done. completion.source says who marked it: the owner (merchant) or CommonTape after the window ended (system). Allowed actions:status.declined— The owner said no. decline.reason says why; decline.next_available is the next open time when there is one. Allowed actions:status,quote.cancelled— Called off by the agent. The time was released. Allowed actions:status,quote.supplier_cancelled— Called off by the owner after it was booked. The job can be quoted again elsewhere from job. Allowed actions:status,quote.expired— Ran out of time: the quote passed expires_at, or the hold or the owner's answer passed its deadline. The time was released. Allowed actions:status,quote.
Anything not allowed in a state is refused, never guessed: a cancelled job cannot be booked, a held job cannot be held again, a booked job is changed only through a new quote and modify.
Books on its own, or waits for the owner
Every match and every ready quote carries booking_authority: auto (the business lets it book on its own) or approval_required with its reasons, decided by one rule from the owner’s booking setting, the job and the price. Reasons: owner_approves_every_job (the owner asks every time), over_auto_book_limit (the price is above the owner’s book-for-me limit), business_not_listed, special_request, or the ask-first key itself (for example cond.very_dirty). Read it, never the prose. book may still ask when search said auto (the owner tightened the setting since); it never books on its own when search said the owner must approve, never the other way round.
booking_authority is the business’s say-so only. Your customer’s permission to book or to spend that amount is not something CommonTape knows: you are responsible for your own customer’s say-so before you call book. Once booked, status shows commitment: supplier is rule (booked under the owner’s setting) or merchant_action (the owner said yes), and customer is agent_assertion (your call), never a customer confirmation CommonTape did not see.
Waiting for the owner
A job needs the owner’s yes when booking_authority says so. book then answers pending_approval and status shows approval.needs (what needs approval, each with why), approval.asked_at and the approval.deadline. The time stays reserved while waiting (approval.capacity_held). With no answer by the deadline the job is expired: the job ends and the reserved time is released. You may cancel while waiting; approval.reasons says why it was asked. A no is a business answer, not a failure: it comes back as declined with the owner’s decline.reason, its canonical decline.code (for example schedule_conflict, outside_service_area, insufficient_staff, property_too_large, condition_not_accepted, unable_to_meet_special_request, unsupported_request; the last four only when the job has a stated home size, a stated condition, a special request or an asked-for task), decline.next_actions and decline.next_available.
A booked job becomes completed when the owner marks it done, or 24 hours after its window ends; completion.source says which (merchant or system). An owner calling off a booked job is supplier_cancelled, apart from your cancelled, with the owner’s reason in cancellation. Status then carries recovery: the booked job (as changed, constraints unchanged; send it to search again) and other businesses for it. A change of that job answers SUPPLIER_CANCELLED with the same recovery.
Who sees what: Before the owner says yes they see the customer's first name and initial, the ZIP, the job and the time. After a yes (booked or done) they also see the full name, phone, address and access instructions. A job that never reached the owner, or that ended without a yes, shows nothing of the customer. Agents never get the customer's details back.
Trust: what is known, who vouches for it, until when
Every business in a search answer (matches, needs_more, owner_must_quote and near_misses) carries trust. Each fact in trust.facts says who vouches for it in provenance: commontape_checked (CommonTape checked the evidence itself), or owner_stated (the owner says so; CommonTape has not checked it, and the words are the owner’s own). The kinds are switched_on (CommonTape reviewed the business and switched it on), insurance, licence (the number’s last 4 only) and outside_rating (a rating elsewhere, as the owner typed it until CommonTape checks it). Each fact has as_of and expires_on, its last current day in the business’s time zone. Insurance and a licence are no longer current the day after they are valid through; an outside rating 90 days after its as_of day; switch-on never expires. A fact that has lapsed moves to trust.expired with expired_on: never shown as current, never dropped without saying so. A newer fact of a kind replaces the older one.
trust.history (commontape_observed) is the business’s job record on CommonTape, all time, counted from its jobs on every answer, never typed by anyone: jobs done and who marked them, the owner’s yes and no, jobs called off by the owner, jobs that ran out of time waiting for the owner, and the owner’s median minutes to answer. Each rate comes with the count it is out of; a new business answers zeros. Nothing marks a job done by an agent yet, so that count is 0 for now.
Trust never changes the order of results, the price, who is eligible or what needs the owner’s approval. It is facts for you to weigh: no score, badge or rank.
Examples
curl -X POST https://commontape.com/api/v1/quote \
-H "Authorization: Bearer $COMMONTAPE_KEY" \
-H "Content-Type: application/json" \
-d '{"merchant_slug":"acme-cleaning","service_key":"house_cleaning.standard",
"inputs":{"bedrooms":2,"bathrooms":1,"customer_zip":"10001"}}'curl -X POST https://commontape.com/api/v1/book \
-H "Authorization: Bearer $COMMONTAPE_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"transaction_id":"<from hold>",
"customer":{"name":"Jane Doe","phone":"5551234567","service_address":"1 Main St, New York, NY"}}'Idempotency
Write operations (hold, book, modify, cancel) require an idempotency key, and quote takes an optional one (8–128 characters, a UUID works): header Idempotency-Key or body field idempotency_key. Retrying with the same key returns the original result; reusing a key with a different request returns IDEMPOTENCY_KEY_CONFLICT.
Quotes, holds and approvals
A quote_token expires shortly after it is issued. A hold locks the price: its answer says what it holds (quote_id, price, window, booking_authority), and a change the owner makes after the hold applies to new quotes only. Holds release their slot when they expire. Some merchants approve each booking; book then returns pending_approval and the slot stays held until decide_by.
If the business changed anything since the quote, the hold judges the same job again. Same price: the hold goes ahead. A lower price: it is held and price_changed says so. A higher price: PRICE_CHANGED, with price_changed (old amount, new amount, currency and the price lines that changed) and a fresh quote_token for the same job at the new price; hold that, or send your own ceiling for the call in accept_price_up_to and a new price at or under it is held and reported. A business that stopped taking jobs, or no longer does this job (a must-have it no longer offers), answers BUSINESS_UNAVAILABLE with its reasons, at hold or at book.
One open hold per job per business: holding the same job again at the same business answers HOLD_EXISTS with your open hold’s transaction_id. If you serve several customers, send customer_ref (your own label, never shown to the owner) on search or quote so two customers with the same job are told apart. You may hold the same job at different businesses; cancel the ones you do not book.
When a slot is gone, the business stopped taking jobs, the price moved, a hold ran out or the owner said no, the answer carries alternatives: up to three other times at this business and up to three other businesses, each with its price, window, a quote_token to hold as it is, and why (other_time, other_business, nice_to_have_missed:<word>, needs_owner_approval). An alternative never breaks a must: it never misses a must-have, never offers a must-not and never adds back a task the customer left out. An empty list comes with alternatives_note.
Beside it, if_changed lists up to two businesses that could do the job if the customer gave up one thing: change names it (a must_have word the business said it does not offer, or a must_not word that is its only option) with the business’s reason, and price_if_changed is that changed job’s search price (not the price at earliest_window_start when the business charges for days or hours). An if_changed business cannot be held as it stands: the item carries no quote_token and no allocation_id. Ask the customer; if they agree, search again without that word, which is a new job. You can still quote that business by name, but do not book it with the job unchanged without the customer’s yes. An empty list comes with if_changed_note.
status carries alternatives and if_changed too, on a dead end reached in the last 24 hours: declined (other times here, not the declined one, and other businesses), expired (other times here and, for a job that came from a search, other businesses), cancelled because the business stopped taking jobs, and supplier_cancelled (other businesses only). Status keeps them up to 60 seconds: polls in the same minute read the same list and tokens, and alternatives_worked_out_at says when they were worked out.
Search places full matches before conditional ones (never outranked on price), then most nice-to-haves met, then lowest price; order_keys says so as keys, and each match carries ranking: its position, earliest time, nice-to-haves met of those wanted, whether the owner must approve, and that the ZIP is in its area. CommonTape has no distance; travel says when the ZIP is further out.
Open times fit the job. A business that has said how many jobs it can do at the same time (discover capacity.jobs_at_once) has its open times built from its working hours for the next 14 days: each working day split into bookings of the job’s length, from its start time. Every quote, search match and alternative says that length as job_hours (hourly services: the hours asked, rounded up; other services: the booking length, plus the business’s bigger-homes hours when the home meets its rule, which may need bedrooms or square_feet first). Each offered time is exactly job_hours long, and is offered only while the business has fewer jobs at the same time than it can do; slots_remaining is how many more it can take then. A hold of a time shorter than the job (or longer) answers OUTSIDE_HOURS, and so does a time that is no longer in the business’s hours; quote again for current times. A job longer than the longest job the business takes, or than its working day, is a near miss job_too_long. A change that makes a booked job longer needs a new time of the new length.
Cleaners and the time between jobs. Every quote, search match and alternative also says job_people, how many cleaners the job takes: an hourly price’s people, the business’s usual crew (crew.min), more when the home meets its crew rule, and at least 2 when the job asks for no solo cleaner (null while the quote is incomplete). The crew rule crew.rule may read bedrooms, bathrooms, living spaces or floors, or square feet (by is one of bedrooms, bathrooms, living_areas, floors, square_feet, from from); a quote that needs that fact and does not state it is incomplete, and floors and living spaces are read from property only. A business may say how many cleaners it has in all (capacity.cleaners): jobs at the same time never need more, so a time is offered only when this job’s cleaners fit, and a hold that would pass it answers CAPACITY_FULL. It may also say the time it needs between jobs (capacity.gap_minutes): built times step by the job’s length plus that gap, and two jobs closer together than it count as at the same time. A change that keeps its time but needs more cleaners than are free there answers CAPACITY_FULL: pick a new time.
Errors
Errors are {"error":{"code","message","next_actions","state"}} (MCP: a tool error with the same body). The code is authoritative; the message is for a person; next_actions says what to try; state is the job’s state when the refusal is about it. Job outcomes use ten fixed codes, transaction codes say the job is not in a state that allows the call, and protocol codes cover the request itself:
OUTSIDE_AREA— next:searchOUTSIDE_HOURS— next:select_alternative_window,quoteCAPACITY_FULL— next:select_alternative_window,quoteSERVICE_UNKNOWN— next:searchPRICE_MISMATCH— next:quoteHOLD_EXPIRED— next:quote,select_alternative_windowOWNER_DECLINED— next:select_alternative_window,searchOWNER_UNDID— next:searchSLOT_TAKEN— next:select_alternative_window,quoteREV_STALE— next:quoteQUOTE_EXPIRED— next:quoteINVALID_STATE— next:statusALREADY_BOOKED— next:statusALREADY_CANCELLED— next:status,searchMODIFICATION_CONFLICT— next:statusPRICE_CHANGED— next:hold,searchHOLD_EXISTS— next:status,cancelBUSINESS_UNAVAILABLE— next:searchNEEDS_OWNER_APPROVAL— next:cancel,searchSUPPLIER_CANCELLED— next:searchINSUFFICIENT_NOTICE— next:select_alternative_window,quote
QUOTE_EXPIRED: the quote ran out or a newer one replaced it. INVALID_STATE: not allowed in the job’s state (read status). ALREADY_BOOKED, ALREADY_CANCELLED: said plainly. MODIFICATION_CONFLICT: the job changed while you acted, or the quote is not this job’s. HOLD_EXPIRED means only a hold that ran out. Protocol codes: UNAUTHORIZED (401), RATE_LIMITED (429), INVALID_REQUEST (400), NOT_FOUND (404), IDEMPOTENCY_KEY_CONFLICT (409), TEMPORARY_ERROR (500, safe to retry).
Limits
Each key has a per-minute request limit and a cap on open holds. Holds you let run out unused count too: at five in 24 hours (per key) the next hold answers RATE_LIMITED with retry_after_seconds. A hold that ran out while the owner was deciding does not count, nor does a decline or a cancel. A retry of a hold that already succeeded is always answered. A job belonging to another key is always reported as NOT_FOUND.
Data
You send the customer’s contact details only at book. They go to the merchant and are deleted if the job doesn’t happen. See our Privacy Policy. Public merchant records are at /m/<slug> and /m/<slug>/record.json; machine-readable index: /llms.txt.