Book local service pros from your AI assistant: real availability, instant booking, easy cancels.
Maintain this server? Claim it
Call this server's tools from your browser. Nothing to install.
Requests go through mcptry because browsers can't call most MCP servers directly. Header values are sent to the server for that request only and are never stored or logged.
Tool list from our last check. Connect live to refresh it.
Cancel a booking made through Canopy. The appointment is called off in the business's calendar right away and the customer is emailed. This cannot be undone: confirm with the customer before calling. An appointment that has already started cannot be cancelled here. Needs the manage_token that came back with the booking_id.
booking_id of the booking to cancel (from request_booking or get_request_status).
manage_token returned with that booking_id (by request_booking when the booking was created, or as booking_manage_token by get_request_status).
Short reason for cancelling, if the customer gave one.
The server marks this tool as destructive. Running it may delete or overwrite data on the server or in any account you connected with your headers.
Pick your client. Values in capitals, like YOUR_TOKEN, are yours to fill in.
claude mcp add --transport http canopy https://canopy-mcp-production.up.railway.app/mcp/public
Cancel a booking made through Canopy. The appointment is called off in the business's calendar right away and the customer is emailed. This cannot be undone: confirm with the customer before calling. An appointment that has already started cannot be cancelled here. Needs the manage_token that came back with the booking_id.
| Argument | Type | Description |
|---|---|---|
| reason | string | Short reason for cancelling, if the customer gave one. |
| booking_id* | string | booking_id of the booking to cancel (from request_booking or get_request_status). |
| manage_token* | string | manage_token returned with that booking_id (by request_booking when the booking was created, or as booking_manage_token by get_request_status). |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"booking_id",
"manage_token"
],
"properties": {
"reason": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 500,
"description": "Short reason for cancelling, if the customer gave one."
},
"booking_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "booking_id of the booking to cancel (from request_booking or get_request_status)."
},
"manage_token": {
"type": "string",
"pattern": "^[A-Za-z0-9_-]{32}$",
"description": "manage_token returned with that booking_id (by request_booking when the booking was created, or as booking_manage_token by get_request_status)."
}
},
"additionalProperties": false
}Given businesses you already found for the user (name plus phone, website or address), report which ones can be booked through Canopy and how (booking_mode). Results come back in exactly the order you sent them. Canopy never ranks, filters or recommends businesses. Results are returned in the same order as the input.
| Argument | Type | Description |
|---|---|---|
| businesses* | array | 1 to 50 businesses, in your order. Results keep this order. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"businesses"
],
"properties": {
"businesses": {
"type": "array",
"items": {
"type": "object",
"properties": {
"phone": {
"type": "string",
"pattern": "^[0-9+().\\s-]*$",
"maxLength": 40,
"description": "Business phone number."
},
"address": {
"type": "object",
"properties": {
"zip": {
"type": "string",
"pattern": "^[A-Za-z0-9 -]*$",
"maxLength": 20
},
"city": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 100
},
"state": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 50
}
},
"description": "City, state and ZIP code of the business.",
"additionalProperties": false
},
"website": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 500,
"description": "Business website URL or domain."
},
"business_name": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 200,
"description": "Business name as you found it. With no phone or website, add the address city, state or ZIP when known."
}
},
"additionalProperties": false
},
"maxItems": 50,
"minItems": 1,
"description": "1 to 50 businesses, in your order. Results keep this order."
}
},
"additionalProperties": false
}List open appointment slots for a Canopy business, optionally for one service and one location, between two dates. Slots are cached from the business calendar (see synced_at); the time is re-checked live when you book.
| Argument | Type | Description |
|---|---|---|
| to_date | string | Last day (inclusive), YYYY-MM-DD, at most 31 days after from_date. Default: 13 days after from_date. |
| timezone | string | IANA time zone for dates and returned times. |
| from_date | string | First day, YYYY-MM-DD. Default: today. |
| service_id | string | Only slots for this service. |
| location_id | string | Location for multi-location businesses. |
| operator_id* | string | operator_id from canopy_check_bookable or resolve_business. |
| customer_zip | string | Customer's ZIP code, if known. For a business with several locations, Canopy uses the location nearest to it unless location_id (or a slot or service tied to one location) decides. The result names the location used. |
Get the prices of a Canopy business's bookable services, its fees and its cancellation terms, exactly as the business has them on file. Anything the business has not put on file comes back as on_file=false: tell the customer it is not on file (they can ask the business) and never estimate or fill in a value.
| Argument | Type | Description |
|---|---|---|
| operator_id* | string | operator_id from canopy_check_bookable or resolve_business. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"operator_id"
],
"properties": {
"operator_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "operator_id from canopy_check_bookable or resolve_business."
}
},
"additionalProperties": false
}Check the status of a booking request (preferred times or callback). Returns pending, approved, rejected, counter_proposed, expired, or converted. Needs the manage_token that came back when the request was made.
| Argument | Type | Description |
|---|---|---|
| request_id* | string | request_id returned by request_booking, propose_times or request_callback. |
| manage_token* | string | manage_token returned with that request_id when the request was created. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"request_id",
"manage_token"
],
"properties": {
"request_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "request_id returned by request_booking, propose_times or request_callback."
},
"manage_token": {
"type": "string",
"pattern": "^[A-Za-z0-9_-]{32}$",
"description": "manage_token returned with that request_id when the request was created."
}
},
"additionalProperties": false
}Propose preferred times to a business that reviews booking requests. The business will approve one time (that books it; the customer is emailed a confirmation) or propose an alternative (the customer confirms it from an emailed code). Check status with get_request_status.
| Argument | Type | Description |
|---|---|---|
| notes | string | Extra details for the business (added to the customer notes). |
| customer* | object | The customer the appointment or callback is for. |
| service_id* | string | Service the customer wants (from get_availability). |
| location_id | string | Location for multi-location businesses. |
| operator_id* | string | operator_id from canopy_check_bookable or resolve_business. |
| customer_zip | string | Customer's ZIP code, if known. For a business with several locations, Canopy uses the location nearest to it unless location_id (or a slot or service tied to one location) decides. The result names the location used. |
| idempotency_key | string | Optional. Reuse the same key when retrying so nothing is created twice. Derived automatically if omitted. |
| preferred_times |
Start a booking with a Canopy-connected business. Canopy automatically routes to the correct flow based on the operator's booking mode: auto-confirm (booked right away in the business's calendar; the customer is emailed a confirmation), request-times (propose preferred times), or submission-only (callback request). Summarize the booking and get the customer's go-ahead before calling.
| Argument | Type | Description |
|---|---|---|
| notes | string | Extra details for the business (added to the customer notes). |
| slot_id | string | Open slot from get_availability (instant-confirm businesses). |
| customer* | object | The customer the appointment or callback is for. |
| service_id | string | Service to book (from get_availability). Needed for instant-confirm and preferred-times businesses. |
| location_id | string | Location for multi-location businesses. |
| operator_id* | string | operator_id from canopy_check_bookable or resolve_business. |
| customer_zip | string | Customer's ZIP code, if known. For a business with several locations, Canopy uses the location nearest to it unless location_id (or a slot or service tied to one location) decides. The result names the location used. |
Request a callback from a business. They will contact the customer directly. No calendar booking is created.
| Argument | Type | Description |
|---|---|---|
| notes | string | Extra details for the business (added to the customer notes). |
| customer* | object | The customer the appointment or callback is for. |
| service_id | string | Service the customer is asking about, if known. |
| location_id | string | Location for multi-location businesses. |
| operator_id* | string | operator_id from canopy_check_bookable or resolve_business. |
| customer_zip | string | Customer's ZIP code, if known. For a business with several locations, Canopy uses the location nearest to it unless location_id (or a slot or service tied to one location) decides. The result names the location used. |
| idempotency_key | string | Optional. Reuse the same key when retrying so nothing is created twice. Derived automatically if omitted. |
| agent_callback_url | string | Optional HTTPS URL that receives a webhook when the business responds. |
Ask to move a booking the customer already confirmed (booked with the business) to another open slot of the same service and location (slot_id from get_availability). Nothing moves right away: Canopy emails the customer a link to approve the new time, and the business calendar changes only after they approve. A hold the customer has not confirmed yet cannot be moved. Calling again with the same slot_id reports whether it was approved. Needs the manage_token that came back with the booking_id.
| Argument | Type | Description |
|---|---|---|
| reason | string | Short reason for the move, if the customer gave one. |
| slot_id* | string | The new open slot (from get_availability, same service and location). The customer must approve the move by email. |
| booking_id* | string | booking_id of the confirmed booking to move (from request_booking or get_request_status). |
| manage_token* | string | manage_token returned with that booking_id (by request_booking when the booking was created, or as booking_manage_token by get_request_status). |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"booking_id",
"manage_token",
"slot_id"
],
"properties": {
"reason": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 500,
"description": "Short reason for the move, if the customer gave one."
},
"slot_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "The new open slot (from get_availability, same service and location). The customer must approve the move by email."
},
"booking_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "booking_id of the confirmed booking to move (from request_booking or get_request_status)."
},
"manage_token": {
"type": "string",
"pattern": "^[A-Za-z0-9_-]{32}$",
"description": "manage_token returned with that booking_id (by request_booking when the booking was created, or as booking_manage_token by get_request_status)."
}
},
"additionalProperties": false
}Match one business the user named to its Canopy listing, by phone, website, or name (add the city, state or ZIP code when you know it). Returns at most one match, with its booking_mode and locations, or found=false. When a name alone is not certain, found=false comes with up to 5 candidates in alphabetical order for the user to choose from.
| Argument | Type | Description |
|---|---|---|
| phone | string | Business phone number. |
| address | object | City, state and ZIP code of the business. |
| website | string | Business website URL or domain. |
| business_name | string | Business name as you found it. With no phone or website, add the address city, state or ZIP when known. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"phone": {
"type": "string",
"pattern": "^[0-9+().\\s-]*$",
"maxLength": 40,
"description": "Business phone number."
},
"address": {
"type": "object",
"properties": {
"zip": {
"type": "string",
"pattern": "^[A-Za-z0-9 -]*$",
"maxLength": 20
},
"city": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 100
},
"state": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 50
}
},
"description": "City, state and ZIP code of the business.",
"additionalProperties": false
},
"website": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 500,
"description": "Business website URL or domain."
},
"business_name": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 200,
"description": "Business name as you found it. With no phone or website, add the address city, state or ZIP when known."
}
},
"additionalProperties": false
}We read every tool's name, description and inputs the way the model will, looking for instructions hidden from you. A clean result means no warning signs, not a guarantee. Scanned Nov 2, 2026.
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"operator_id"
],
"properties": {
"to_date": {
"type": "string",
"format": "date",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
"description": "Last day (inclusive), YYYY-MM-DD, at most 31 days after from_date. Default: 13 days after from_date."
},
"timezone": {
"type": "string",
"default": "America/New_York",
"pattern": "^[A-Za-z][A-Za-z0-9_+-]*(\\/[A-Za-z0-9_+-]+){0,2}$",
"maxLength": 64,
"description": "IANA time zone for dates and returned times."
},
"from_date": {
"type": "string",
"format": "date",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
"description": "First day, YYYY-MM-DD. Default: today."
},
"service_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Only slots for this service."
},
"location_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Location for multi-location businesses."
},
"operator_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "operator_id from canopy_check_bookable or resolve_business."
},
"customer_zip": {
"type": "string",
"pattern": "^\\d{5}(-?\\d{4})?$",
"maxLength": 10,
"description": "Customer's ZIP code, if known. For a business with several locations, Canopy uses the location nearest to it unless location_id (or a slot or service tied to one location) decides. The result names the location used."
}
},
"additionalProperties": false
}| array |
| 1 to 3 preferred time windows, in order of preference. |
| agent_callback_url | string | Optional HTTPS URL that receives a webhook when the business responds. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"operator_id",
"customer",
"service_id",
"preferred_times"
],
"properties": {
"notes": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 2000,
"description": "Extra details for the business (added to the customer notes)."
},
"customer": {
"type": "object",
"required": [
"name",
"email"
],
"properties": {
"name": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 200,
"description": "Customer's full name."
},
"email": {
"type": "string",
"format": "email",
"pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
"maxLength": 254,
"description": "Customer's email address. Mode 1 sends the confirmation code here."
},
"notes": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 2000,
"description": "What the customer needs, in their words."
},
"phone": {
"type": "string",
"pattern": "^\\+?[0-9 ().-]{7,32}$",
"maxLength": 40,
"description": "Customer's phone number."
},
"address": {
"type": "object",
"required": [
"street",
"city",
"state",
"zip"
],
"properties": {
"zip": {
"type": "string",
"pattern": "^\\d{5}(-?\\d{4})?$",
"maxLength": 10,
"description": "ZIP code."
},
"city": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 100,
"description": "City."
},
"state": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 50,
"description": "State, e.g. \"VA\"."
},
"street": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 200,
"description": "Street address, e.g. \"605 E Main Street, Apt 2\"."
}
},
"description": "Where the work will be done. Ask the customer for it whenever the visit is at their home or business: some businesses' scheduling systems cannot book a job without it.",
"additionalProperties": false
}
},
"description": "The customer the appointment or callback is for.",
"additionalProperties": false
},
"service_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Service the customer wants (from get_availability)."
},
"location_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Location for multi-location businesses."
},
"operator_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "operator_id from canopy_check_bookable or resolve_business."
},
"customer_zip": {
"type": "string",
"pattern": "^\\d{5}(-?\\d{4})?$",
"maxLength": 10,
"description": "Customer's ZIP code, if known. For a business with several locations, Canopy uses the location nearest to it unless location_id (or a slot or service tied to one location) decides. The result names the location used."
},
"idempotency_key": {
"type": "string",
"pattern": "^[A-Za-z0-9._:-]{8,128}$",
"description": "Optional. Reuse the same key when retrying so nothing is created twice. Derived automatically if omitted."
},
"preferred_times": {
"type": "array",
"items": {
"type": "object",
"required": [
"starts_at",
"ends_at"
],
"properties": {
"ends_at": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
"description": "End, ISO 8601 with UTC offset."
},
"starts_at": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
"description": "Start, ISO 8601 with UTC offset, e.g. 2026-10-20T14:00:00-04:00."
}
},
"additionalProperties": false
},
"maxItems": 3,
"minItems": 1,
"description": "1 to 3 preferred time windows, in order of preference."
},
"agent_callback_url": {
"type": "string",
"format": "uri",
"maxLength": 2000,
"description": "Optional HTTPS URL that receives a webhook when the business responds."
}
},
"additionalProperties": false
}| idempotency_key | string | Optional. Reuse the same key when retrying so nothing is created twice. Derived automatically if omitted. |
| preferred_times | array | 1 to 3 preferred time windows (businesses that review requests). |
| agent_callback_url | string | Optional HTTPS URL that receives a webhook when the business responds. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"operator_id",
"customer"
],
"properties": {
"notes": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 2000,
"description": "Extra details for the business (added to the customer notes)."
},
"slot_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Open slot from get_availability (instant-confirm businesses)."
},
"customer": {
"type": "object",
"required": [
"name",
"email"
],
"properties": {
"name": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 200,
"description": "Customer's full name."
},
"email": {
"type": "string",
"format": "email",
"pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
"maxLength": 254,
"description": "Customer's email address. Mode 1 sends the confirmation code here."
},
"notes": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 2000,
"description": "What the customer needs, in their words."
},
"phone": {
"type": "string",
"pattern": "^\\+?[0-9 ().-]{7,32}$",
"maxLength": 40,
"description": "Customer's phone number."
},
"address": {
"type": "object",
"required": [
"street",
"city",
"state",
"zip"
],
"properties": {
"zip": {
"type": "string",
"pattern": "^\\d{5}(-?\\d{4})?$",
"maxLength": 10,
"description": "ZIP code."
},
"city": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 100,
"description": "City."
},
"state": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 50,
"description": "State, e.g. \"VA\"."
},
"street": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 200,
"description": "Street address, e.g. \"605 E Main Street, Apt 2\"."
}
},
"description": "Where the work will be done. Ask the customer for it whenever the visit is at their home or business: some businesses' scheduling systems cannot book a job without it.",
"additionalProperties": false
}
},
"description": "The customer the appointment or callback is for.",
"additionalProperties": false
},
"service_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Service to book (from get_availability). Needed for instant-confirm and preferred-times businesses."
},
"location_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Location for multi-location businesses."
},
"operator_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "operator_id from canopy_check_bookable or resolve_business."
},
"customer_zip": {
"type": "string",
"pattern": "^\\d{5}(-?\\d{4})?$",
"maxLength": 10,
"description": "Customer's ZIP code, if known. For a business with several locations, Canopy uses the location nearest to it unless location_id (or a slot or service tied to one location) decides. The result names the location used."
},
"idempotency_key": {
"type": "string",
"pattern": "^[A-Za-z0-9._:-]{8,128}$",
"description": "Optional. Reuse the same key when retrying so nothing is created twice. Derived automatically if omitted."
},
"preferred_times": {
"type": "array",
"items": {
"type": "object",
"required": [
"starts_at",
"ends_at"
],
"properties": {
"ends_at": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
"description": "End, ISO 8601 with UTC offset."
},
"starts_at": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
"description": "Start, ISO 8601 with UTC offset, e.g. 2026-10-20T14:00:00-04:00."
}
},
"additionalProperties": false
},
"maxItems": 3,
"minItems": 1,
"description": "1 to 3 preferred time windows (businesses that review requests)."
},
"agent_callback_url": {
"type": "string",
"format": "uri",
"maxLength": 2000,
"description": "Optional HTTPS URL that receives a webhook when the business responds."
}
},
"additionalProperties": false
}{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"operator_id",
"customer"
],
"properties": {
"notes": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 2000,
"description": "Extra details for the business (added to the customer notes)."
},
"customer": {
"type": "object",
"required": [
"name",
"email"
],
"properties": {
"name": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 200,
"description": "Customer's full name."
},
"email": {
"type": "string",
"format": "email",
"pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
"maxLength": 254,
"description": "Customer's email address. Mode 1 sends the confirmation code here."
},
"notes": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 2000,
"description": "What the customer needs, in their words."
},
"phone": {
"type": "string",
"pattern": "^\\+?[0-9 ().-]{7,32}$",
"maxLength": 40,
"description": "Customer's phone number."
},
"address": {
"type": "object",
"required": [
"street",
"city",
"state",
"zip"
],
"properties": {
"zip": {
"type": "string",
"pattern": "^\\d{5}(-?\\d{4})?$",
"maxLength": 10,
"description": "ZIP code."
},
"city": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 100,
"description": "City."
},
"state": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 50,
"description": "State, e.g. \"VA\"."
},
"street": {
"type": "string",
"pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
"maxLength": 200,
"description": "Street address, e.g. \"605 E Main Street, Apt 2\"."
}
},
"description": "Where the work will be done. Ask the customer for it whenever the visit is at their home or business: some businesses' scheduling systems cannot book a job without it.",
"additionalProperties": false
}
},
"description": "The customer the appointment or callback is for.",
"additionalProperties": false
},
"service_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Service the customer is asking about, if known."
},
"location_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Location for multi-location businesses."
},
"operator_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "operator_id from canopy_check_bookable or resolve_business."
},
"customer_zip": {
"type": "string",
"pattern": "^\\d{5}(-?\\d{4})?$",
"maxLength": 10,
"description": "Customer's ZIP code, if known. For a business with several locations, Canopy uses the location nearest to it unless location_id (or a slot or service tied to one location) decides. The result names the location used."
},
"idempotency_key": {
"type": "string",
"pattern": "^[A-Za-z0-9._:-]{8,128}$",
"description": "Optional. Reuse the same key when retrying so nothing is created twice. Derived automatically if omitted."
},
"agent_callback_url": {
"type": "string",
"format": "uri",
"maxLength": 2000,
"description": "Optional HTTPS URL that receives a webhook when the business responds."
}
},
"additionalProperties": false
}