---
title: "Get a quote"
method: POST
path: "/api/v1/quote"
---

# Get a quote

`POST /api/v1/quote`

## Request

- Base URL: `https://commontape.com`
- URL: `https://commontape.com/api/v1/quote`
- Auth: HTTP bearer

Returns status "ready" (one exact price, an itemized breakdown, expires_at and a signed quote_token) or status "incomplete" with a needs list of exactly which inputs are still missing. Never a range. Unknown inputs are rejected. Call discover first: each service lists the inputs it needs. Every quote is a record: the answer carries transaction_id, quote_id and job_version, and state quoted (or quote_incomplete). Quote again with transaction_id to change the job: the next version, a new quote, the same record; everything the customer asked for earlier is carried forward, never dropped. On a booked job that quote is the change modify commits. A job that ended (declined, cancelled, supplier_cancelled, expired, completed) is never revived: quoting it starts a new job. M-21: a job outside the service's limits (discover services[].takes) is refused with its reason; a limit the job leaves out is a need; a home above an ask-first size, or a limit the owner is not sure of, is quoted with booking_authority approval_required (reason takes.ask.<field> or takes.unsure.<field>). A service whose task list the owner has not confirmed (matchable false) answers SERVICE_UNKNOWN with reason scope_not_confirmed.

## Request body

- object
  - `merchant_slug` string, required
  - `service_key` string, required
  - `transaction_id` string, uuid: Optional: quote this job again (its next version).
  - `property` object: The customer's home, stated once in CommonTape's words (see "property" in the word list). Every fact is optional. An unknown fact, a misspelled word, a number out of range or a contradiction is refused in plain words, naming the word probably meant; an unclear word ("bathroom", "house") is refused with the question to ask. A room that exists is a fact, never a request to clean it: cleaning goes in must_have as a task word. 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 in inputs; stated in both with different numbers is refused. The home rides with the job to the booking.
    - `type` 'property.studio' | 'property.apartment' | 'property.condo' | 'property.townhouse' | 'property.detached_house' | 'property.duplex' | 'property.villa' | 'property.other_residential': Kind of home.
    - `occupancy` 'occupied' | 'vacant': occupied: people live there (whether or not anyone is home during the job: that is cond.nobody_home). vacant: nobody lives there.
    - `furnishing` 'furnished' | 'partly_furnished' | 'unfurnished'
    - `floor_level` integer: The floor the home's front door is on; the street-level floor is 1, below it is 0.
    - `floors` integer: Floors inside the home.
    - `size` object
      - `square_feet` integer, required
      - `approximate` boolean: true when the size is the customer's rough guess. Priced as stated; read back as "about".
    - `spaces` object: How many of each kind of room, by space.* key: space.bedroom, space.bathroom.full, space.bathroom.half, space.living_room, space.family_room, space.formal_living, space.kitchen, space.dining_room, space.breakfast_area, space.pantry, space.laundry_room, space.storage_room, space.office, space.foyer, space.hallway, space.staircase, space.balcony, space.patio, space.garage. 0 states that the home has none; leave a kind out when unknown.
  - `inputs` object
    - `bedrooms` integer
    - `bathrooms` integer
    - `square_feet` integer: Size of the home in square feet, as the customer states it (services with pricing_model by_sqft).
    - `hours` number: Hours of work (hourly services).
    - `people` integer: Number of workers (hourly services).
    - `quantities` object: Count per named unit, e.g. {"room": 3}.
    - `frequency` 'once' | 'weekly' | 'biweekly' | 'monthly'
    - `addons` string[]: Add-on keys from discover. Tasks at extra cost use their task word, e.g. task.fridge_interior. These are the must-have tasks to price: search fills them from must_have; on a direct quote send them here, and again on every re-quote of a booked job. A task the service does not offer on its own may be sent when one of its packages includes it; a task the picked package includes is not charged.
    - `conditions` string[]: Job conditions from the fixed list that apply to this job: cond.same_day, cond.next_day, cond.weekend, cond.evening, cond.holiday, cond.pets, cond.no_free_parking, cond.stairs_no_lift, cond.very_dirty, cond.nobody_home. Each has one fixed meaning at GET /api/v1/taxonomy/house_cleaning. A condition adds the business's charge for it, adds nothing, or (answer ask_first or not_answered) needs the owner's approval. State every one that is true. Words not on the list are refused, never priced.
    - `surcharges` string[]: Only for a business saved before the condition list: its own charge keys, listed under inputs.surcharges in discover. Never a cond. key.
    - `customer_zip` string: Required when the business has further-out ZIP codes with a travel charge or longer notice.
  - `scope` object: The rooms the job covers: leave_out (rooms not to clean) or clean_only (the only rooms to clean), never both, by space key from property.spaces in the word list. A must-have task in a room left out is refused. Under a by_rooms price a room left out comes off the price (its line is gone); a fixed, by_home, square-foot or hourly price does not change. It is echoed on every match, needs_more, near miss, quote and hold, and shown to the owner.
    - `leave_out` string[]
    - `clean_only` string[]
  - `special_requests` string[]: Plain-text requests not on the word list. Always need the owner's yes (book answers pending_approval).
  - `idempotency_key` string: Optional. A retry with the same key returns the same quote instead of a second one.
  - `customer_ref` string: Optional. Your own label for your customer. Two holds of the same job at one business are refused (HOLD_EXISTS) unless their customer_ref differs, so send one when you serve several customers. Never shown to the owner.

