---
title: "Describe a merchant"
method: POST
path: "/api/v1/discover"
---

# Describe a merchant

`POST /api/v1/discover`

## Request

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

Services with pricing_model and a typed inputs list (needs, frequencies, conditions), add-ons, the business time zone, service area (ZIPs, with town and state) and hours. Hours are wall-clock times in the business time zone.

## Request body

- object
  - `merchant_slug` string, required

## Response `200`

Success

- object
  - `slug` string
  - `display_name` string
  - `vertical` string
  - `services` object[]
    - `key` string
    - `inputs` object: What the price needs and allows.
      - `pricing_model` 'flat' | 'by_home' | 'by_sqft' | 'hourly' | 'per_unit' | 'project' | 'by_rooms' | 'owner_quotes' | 'packages': How the service is priced: flat, by_home (bedrooms and bathrooms), by_sqft (square_feet), hourly, per_unit, project, by_rooms (a base price plus a price per kind of room, read from property.spaces: space.bedroom, space.bathroom.full, space.bathroom.half, space.living_area = living + family + formal living rooms, space.kitchen, space.balcony, space.garage; one price line per kind, keyed by the space key) or owner_quotes (the owner quotes each job: owner_must_quote). A flat or project price may declare what it covers (covers: most bedrooms, full and half bathrooms, living areas, square feet, rooms in total); a home over a limit is owner_must_quote, a limited fact the home did not state is a need. packages: 2 to 5 fixed prices (listed in packages), each with the same optional limits and optionally the kinds of home it is for; the price is the cheapest package that fits the WHOLE home (never the job's scope: a package is for a size of home) and does every must-have task (inputs.addons): a task the service does not offer on its own is quoted only in a package that includes it (includes), and packages are compared by price plus the extra-cost tasks each does not include, then list order; a task the picked package includes is not charged; a limited fact not stated before any fit is a need (with property.type when a package is for some kinds only); no package left is owner_must_quote.
      - `packages` object[]: Only when pricing_model is packages: each package the owner offers.
        - `key` string
        - `label` string
        - `price` number: Whole dollars, before discounts, the minimum charge, extra charges and travel.
        - `covers` object: Its limits (max_bedrooms, max_full_bathrooms, max_half_bathrooms, max_living_areas, max_square_feet, max_rooms); none set = no limit.
        - `home_kinds` string[], nullable: The property.type values it is for; null = every kind.
        - `includes` string[]: Tasks (task words) this package also does at no extra charge: ones the service charges extra for or does not offer on its own (M-17c). Empty = none.
      - `covers` object: What a fixed price covers (flat / project only). A limit never set is no limit.
        - `max_bedrooms` integer
        - `max_full_bathrooms` integer
        - `max_half_bathrooms` integer
        - `max_living_areas` integer
        - `max_square_feet` integer
        - `max_rooms` integer
      - `needs` object[]
        - `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
      - `frequencies` string[]
      - `conditions` object[]: This service's answer for every job condition on the list. extra_charge carries the amount added when the condition is stated. not_answered: never assumed; stating it needs the owner's approval.
        - `key` string, required
        - `answer` 'no_charge' | 'extra_charge' | 'ask_first' | 'not_answered', required
        - `amount` number
      - `surcharges` string[]: Charge keys typed by the owner before the condition list existed. Empty for every business saved since.
    - `booking_hours` integer, nullable: How long one booking of this service takes, in whole hours. null = the owner has not answered yet.
    - `passport` object: What this service covers. tasks maps each task word to included, extra_cost, ask_first or not_offered. confirmed false = the owner has not answered; assume nothing. A service priced by packages may do an extra_cost or not_offered task inside a package at no extra charge (inputs.packages[].includes).
      - `confirmed` boolean
      - `tasks` object
    - `matchable` boolean: M-21: false until the owner confirmed this service's task list (passport.confirmed). Such a service is a near miss scope_not_confirmed in search, left out of plain search, and refused on a direct quote (SERVICE_UNKNOWN with reason scope_not_confirmed).
    - `reason` 'scope_not_confirmed': Only when matchable is false.
    - `takes` object, nullable: M-21: the limits that apply to this service: the business-wide takes and crew.max_bedrooms, with the service's own answers on top (a service may take bigger or smaller homes than the business). Judge a job for this service by these, not the business-wide takes. null = nothing answered.
      - `home_kinds` string[]
      - `max_bedrooms` integer: The largest home by bedrooms for this service.
      - `max_square_feet` integer
      - `max_bathrooms` integer: Full plus half bathrooms.
      - `max_floors` integer: Floors inside the home.
      - `max_garages` integer
      - `max_balconies` integer
      - `max_kitchens` integer
      - `max_rooms` integer: Bedrooms, full and half bathrooms, living areas, kitchens, balconies and garages together; read only when the home states every one.
      - `min_bedrooms` integer: The fewest bedrooms (a studio counts as 0). Below it: too_few_bedrooms; bedrooms left out: a need (property.spaces.space.bedroom).
      - `max_full_bathrooms` integer: The most full bathrooms. Above it: too_many_full_bathrooms; a bare bathroom total above it: a need for the full count.
      - `occupancy` string[]: Lived-in or empty homes taken. Not taken: occupancy_not_taken; left out of the job: a need (property.occupancy).
      - `furnishing` string[]: How furnished a home may be. Not taken: furnishing_not_taken; left out: a need (property.furnishing).
      - `ask_first_above` object: Ask me first above a size (inside any hard limit): a bigger home, or one that leaves the size out, books only with the owner's yes (conditional; approval reason ask_first_limit, key takes.ask.<field>).
        - `bedrooms` integer
        - `square_feet` integer
        - `bathrooms` integer
      - `unsure` string[]: Limits the owner is not sure of. Not sure reads as ask first: any home books only with the owner's yes (conditional; approval reason ask_first_limit, key takes.unsure.<field>). Never a miss.
  - `travel` object: Further-out ZIPs (a subset of area) and what they add.
    - `further_zips` string[]
    - `travel_charge` number, nullable
    - `notice_hours` integer, nullable
  - `crew` object, nullable: Crew the business sends; null = not answered.
    - `min` integer
    - `max` integer
    - `max_bedrooms` integer
    - `rule` object: M-19b: bigger homes need more cleaners: at least `people` cleaners once the home's `by` reaches `from` (bedrooms, bathrooms as full plus half, living_areas as living, family and formal living rooms, floors inside the home, square_feet). A quote that needs the fact and does not state it is incomplete with a need (reason required_for_crew); floors and living spaces are read from property only.
      - `by` 'bedrooms' | 'bathrooms' | 'living_areas' | 'floors' | 'square_feet'
      - `from` integer
      - `people` integer
      - `bedrooms_from` integer: Same as from, kept when by is bedrooms.
  - `capacity` object: M-19: what turns the business's working hours into open times. jobs_at_once set: open times are built from its days, hours and each service's booking length for the next 14 days, each exactly the job's length (job_hours), and never more jobs at the same moment than jobs_at_once. null fields = not answered (hand-made open times, as before).
    - `jobs_at_once` integer, nullable: How many jobs the business can do at the same time.
    - `longest_job_hours` integer, nullable: The longest job it takes, in hours; a longer job is a near miss job_too_long.
    - `longer_for_bigger_homes` object, nullable: Bigger homes take longer: extra_hours added to the booking length from this many bedrooms or square feet.
      - `from_bedrooms` integer
      - `from_square_feet` integer
      - `extra_hours` integer
    - `cleaners` integer, nullable: M-19b: the cleaners the business has in all, one pool shared by jobs at the same time: a time is offered only when the job's cleaners (job_people) fit beside the jobs already there. null = no pool limit.
    - `gap_minutes` 0 | 30 | 60 | 90 | 120 | null, nullable: M-19b: the time the business needs between jobs (travel, setting up). Built open times step by the job's length plus this gap, and two jobs closer together than it count as at the same time. null = jobs_at_once not answered.
  - `takes` object, nullable: What the business takes, for every service (M-11, M-21); null = not answered, no limit applied. The largest home by bedrooms is crew.max_bedrooms. A condo counts as an apartment. A service may differ: read services[].takes, the limits that apply to that service.
    - `home_kinds` string[]: property.* keys from the word list.
    - `max_square_feet` integer
    - `max_bathrooms` integer: Full plus half bathrooms.
    - `max_floors` integer: Floors inside the home.
    - `max_garages` integer
    - `max_balconies` integer
    - `max_kitchens` integer
    - `max_rooms` integer: Bedrooms, full and half bathrooms, living areas, kitchens, balconies and garages together; read only when the home states every one.
    - `min_bedrooms` integer: The fewest bedrooms (a studio counts as 0). Below it: too_few_bedrooms; bedrooms left out: a need (property.spaces.space.bedroom).
    - `max_full_bathrooms` integer: The most full bathrooms. Above it: too_many_full_bathrooms; a bare bathroom total above it: a need for the full count.
    - `occupancy` string[]: Lived-in or empty homes taken. Not taken: occupancy_not_taken; left out of the job: a need (property.occupancy).
    - `furnishing` string[]: How furnished a home may be. Not taken: furnishing_not_taken; left out: a need (property.furnishing).
    - `ask_first_above` object: Ask me first above a size (inside any hard limit): a bigger home, or one that leaves the size out, books only with the owner's yes (conditional; approval reason ask_first_limit, key takes.ask.<field>).
      - `bedrooms` integer
      - `square_feet` integer
      - `bathrooms` integer
    - `unsure` string[]: Limits the owner is not sure of. Not sure reads as ask first: any home books only with the owner's yes (conditional; approval reason ask_first_limit, key takes.unsure.<field>). Never a miss.
  - `when` object, nullable: M-21: when agents can book, from the business's plain day-and-hour answers; only a No is stored. null = not answered (nothing restricted). A time that breaks it is never offered; holding one answers INSUFFICIENT_NOTICE (too soon) or OUTSIDE_HOURS (weekend, evening, holiday).
    - `min_days_ahead` 1 | 2: 1 = no same-day jobs; 2 = no same-day or next-day jobs (calendar days in the business time zone).
    - `weekends` false: false = no jobs starting on Saturday or Sunday.
    - `evening_starts` false: false = no jobs starting at 6:00 pm or later.
    - `holidays` false: false = no jobs on the eleven US federal holidays, on the day each is observed.
  - `brings` string[], nullable
  - `products` string[], nullable
  - `brings_not_said` string[], nullable: Kit words added to the word list after this business answered: it has not said, which is not a no.
  - `products_not_said` string[], nullable: Product words added to the word list after this business answered: it has not said, which is not a no.
  - `payment` object, nullable: Declared by the business; null = not answered. The customer pays the business directly. CommonTape never takes payment.
    - `methods` string[]
    - `due` 'on_the_day' | 'after_job'
    - `deposit` boolean
  - `invoice` string[], nullable: M-32: what the customer gets after the job, sent by the business (CommonTape never issues one). invoice.receipt is a receipt only. null: the business has not said.
  - `id_to_security` object, nullable: M-32: how many hours before a job the business can send each cleaner's name and ID to building security. null: not said.
    - `hours_before` integer
  - `timezone` string: The business's IANA time zone, e.g. America/Los_Angeles. Its hours are in this zone.
  - `area` string[]: ZIP codes the business serves.
  - `area_details` object[]: Town and state for each ZIP in area, in the same order. city and state are null for a ZIP we have no data for.
    - `zip` string, required
    - `city` string, nullable, required
    - `state` string, nullable, required
  - `hours` object[]: Working hours, in the business time zone. A close_time earlier than open_time ends the next day (overnight work).
    - `day_of_week` integer: 0 = Sunday
    - `open_time` string
    - `close_time` string

## 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)
