Documentation
onepick is a connector for AI assistants. It has four tools: one finds the best local business for a job, one books it, one reports on a booking, and one deletes a person’s data.
Connect
The connector is served three ways from the same handlers, with the same sign-in.
- MCP, over streamable HTTP:
/api/mcp - REST, one route per tool:
/api/tools - An OpenAPI document describing those routes:
/openapi.json
All three are HTTPS only. Location and the person’s details are tool inputs or come from our own sign-in; nothing is assumed from the assistant.
Sign-in
Sign-in is OAuth with PKCE. The person signs in with a one-time code, so there is no password to set. In demo mode, emailed codes are off and the reviewer account signs in with a password. An unauthenticated request to /api/mcp returns 401 with OAuth resource metadata, which is how an assistant discovers where to send the person.
Find the one best local business
find_pick
Given what the person needs and a US location, returns one recommended business and why it was picked.
Inputs of find_pick
service- Type: text
- Notes: Required. 1 to 200 characters. What the person needs, in their words, e.g. 'a haircut' or 'AC not cooling'.
category- Type: one of: haircut, lawn_care, house_cleaning, hvac, plumbing, electrical
- Notes: Optional. Set this when you can tell which one applies; otherwise leave it out.
location- Type: text
- Notes: Required. 1 to 200 characters. US zip code or 'City, ST'.
What comes back from find_pick
Every result has plain text the assistant can show as it is, and the structured data below. Each possible result is a status.
picked- One business was picked. The result carries its name, phone, address, distance and timezone, the criteria it was picked on, its booking link when it has one, whether a runner-up is held, and a disclosure saying the pick is not paid for.
pickId- Type: text
business- Type: object
business.name- Type: text
business.phone- Type: text
- Notes: US phone number in E.164, +1 then 10 digits.
business.address- Type: text or null
business.website- Type: text or null
business.distanceKm- Type: number
business.timezone- Type: text
criteria- Type: list
- Notes: At least 1 item.
criteria[].key- Type: one of: distance, licence, sources, website, online_booking, track_record
criteria[].label- Type: text
criteria[].detail- Type: text
bookingLink- Type: object or null
bookingLink.platform- Type: one of: booksy, vagaro, fresha, square, styleseat
bookingLink.url- Type: text
- Notes: URL.
hasRunnerUp- Type: true or false
disclosure- Type: text
- Notes: Not empty.
nothing_found- No pick. The reason says why: the location is not a US zip code or city we can place (unknown_location), the request is not one of the launch services (unsupported_service), or no suitable business was found near there (no_match).
reason- Type: one of: unknown_location, unsupported_service, no_match
Permissions of find_pick
Read-only: it changes nothing the person or any business can observe; the stored pick is our own copy of the search result, so it is safe to retry. Open world: it reads the business's public website.
- Read-only
- yes
- Destructive
- no
- Idempotent
- no
- Open world
- yes
Book the pick
book
Books the picked business for the person. First call it without a confirmation token to preview what will be shared, then again with the token to go ahead.
Inputs of book
pickId- Type: text
- Notes: Required.
useRunnerUp- Type: true or false
- Notes: Optional. Default false.
timeWindows- Type: list
- Notes: Required. 1 to 5 items. Local wall-clock times in the business's timezone; no UTC offset needed.
timeWindows[].start- Type: text
- Notes: Required. Local time, YYYY-MM-DDTHH:mm.
timeWindows[].end- Type: text
- Notes: Required. Local time, YYYY-MM-DDTHH:mm.
firstName- Type: text
- Notes: Required. 1 to 60 characters.
callbackPhone- Type: text
- Notes: Required. US phone number in E.164, +1 then 10 digits.
confirmationToken- Type: text
- Notes: Optional. Leave out to preview what will be shared; pass the token from the preview to go ahead.
What comes back from book
Every result has plain text the assistant can show as it is, and the structured data below. Each possible result is a status.
needs_confirmation- The preview. Nothing has been shared and nobody has been called. willShare lists exactly the details that would be spoken to the business, and confirmationToken is what to send back to go ahead.
businessName- Type: text
willShare- Type: list
- Notes: At least 1 item.
confirmationToken- Type: text
calling- The booking exists and an AI call is being placed. Ask booking_status for the result.
bookingId- Type: text
businessName- Type: text
use_booking_link- The business takes bookings online. The person books through bookingLink, and no call is placed.
bookingId- Type: text
businessName- Type: text
bookingLink- Type: object
bookingLink.platform- Type: one of: booksy, vagaro, fresha, square, styleseat
bookingLink.url- Type: text
- Notes: URL.
call_yourself- No AI call can be placed. The result carries the business's number, a short script to read out, the booking link when one exists, and the reason.
bookingId- Type: text
businessName- Type: text
reason- Type: one of: no_phone, mobile_number, line_type_unknown, excluded_state, outside_hours, calls_stopped, call_limit
phone- Type: text
- Notes: US phone number in E.164, +1 then 10 digits.
script- Type: text
- Notes: Not empty.
bookingLink- Type: object or null
bookingLink.platform- Type: one of: booksy, vagaro, fresha, square, styleseat
bookingLink.url- Type: text
- Notes: URL.
retryAfter- Type: text or null
too_many_requests- The person has reached the limit of 5 bookings a day. retryAfter says when to try again.
retryAfter- Type: text
not_found- The pick is unknown or belongs to someone else, there is no runner-up to use, or the confirmation token has expired or belongs to someone else.
Permissions of book
A write, marked destructive: with a confirmation token it shares personal details with a business and may place a phone call, which cannot be undone. It is open world because it reaches a real business outside our own records. Repeating a token that was already used returns the same booking and does not place a second call.
- Read-only
- no
- Destructive
- yes
- Idempotent
- no
- Open world
- yes
Check a booking
booking_status
Returns the current state of a booking and, when the first business did not come through, the runner-up.
Inputs of booking_status
bookingId- Type: text
- Notes: Required.
What comes back from booking_status
Every result has plain text the assistant can show as it is, and the structured data below. Each possible result is a status.
calling- The call is in progress. A call with no result after 15 minutes reads as failed.
confirmed- The business confirmed. confirmedStart is the appointment time with its UTC offset, and timezone is the business's timezone to show it in.
declined- The business said no. note may carry the next slot it offered.
no_answer- Nobody answered.
voicemail- The call reached voicemail. No message is left.
person_must_call- The business wants the person to call. This is also the result when the business asks for a price, a deposit or card details, which the call does not agree.
failed- The call could not be completed, or no result arrived within 15 minutes.
call_yourself- This booking was handed to the person to call, with a script.
use_booking_link- This booking was handed to the person as the business's own booking link.
bookingId- Type: text
pickId- Type: text
serviceText- Type: text
businessName- Type: text
businessPhone- Type: text
- Notes: US phone number in E.164, +1 then 10 digits.
confirmedStart- Type: text or null
timezone- Type: text
note- Type: text or null
attempts- Type: whole number
- Notes: 0 to 2.
runnerUp- Type: object or null
runnerUp.businessName- Type: text
runnerUp.distanceKm- Type: number
runnerUp.criteria- Type: list
- Notes: At least 1 item.
runnerUp.criteria[].key- Type: one of: distance, licence, sources, website, online_booking, track_record
runnerUp.criteria[].label- Type: text
runnerUp.criteria[].detail- Type: text
runnerUp.disclosure- Type: text
- Notes: Not empty.
not_found- There is no such booking for this person.
Permissions of booking_status
Read-only and idempotent, closed world: it reads our own record of the booking.
- Read-only
- yes
- Destructive
- no
- Idempotent
- yes
- Open world
- no
Delete my data
delete_my_data
Deletes the person's account, picks and booking history. Cannot be undone.
Inputs of delete_my_data
confirm- Type: must be true
- Notes: Required.
What comes back from delete_my_data
Every result has plain text the assistant can show as it is, and the structured data below. Each possible result is a status.
This tool has one result and no status field.
deletedBookings- Type: whole number
- Notes: 0 or more.
deletedPicks- Type: whole number
- Notes: 0 or more.
Permissions of delete_my_data
Destructive and idempotent, closed world: it deletes the person's data, and running it again deletes nothing more.
- Read-only
- no
- Destructive
- yes
- Idempotent
- yes
- Open world
- no
The two-step book
book shares nothing and calls nobody without a preview first.
- Call
bookwithout aconfirmationToken. The result isneeds_confirmation: it names the business, lists exactly what will be shared, and carries a single-use token that lasts 30 minutes. No booking exists yet. - Show the person that preview. When they agree, call
bookagain with the same inputs and the token. Only this call creates a booking.
Repeating a call with a token that was already used returns the same booking and does not place a second call, so a retry is safe. A second attempt at the same business happens only when the person asks again, with a fresh preview and token. To book the runner-up after the first business did not come through, call book with the same pickId and useRunnerUp set to true.
Errors
Every result a person can cause, or needs to hear about, is a status in the tool’s result, not an error. Inputs that do not match the tables above are rejected before the tool runs, with a message naming the field.
A fault on our side returns one fixed message, with isError set over MCP and 500 over REST: “Something went wrong on our side. Please try again.” It carries no stack trace, vendor name or internal id.
Limits
| Rule | Limit |
|---|---|
| Bookings per person per day | 5 |
| Call attempts per business for one request | 2 |
| Calls to one business in any 30 days, counted by phone number | 3 |
| Minutes a confirmation token lasts | 30 |
| Minutes before a call with no result reads as failed | 15 |
When no AI call is placed
AI calls are placed only between 9am and 6pm in the business’s local time, only to landline and internet phone numbers unless the business has given verified consent, and not to businesses in Indiana, Louisiana, Minnesota, Montana and Nebraska. In every other case book returns call_yourself with one of these reasons.
no_phone- The business has no usable phone number.
mobile_number- The number is a mobile number. Without the business's verified consent, only landline and internet phone numbers are called.
line_type_unknown- The kind of line could not be determined, so the number is treated as one we may not call.
excluded_state- The business is in a state where no AI booking call is placed: Indiana, Louisiana, Minnesota, Montana and Nebraska.
outside_hours- It is outside 9am to 6pm in the business's local time. retryAfter says when a call becomes possible.
calls_stopped- The business asked us to stop AI calls to this number. That request is permanent.
call_limit- The business has had its limit of calls: 2 attempts for one request, or 3 calls in any 30 days.
Example prompts
These work:
- I need a haircut near 07030 on Friday after 5pm. Pick a barber and book it.
- My AC stopped cooling. Find an HVAC company in Hoboken, NJ and book the earliest visit this week.
- Find a house cleaner near Austin, TX 78704 and book a first clean for Saturday morning.
- Book a plumber for a leaking kitchen tap in Tampa, FL, any weekday between 9 and noon.
- Did my lawn care booking go through? If not, try the next business.
These are declined:
- Book me a dentist appointment for a cleaning next week.
- Health and dental bookings are out of scope. find_pick returns nothing_found with the reason unsupported_service.
- Get me a table for four at an Italian place tonight.
- Restaurant bookings are out of scope. find_pick returns nothing_found with the reason unsupported_service.
- Find a plumber near Toronto, ON.
- Only US locations are served. find_pick returns nothing_found with the reason unknown_location.
Data sources and notices
onepick's directory of businesses is built from open data. This file carries the notices those licences ask for.
No Google Places or Yelp content is used.
Business listings: Overture Maps Foundation, places theme
Business names, addresses, phone numbers, websites and locations come from the Overture Maps Foundation places theme, https://overturemaps.org. Each stored business records which upstream datasets it came from (dataSources, for example overture-places:meta).
The places theme combines several sources, each under its own licence (https://docs.overturemaps.org/attribution/):
| Source | Licence |
|---|---|
| Meta, Microsoft, PinMeTo, Krick, RenderSEO, DAC, BrightQuery | Community Data License Agreement, Permissive, Version 2.0 (CDLA-Permissive-2.0) |
| Foursquare | Apache License, Version 2.0, with the NOTICE below |
| AllThePlaces | CC0 1.0 Universal |
The places theme contains no OpenStreetMap data.
Changes made by onepick
The data is changed before it is stored or shown: only places in the United States in six service categories are kept; places marked permanently closed are dropped; phone numbers are rewritten in E.164 form; duplicate listings of one business are merged; a timezone is derived from each location.
CDLA-Permissive-2.0
Section 2.1 of the agreement: "A Data Recipient may share Data, with or without modifications, so long as the Data Recipient makes available the text of this agreement with the shared Data."
The text of the agreement: https://cdla.dev/permissive-2-0/
Foursquare OS Places
Overture's note on these rows: Foursquare data was transformed to the Overture schema.
The Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
The full content of Foursquare's NOTICE.txt, as published at https://opensource.foursquare.com/places-notice-txt/ and read on 2026-09-21, unmodified:
© 2026 Foursquare Labs, Inc. All rights reserved. The Foursquare OS Places dataset (the “Data”) is licensed under the Apache License, Version 2.0 (the “License”). You may not use, modify, or distribute the Data except in compliance with the License. As set forth more fully in the License, if you use, modify, or distribute the Data, you must: – provide recipients with a copy of the License. – if applicable, include prominent notices to the extent you’ve changed the Data. – preserve attribution to Foursquare, including preserving the full content of this NOTICE.txt file. To ensure appropriate attribution to Foursquare, we recommend the following: – if using/distributing the Data in flat file form as-is or after making changes/modifications: include this NOTICE.txt file, which may be modified to include an additional notice of your changes/modifications, if any. – if using/distributing the Data in API form as-is or after making changes/modifications: include a copy of the content from this NOTICE.txt file prominently in your developer documentation for such API, which may be modified to include an additional notice of your changes/modifications, if any. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0. Unless required by applicable law or agreed to in writing, the Data distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. We also encourage you to join our Placemaker community where you can contribute and provide suggestions to improve the accuracy of the Data for future releases for yourself and others.
Notice of changes, as that file invites: onepick's changes to the data are listed under "Changes made by onepick" above.
CC0 1.0 Universal
AllThePlaces rows are dedicated to the public domain: https://creativecommons.org/publicdomain/zero/1.0/
Zip codes and city names: United States Census Bureau
Zip code centroids, city names and their states come from the Census Bureau's 2025 gazetteer files and the 2020 ZCTA-to-county relationship file. Works of the United States government are in the public domain; no licence applies. This product uses Census Bureau data but is not endorsed or certified by the Census Bureau.
- https://www.census.gov/geographies/reference-files/time-series/geo/gazetteer-files.html
- https://www.census.gov/geographies/reference-files/time-series/geo/relationship-files.html
Timezones
Each business's and each location's timezone name (for example America/New_York) is worked out from its coordinates, offline, with the tz-lookup package. The package is published under CC0 1.0. Its boundary data comes from the timezone-boundary-builder project (https://github.com/evansiroky/timezone-boundary-builder), whose output is licensed under the Open Data Commons Open Database License (ODbL), https://opendatacommons.org/licenses/odbl/, and is derived from OpenStreetMap.
Timezone boundaries © OpenStreetMap contributors, https://www.openstreetmap.org/copyright.
onepick stores only the resulting timezone name beside each record; it does not store or redistribute the boundary data.
State license data
Whether a trade business holds a state license is checked against public license files published by three state agencies. Each stored license records the file it came from and the day that file was copied (source_file, source_date).
Changes made by onepick
Only heating and air conditioning, plumbing and electrical business licenses are kept. Names are normalized for matching, phone numbers are rewritten in E.164 form, and each agency's own status codes are reduced to "active" or "inactive". A license's current standing can change at any time: the agency's own lookup is the authority.
Texas Department of Licensing and Regulation (TDLR)
Source: Texas Department of Licensing and Regulation, https://www.tdlr.texas.gov, license data files at https://www.tdlr.texas.gov/LicenseSearch/licfile.asp. The date each record was copied is stored with it; the files are re-copied on every import.
This information is copied from the TDLR website. Neither onepick nor the way this information is presented is endorsed by the State of Texas or by TDLR. No TDLR logo is used.
Current status of any Texas license: https://www.tdlr.texas.gov/LicenseSearch/
California Contractors State License Board (CSLB)
Source: CSLB License Master file, https://www.cslb.ca.gov/onlineservices/dataportal/ContractorList. Information on State of California websites is, unless otherwise indicated, in the public domain (https://www.ca.gov/use/); no credit is required. The State of California makes no guarantee of its accuracy and does not endorse onepick.
Current status of any California license: https://www.cslb.ca.gov/OnlineServices/CheckLicenseII/CheckLicense.aspx
Florida Department of Business and Professional Regulation (DBPR)
Source: DBPR construction and electrical licensee files, https://www2.myfloridalicense.com/construction-industry/public-records/ and https://www2.myfloridalicense.com/electrical-contractors/public-records/, provided as public records under Chapter 119, Florida Statutes; no credit is required. DBPR does not endorse onepick.
Current status of any Florida license: https://www.myfloridalicense.com/wl11.asp