## Response `200`

Success

- union
  - object: One exact price. Book with quote_token before expires_at.
    - `status` 'ready', required
    - `amount` number, required
    - `currency` string, required
    - `breakdown` object[]: Line items that add up to amount, to the cent.
      - `key` string
      - `label` string
      - `amount` number
    - `assumed` string[]
    - `ask_first` string[]: Stated conditions, and home sizes, the owner must approve. book returns pending_approval.
    - `package` object: Only for a service priced by packages: the package this price is, with includes = the asked-for tasks it includes at no extra charge (M-17c) (the breakdown's first line is its price). Kept with the job, so status shows it too.
      - `key` string
      - `label` string: The owner's own name for it.
      - `includes` string[]: The asked-for tasks (inputs.addons) this package includes: not charged, no extra line. Absent = none.
    - `booking_authority` object: Whether the business lets this job book on its own (auto) or the owner must say yes first (approval_required), decided by one rule from the owner's booking setting, the job and the price; never read it from prose. 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 (not open to agents yet), special_request (a free-text request), or the ask-first key itself (e.g. cond.very_dirty, task.oven_interior). book may still ask when this said auto (the owner tightened the setting since), never the reverse. This is the business's say-so only: your customer's permission to book or spend is yours to keep.
      - `mode` 'auto' | 'approval_required', required
      - `reasons` string[], required
      - `version` integer, required: The owner's booking setting's version (versions.booking_authority).
    - `expires_at` string, date-time, required
    - `quote_token` string, required
    - `available_windows` object[], required
      - `allocation_id` string, uuid
      - `window_start` string, date-time
      - `window_end` string, date-time
      - `slots_remaining` integer: Jobs the business can still take at this time. For a business with jobs_at_once: jobs_at_once minus the most jobs it already has at any moment inside this time; with capacity.cleaners also no more jobs of this size (job_people) than its cleaners left at that moment. Jobs closer together than capacity.gap_minutes count as at the same time.
      - `conditions` string[]: Day and hour conditions (weekend, evening, holiday, same-day, next-day, in the business time zone) this business charges for or asks about on this window, as of the moment of the quote. The quote must state exactly these in inputs.conditions, or hold is refused with PRICE_MISMATCH. Same-day and next-day are read again at hold, so a quote made just before midnight can be refused just after: quote again as the message says.
    - `taxonomy_version` integer: The word list version this was made under (GET /api/v1/taxonomy/house_cleaning returns the current one). A job made before reads as 4.
    - `scope` object: The rooms the job covers: leave_out (rooms not to clean) or clean_only (the only rooms to clean), never both, by space key from property.spaces in the word list. A must-have task in a room left out is refused. Under a by_rooms price a room left out comes off the price (its line is gone); a fixed, by_home, square-foot or hourly price does not change. It is echoed on every match, needs_more, near miss, quote and hold, and shown to the owner.
      - `leave_out` string[]
      - `clean_only` string[]
    - `home` string: The home as CommonTape understood it, in plain words (only when property was stated).
    - `transaction_id` string, uuid: The job's record, from this quote on. Quote again with it to change the job (a new version).
    - `quote_id` string, uuid: This quote, kept with the job version it prices.
    - `quoted_at` string, date-time
    - `availability_checked_at` string, date-time: When the open windows were read. Windows change: a later hold can still find a slot gone (CAPACITY_FULL).
    - `job_hours` integer: How long this job takes on site, in whole 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. For a business that builds its open times from its working hours (discover capacity.jobs_at_once), every offered time is exactly this long.
    - `job_people` integer, nullable: How many cleaners this job takes: an hourly price's people, the business's usual crew (crew.min), more when the home meets its crew rule (crew.rule), at least 2 for a must-not crew.solo or must-have crew.two_or_more, 3 for crew.three_or_more. Counted against capacity.cleaners. null while the quote is incomplete.
    - `versions` object: The versions this was priced under: the business's passport (what it does), eligibility (where, what homes, hours), pricing, booking authority (1: the owner says yes to every job that needs it) and the word list. Each version's content is kept, so the price can be worked out again.
      - `passport` integer
      - `eligibility` integer
      - `pricing` integer
      - `booking_authority` integer
      - `taxonomy` integer
    - `state` 'quoted': One of ten states, each with one meaning. quote_incomplete: Not priced yet: the job needs more facts. needs says which. Nothing is held. Allowed: quote. quoted: An exact price for one exact job version, until expires_at. Nothing is held: the time is not reserved yet. Allowed: hold, quote. held: A time is reserved for this job until hold_expires_at. Book it, or cancel to let it go. Allowed: 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: status, cancel. booked: The owner is committed to do this job at this price and time. Allowed: 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: status. declined: The owner said no. decline.reason says why; decline.next_available is the next open time when there is one. Allowed: status, quote. cancelled: Called off by the agent. The time was released. Allowed: status, quote. supplier_cancelled: Called off by the owner after it was booked. The job can be quoted again elsewhere from job. Allowed: 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: status, quote.
    - `allowed_actions` string[]: The operations you may call on this job now. Anything else is refused (INVALID_STATE, ALREADY_BOOKED, ALREADY_CANCELLED).
    - `next_actions` string[]: The suggested next steps, from a fixed list.
    - `job_version` integer, nullable: The job's version. Every change to the job is a new version; old versions, their quotes and prices are kept, never written over.
  - object: The business takes the job and its owner quotes it (the service is priced owner_quotes, or the home is over what its fixed price covers). No amount, no token, nothing to hold or record; why says so in plain words.
    - `status` 'owner_must_quote', required
    - `currency` string, required
    - `why` string, required
    - `available_windows` object[], required
      - `allocation_id` string, uuid
      - `window_start` string, date-time
      - `window_end` string, date-time
      - `slots_remaining` integer: Jobs the business can still take at this time. For a business with jobs_at_once: jobs_at_once minus the most jobs it already has at any moment inside this time; with capacity.cleaners also no more jobs of this size (job_people) than its cleaners left at that moment. Jobs closer together than capacity.gap_minutes count as at the same time.
      - `conditions` string[]: Day and hour conditions (weekend, evening, holiday, same-day, next-day, in the business time zone) this business charges for or asks about on this window, as of the moment of the quote. The quote must state exactly these in inputs.conditions, or hold is refused with PRICE_MISMATCH. Same-day and next-day are read again at hold, so a quote made just before midnight can be refused just after: quote again as the message says.
    - `job_hours` integer: How long this job takes on site, in whole 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. For a business that builds its open times from its working hours (discover capacity.jobs_at_once), every offered time is exactly this long.
    - `job_people` integer, nullable: How many cleaners this job takes: an hourly price's people, the business's usual crew (crew.min), more when the home meets its crew rule (crew.rule), at least 2 for a must-not crew.solo or must-have crew.two_or_more, 3 for crew.three_or_more. Counted against capacity.cleaners. null while the quote is incomplete.
    - `taxonomy_version` integer: The word list version this was made under (GET /api/v1/taxonomy/house_cleaning returns the current one). A job made before reads as 4.
    - `scope` object: The rooms the job covers: leave_out (rooms not to clean) or clean_only (the only rooms to clean), never both, by space key from property.spaces in the word list. A must-have task in a room left out is refused. Under a by_rooms price a room left out comes off the price (its line is gone); a fixed, by_home, square-foot or hourly price does not change. It is echoed on every match, needs_more, near miss, quote and hold, and shown to the owner.
      - `leave_out` string[]
      - `clean_only` string[]
    - `home` string
    - `availability_checked_at` string, date-time: When the open windows were read. Windows change: a later hold can still find a slot gone (CAPACITY_FULL).
    - `versions` object: The versions this was priced under: the business's passport (what it does), eligibility (where, what homes, hours), pricing, booking authority (1: the owner says yes to every job that needs it) and the word list. Each version's content is kept, so the price can be worked out again.
      - `passport` integer
      - `eligibility` integer
      - `pricing` integer
      - `booking_authority` integer
      - `taxonomy` integer
  - object: Not enough inputs for a price yet. Send the listed needs and quote again. No token is issued.
    - `status` 'incomplete', required
    - `currency` string, required
    - `needs` object[], required
      - `field` string, required: Input to send, e.g. bedrooms, square_feet, hours, people, or quantities.<unit key>.
      - `type` 'integer' | 'number' | 'string', required
      - `reason` 'required_for_pricing' | 'required_for_eligibility' | 'required_for_crew' | 'required_for_booking_length', required: Why it is needed, as a code. required_for_booking_length: the business books bigger homes for longer (by bedrooms or square feet), so the job's length needs it.
      - `path` string, required: The exact request field that answers it, dotted: property.spaces.<space key>, property.size.square_feet, inputs.hours, inputs.quantities.<unit key>, inputs.customer_zip.
      - `min` number
      - `max` number
      - `unit` string
      - `why` string, required
    - `available_windows` object[], required
      - `allocation_id` string, uuid
      - `window_start` string, date-time
      - `window_end` string, date-time
      - `slots_remaining` integer: Jobs the business can still take at this time. For a business with jobs_at_once: jobs_at_once minus the most jobs it already has at any moment inside this time; with capacity.cleaners also no more jobs of this size (job_people) than its cleaners left at that moment. Jobs closer together than capacity.gap_minutes count as at the same time.
      - `conditions` string[]: Day and hour conditions (weekend, evening, holiday, same-day, next-day, in the business time zone) this business charges for or asks about on this window, as of the moment of the quote. The quote must state exactly these in inputs.conditions, or hold is refused with PRICE_MISMATCH. Same-day and next-day are read again at hold, so a quote made just before midnight can be refused just after: quote again as the message says.
    - `job_hours` integer: How long this job takes on site, in whole 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. For a business that builds its open times from its working hours (discover capacity.jobs_at_once), every offered time is exactly this long.
    - `job_people` integer, nullable: How many cleaners this job takes: an hourly price's people, the business's usual crew (crew.min), more when the home meets its crew rule (crew.rule), at least 2 for a must-not crew.solo or must-have crew.two_or_more, 3 for crew.three_or_more. Counted against capacity.cleaners. null while the quote is incomplete.
    - `taxonomy_version` integer: The word list version this was made under (GET /api/v1/taxonomy/house_cleaning returns the current one). A job made before reads as 4.
    - `scope` object: The rooms the job covers: leave_out (rooms not to clean) or clean_only (the only rooms to clean), never both, by space key from property.spaces in the word list. A must-have task in a room left out is refused. Under a by_rooms price a room left out comes off the price (its line is gone); a fixed, by_home, square-foot or hourly price does not change. It is echoed on every match, needs_more, near miss, quote and hold, and shown to the owner.
      - `leave_out` string[]
      - `clean_only` string[]
    - `transaction_id` string, uuid
    - `quote_id` string, uuid
    - `quoted_at` string, date-time
    - `expires_at` string, date-time
    - `availability_checked_at` string, date-time: When the open windows were read. Windows change: a later hold can still find a slot gone (CAPACITY_FULL).
    - `versions` object: The versions this was priced under: the business's passport (what it does), eligibility (where, what homes, hours), pricing, booking authority (1: the owner says yes to every job that needs it) and the word list. Each version's content is kept, so the price can be worked out again.
      - `passport` integer
      - `eligibility` integer
      - `pricing` integer
      - `booking_authority` integer
      - `taxonomy` integer
    - `state` 'quote_incomplete': One of ten states, each with one meaning. quote_incomplete: Not priced yet: the job needs more facts. needs says which. Nothing is held. Allowed: quote. quoted: An exact price for one exact job version, until expires_at. Nothing is held: the time is not reserved yet. Allowed: hold, quote. held: A time is reserved for this job until hold_expires_at. Book it, or cancel to let it go. Allowed: 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: status, cancel. booked: The owner is committed to do this job at this price and time. Allowed: 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: status. declined: The owner said no. decline.reason says why; decline.next_available is the next open time when there is one. Allowed: status, quote. cancelled: Called off by the agent. The time was released. Allowed: status, quote. supplier_cancelled: Called off by the owner after it was booked. The job can be quoted again elsewhere from job. Allowed: 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: status, quote.
    - `allowed_actions` string[]: The operations you may call on this job now. Anything else is refused (INVALID_STATE, ALREADY_BOOKED, ALREADY_CANCELLED).
    - `next_actions` string[]: The suggested next steps, from a fixed list.
    - `job_version` integer, nullable: The job's version. Every change to the job is a new version; old versions, their quotes and prices are kept, never written over.

## Other responses

- `default`: Error

---

[API](https://skmtc.dev/commontape/apis/commontape-agent-api.md) · [All operations](https://skmtc.dev/commontape/apis/commontape-agent-api/llms.txt) · [OpenAPI document](https://skmtc.dev/commontape/apis/commontape-agent-api/revisions/714820e6cc4d?raw)
