Connect to bookings
An agent uses MCP or the REST API to search businesses, check availability, and create and manage bookings.
LiveBooking and lookup features are live. For online payment when booking, see Pay for a customer; the first merchant, a personal trainer, turned on online payment on 2026-10-22.
This page is for the engineers who build the integration. Merchants don't need to read it; to see how it works from the merchant side, read Accept agent bookings.
How it works
SOP's booking connector offers two interfaces: an MCP server and a REST API. Both share one booking core, so they behave the same way. A typical agent flow: search businesses, read a business's data and prices, check availability, create a booking; pay online when the merchant requires it; later look up, change or cancel the booking.
Steps
- Customer → Agent: Asks for a service
- Agent → SOP: Search businesses and open slots
- SOP → Agent: Services, prices and open slots
- Customer → Agent: Chooses a slot
- Agent → SOP: request_booking
- SOP → Agent: Slot held: awaiting_payment, amount, expires_at, payment_handlers
- Customer → Agent: Approves the exact amount in the wallet
- Agent → SOP: complete_booking with the payment credential
- SOP → SOP: Charge once; if the business declines or doesn't confirm, refund in full
- SOP → Seller: Notify the new booking
- Seller → SOP: Confirms a first visit; if not in time, it is released and refunded
- SOP → Agent: Booking status (confirmed or pending) and payment result
- Agent → Customer: Booking confirmed, or waiting for the business
MCP server
The endpoint is https://mcp.srct.tw/mcp. It uses Streamable HTTP, is stateless, and replies in JSON. After connecting, call initialize, then tools/list to get the tool list.
Open an MCP session: initialize
curl
curl -sS -X POST https://mcp.srct.tw/mcp \
-H "User-Agent: my-agent/1.0" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"my-agent","version":"1.0"}}}'
Python
import json
import urllib.error
import urllib.request
BASE = "https://mcp.srct.tw"
def call(method, path, body=None, headers=None):
req = urllib.request.Request(
BASE + path,
data=None if body is None else json.dumps(body).encode(),
method=method,
headers={"User-Agent": "my-agent/1.0", "Content-Type": "application/json", **(headers or {})},
)
try:
with urllib.request.urlopen(req) as res:
return res.status, json.loads(res.read())
except urllib.error.HTTPError as err: # 4xx and 5xx responses still carry a JSON body
return err.code, json.loads(err.read())
def show(status, body):
print("HTTP", status)
print(json.dumps(body, ensure_ascii=False, indent=2))
MCP = {"Accept": "application/json, text/event-stream"} # the server requires both types
show(*call("POST", "/mcp", {
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {"protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": {"name": "my-agent", "version": "1.0"}},
}, MCP))
Node.js
const BASE = "https://mcp.srct.tw";
async function call(method, path, body, headers = {}) {
const res = await fetch(BASE + path, {
method,
headers: { "User-Agent": "my-agent/1.0", "Content-Type": "application/json", ...headers },
body: body === undefined ? undefined : JSON.stringify(body),
});
return { status: res.status, body: await res.json() };
}
function show({ status, body }) {
console.log("HTTP", status);
console.log(JSON.stringify(body, null, 2));
}
const MCP = { Accept: "application/json, text/event-stream" }; // the server requires both types
(async () => {
show(await call("POST", "/mcp", {
jsonrpc: "2.0",
id: 1,
method: "initialize",
params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "my-agent", version: "1.0" } },
}, MCP));
})();
Example response
HTTP 200
{
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": {
"listChanged": true
}
},
"serverInfo": {
"name": "…-booking",
"title": "…",
"version": "1.0.0",
"websiteUrl": "https://mcp.srct.tw/docs"
},
"instructions": "… lists local service businesses (gyms, personal trainers, studios) in Taiwan that accept bookings through AI agents. Times are Asia/Taipei. Bookings are paid at the venue, unless the business takes online payment: then request_booking returns status awaiting_payment with amount, currency, expires_at and payment_handlers, and complete_booking takes a Stripe shared payment token (payments are collected by Seller Tech U.S.A. LLC through Stripe; unconfirmed paid bookings are refunded in full). A booking is either confirmed immediately or waits for the business to confirm; the status field says which. A pending booking is held only for the business's pending_hold time (10 minutes unless the business sets another); if not confirmed by then, it is released."
},
"jsonrpc": "2.0",
"id": 1
}Run against the live service (read-only)
List the tools: tools/list
curl
curl -sS -X POST https://mcp.srct.tw/mcp \
-H "User-Agent: my-agent/1.0" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
Python
import json
import urllib.error
import urllib.request
BASE = "https://mcp.srct.tw"
def call(method, path, body=None, headers=None):
req = urllib.request.Request(
BASE + path,
data=None if body is None else json.dumps(body).encode(),
method=method,
headers={"User-Agent": "my-agent/1.0", "Content-Type": "application/json", **(headers or {})},
)
try:
with urllib.request.urlopen(req) as res:
return res.status, json.loads(res.read())
except urllib.error.HTTPError as err: # 4xx and 5xx responses still carry a JSON body
return err.code, json.loads(err.read())
def show(status, body):
print("HTTP", status)
print(json.dumps(body, ensure_ascii=False, indent=2))
MCP = {"Accept": "application/json, text/event-stream"} # the server requires both types
show(*call("POST", "/mcp", {"jsonrpc": "2.0", "id": 2, "method": "tools/list"}, MCP))
Node.js
const BASE = "https://mcp.srct.tw";
async function call(method, path, body, headers = {}) {
const res = await fetch(BASE + path, {
method,
headers: { "User-Agent": "my-agent/1.0", "Content-Type": "application/json", ...headers },
body: body === undefined ? undefined : JSON.stringify(body),
});
return { status: res.status, body: await res.json() };
}
function show({ status, body }) {
console.log("HTTP", status);
console.log(JSON.stringify(body, null, 2));
}
const MCP = { Accept: "application/json, text/event-stream" }; // the server requires both types
(async () => {
show(await call("POST", "/mcp", { jsonrpc: "2.0", id: 2, method: "tools/list" }, MCP));
})();
Example response
HTTP 200
{
"result": {
"tools": [
{
"name": "search_businesses",
"title": "Search businesses",
"description": "Search …'s listed businesses by keyword (business name, city or district, or type of class) and/or category. Returns up to 10 businesses with a few matching services. Read-only.",
"inputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"keyword": {
"description": "Words to match, e.g. 'personal training' or 'Taipei'",
"type": "string",
"maxLength": 40
},
"category": {
"description": "gym, coaching (personal trainers), sauna, or recovery",
"type": "string",
"enum": [
"gym",
"coaching",
"sauna",
"recovery"
]
},
"limit": {
"description": "Maximum results, default 5",
"type": "integer",
"minimum": 1,
"maximum": 10
}
}
},
"annotations": {
"title": "Search businesses",
"readOnlyHint": true,
"destructiveHint": false,
"idempotentHint": true,
"openWorldHint": false
},
"execution": {
"taskSupport": "forbidden"
}
},
…
]
},
"jsonrpc": "2.0",
"id": 2
}Run against the live service (read-only): Shortened for display; … marks omitted parts. The service returned 9 tools: search_businesses, get_business, get_availability, request_booking, complete_booking, modify_booking, get_booking_status, cancel_booking, get_booking_link. Only the first is shown.
There are 9 tools:
| Tool | Purpose | Read-only |
|---|---|---|
search_businesses | Search businesses by keyword or category; returns up to 10. | Yes |
get_business | Get a business's profile, booking and cancellation rules, and its bookable services with prices and durations. | Yes |
get_availability | List open slots and remaining places for one service over the next 1 to 14 days. | Yes |
request_booking | Create a booking for the customer. | No |
complete_booking | Complete a booking that needs online payment, with a payment credential the customer approved. | No |
modify_booking | Change a booking's slot or quantity. | No |
get_booking_status | Get a booking's status, including payment and refund details. | Yes |
cancel_booking | Cancel a booking; a booking paid online is refunded, and when the customer cancels it, the payment processing fee and the platform fee are deducted. | No |
get_booking_link | For a merchant that uses its own booking system, get a link to that system. | No (one click is counted) |
Write tools accept idempotency_key, which makes retries safe; see Pay for a customer.
Search businesses and read data
search_businesses searches by keyword (business name, city or district, type of class) and by category. get_business returns the public profile and the bookable services. Use the business_id and service_id values these tools return; don't construct them yourself.
Search businesses: search_businesses
curl
curl -sS -X POST https://mcp.srct.tw/mcp \
-H "User-Agent: my-agent/1.0" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_businesses","arguments":{"keyword":"Taipei","limit":3}}}'
Python
import json
import urllib.error
import urllib.request
BASE = "https://mcp.srct.tw"
def call(method, path, body=None, headers=None):
req = urllib.request.Request(
BASE + path,
data=None if body is None else json.dumps(body).encode(),
method=method,
headers={"User-Agent": "my-agent/1.0", "Content-Type": "application/json", **(headers or {})},
)
try:
with urllib.request.urlopen(req) as res:
return res.status, json.loads(res.read())
except urllib.error.HTTPError as err: # 4xx and 5xx responses still carry a JSON body
return err.code, json.loads(err.read())
def show(status, body):
print("HTTP", status)
print(json.dumps(body, ensure_ascii=False, indent=2))
MCP = {"Accept": "application/json, text/event-stream"} # the server requires both types
show(*call("POST", "/mcp", {"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "search_businesses", "arguments": {"keyword": "Taipei", "limit": 3}}}, MCP))
Node.js
const BASE = "https://mcp.srct.tw";
async function call(method, path, body, headers = {}) {
const res = await fetch(BASE + path, {
method,
headers: { "User-Agent": "my-agent/1.0", "Content-Type": "application/json", ...headers },
body: body === undefined ? undefined : JSON.stringify(body),
});
return { status: res.status, body: await res.json() };
}
function show({ status, body }) {
console.log("HTTP", status);
console.log(JSON.stringify(body, null, 2));
}
const MCP = { Accept: "application/json, text/event-stream" }; // the server requires both types
(async () => {
show(await call("POST", "/mcp", { jsonrpc: "2.0", id: 3, method: "tools/call", params: { name: "search_businesses", arguments: { keyword: "Taipei", limit: 3 } } }, MCP));
})();
Example response
HTTP 200
{
"result": {
"content": [
{
"type": "text",
"text": …
}
],
"structuredContent": {
"ok": true,
"count": 1,
"results": [
{
"business_id": "coach-dunken",
"name": "Dunken教練",
"city": "台北市信義區(Taipei Xinyi)",
"categories": [
"coaching"
],
"booking_mode": "request_here",
"services": [
{
"service_id": "first_assessment",
"name": "初次體能檢查(含InBody一次)",
"price": "免費"
},
{
"service_id": "pt",
"name": "PT一對一教練課",
"price": "NT$18,000/10堂"
},
{
"service_id": "trx",
"name": "TRX懸吊訓練",
"price": "NT$18,000/10堂"
}
]
}
]
},
"isError": false
},
"jsonrpc": "2.0",
"id": 3
}Run against the live service (read-only): Shortened for display; … marks omitted parts. result.content[0].text (the same JSON as a string) is omitted.
Get a business and its services: get_business
curl
curl -sS -X POST https://mcp.srct.tw/mcp \
-H "User-Agent: my-agent/1.0" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"get_business","arguments":{"business_id":"coach-dunken"}}}'
Python
import json
import urllib.error
import urllib.request
BASE = "https://mcp.srct.tw"
def call(method, path, body=None, headers=None):
req = urllib.request.Request(
BASE + path,
data=None if body is None else json.dumps(body).encode(),
method=method,
headers={"User-Agent": "my-agent/1.0", "Content-Type": "application/json", **(headers or {})},
)
try:
with urllib.request.urlopen(req) as res:
return res.status, json.loads(res.read())
except urllib.error.HTTPError as err: # 4xx and 5xx responses still carry a JSON body
return err.code, json.loads(err.read())
def show(status, body):
print("HTTP", status)
print(json.dumps(body, ensure_ascii=False, indent=2))
MCP = {"Accept": "application/json, text/event-stream"} # the server requires both types
show(*call("POST", "/mcp", {"jsonrpc": "2.0", "id": 4, "method": "tools/call", "params": {"name": "get_business", "arguments": {"business_id": "coach-dunken"}}}, MCP))
Node.js
const BASE = "https://mcp.srct.tw";
async function call(method, path, body, headers = {}) {
const res = await fetch(BASE + path, {
method,
headers: { "User-Agent": "my-agent/1.0", "Content-Type": "application/json", ...headers },
body: body === undefined ? undefined : JSON.stringify(body),
});
return { status: res.status, body: await res.json() };
}
function show({ status, body }) {
console.log("HTTP", status);
console.log(JSON.stringify(body, null, 2));
}
const MCP = { Accept: "application/json, text/event-stream" }; // the server requires both types
(async () => {
show(await call("POST", "/mcp", { jsonrpc: "2.0", id: 4, method: "tools/call", params: { name: "get_business", arguments: { business_id: "coach-dunken" } } }, MCP));
})();
Example response
HTTP 200
{
"result": {
"content": [
{
"type": "text",
"text": …
}
],
"structuredContent": {
"ok": true,
"business": {
"business_id": "coach-dunken",
"name": "Dunken教練",
"categories": [
"coaching"
],
"booking_mode": "request_here",
"city": "台北市信義區(Taipei Xinyi)",
"profile": {
"city": "台北市信義區(Taipei Xinyi)",
"address": "…",
"hours": "每天08:00至20:00",
"booking_policy": "取消與改期需在課程開始24小時前(經AI助理線上取消也是開始前24小時截止)。遲到15分鐘將直接結束當次課程,且無法退費。",
"payment": "現金或轉帳。課程一次購買10堂NT$18,000(每堂折合NT$1,800)。",
"notes": "請自行準備合適的訓練服裝,至少提早10分鐘抵達教室。",
"contact": "…"
},
"faq": [],
"quantity": {
"label": "人數",
"unit": "位",
"max_per_booking": 1
},
"online_cancellation": {
"closes_minutes_before_start": 1440,
"text": "Online cancellation closes 24 hours before the start time; after that, contact the business directly."
},
"pending_hold": {
"minutes": 10,
"text": "Bookings that need the business's confirmation are held for 10 minutes. If the business does not confirm by then, the hold is released automatically and the customer can choose another slot."
},
"payment": {
"mode": "venue",
"text": "Pay at the venue."
}
},
"services": [
{
"service_id": "first_assessment",
"name": "初次體能檢查(含InBody一次)",
"category": "一對一",
"summary": "第一次上課:基本動作與體能檢查,並免費做一次InBody身體組成量測(非醫療評估)。",
"price": {
"amount": 0,
"currency": "TWD",
"unit": "每次",
"text": "免費"
},
"duration_min": 60,
"schedule": [
{
"weekday": "sun",
"time": "08:00",
"teacher": "Dunken"
},
{
"weekday": "sun",
"time": "09:00",
"teacher": "Dunken"
},
…
],
"capacity_per_slot": 1,
"teachers": [
"Dunken"
],
"credit_cost": 0
},
…
],
"payment": "Paid services are paid at the venue; services priced at 0 are free. This connector does not take payments."
},
"isError": false
},
"jsonrpc": "2.0",
"id": 4
}Run against the live service (read-only): Shortened for display; … marks omitted parts. result.content[0].text (the same JSON as a string) and the business's address and contact details are omitted.
Check availability
get_availability needs business_id and service_id, and accepts days (1 to 14, default 14) and teacher. Every slot has a stable slot_id (the same value as slot), the remaining places and the price; the time zone is Asia/Taipei.
Check open time slots
curl
curl -sS "https://mcp.srct.tw/api/v1/businesses/coach-dunken/availability?service_id=pt&days=2" \
-H "User-Agent: my-agent/1.0"
Python
import json
import urllib.error
import urllib.request
BASE = "https://mcp.srct.tw"
def call(method, path, body=None, headers=None):
req = urllib.request.Request(
BASE + path,
data=None if body is None else json.dumps(body).encode(),
method=method,
headers={"User-Agent": "my-agent/1.0", "Content-Type": "application/json", **(headers or {})},
)
try:
with urllib.request.urlopen(req) as res:
return res.status, json.loads(res.read())
except urllib.error.HTTPError as err: # 4xx and 5xx responses still carry a JSON body
return err.code, json.loads(err.read())
def show(status, body):
print("HTTP", status)
print(json.dumps(body, ensure_ascii=False, indent=2))
show(*call("GET", "/api/v1/businesses/coach-dunken/availability?service_id=pt&days=2"))
Node.js
const BASE = "https://mcp.srct.tw";
async function call(method, path, body, headers = {}) {
const res = await fetch(BASE + path, {
method,
headers: { "User-Agent": "my-agent/1.0", "Content-Type": "application/json", ...headers },
body: body === undefined ? undefined : JSON.stringify(body),
});
return { status: res.status, body: await res.json() };
}
function show({ status, body }) {
console.log("HTTP", status);
console.log(JSON.stringify(body, null, 2));
}
(async () => {
show(await call("GET", "/api/v1/businesses/coach-dunken/availability?service_id=pt&days=2"));
})();
Example response
HTTP 200
{
"ok": true,
"service_id": "pt",
"bookable_here": true,
"payment": "venue",
"timezone": "Asia/Taipei",
"earliest_bookable": "120 minutes from now",
"online_cancellation": "Online cancellation closes 24 hours before the start time; after that, contact the business directly.",
"quantity_unit": "位",
"slots": [
{
"slot_id": "2026-10-22T08:00",
"slot": "2026-10-22T08:00",
"start": "2026-10-22T08:00:00+08:00",
"label": "10/22(四)08:00",
"remaining": 1,
"price": {
"amount": 1800000,
"currency": "twd",
"per": "person or place",
"text": "NT$18,000/10堂"
},
"resource": {
"teacher": "Dunken"
}
},
{
"slot_id": "2026-10-22T09:00",
"slot": "2026-10-22T09:00",
"start": "2026-10-22T09:00:00+08:00",
"label": "10/22(四)09:00",
"remaining": 1,
"price": {
"amount": 1800000,
"currency": "twd",
"per": "person or place",
"text": "NT$18,000/10堂"
},
"resource": {
"teacher": "Dunken"
}
},
…
]
}Run against the live service (read-only): Shortened for display; … marks omitted parts. price.amount is in the smallest currency unit (TWD x100); times are Asia/Taipei. The slots depend on the day you call.
REST API
The same core is available over REST, with all paths under /api/v1/businesses/{business_id}/. Booking endpoints authenticate with Authorization: Bearer <status_token>; the status_token is returned only once, when the booking is created.
| Method and path | Purpose |
|---|---|
GET availability?service_id=…&days=… | Check availability |
POST bookings | Create a booking (same body as request_booking) |
GET bookings/{booking_id} | Get a booking, including amount, expiry and payment handlers |
POST bookings/{booking_id}/complete | Complete a booking with a payment credential |
PATCH bookings/{booking_id} | Change the slot or quantity |
DELETE bookings/{booking_id} | Cancel a booking (a paid booking is refunded; when the customer cancels it, the payment processing fee and the platform fee are deducted) |
Errors are always {"error": {"code", "message"}} with a matching HTTP status; common codes are in Accept agent bookings.
Discovery document
https://mcp.srct.tw/openapi.json currently describes only the machine-payment link, and marks payable operations with x-payment-info. Today only testnet machine payments use it; see Machine payments.
Read the discovery document: /openapi.json
curl
curl -sS "https://mcp.srct.tw/openapi.json" \
-H "User-Agent: my-agent/1.0"
Python
import json
import urllib.error
import urllib.request
BASE = "https://mcp.srct.tw"
def call(method, path, body=None, headers=None):
req = urllib.request.Request(
BASE + path,
data=None if body is None else json.dumps(body).encode(),
method=method,
headers={"User-Agent": "my-agent/1.0", "Content-Type": "application/json", **(headers or {})},
)
try:
with urllib.request.urlopen(req) as res:
return res.status, json.loads(res.read())
except urllib.error.HTTPError as err: # 4xx and 5xx responses still carry a JSON body
return err.code, json.loads(err.read())
def show(status, body):
print("HTTP", status)
print(json.dumps(body, ensure_ascii=False, indent=2))
show(*call("GET", "/openapi.json"))
Node.js
const BASE = "https://mcp.srct.tw";
async function call(method, path, body, headers = {}) {
const res = await fetch(BASE + path, {
method,
headers: { "User-Agent": "my-agent/1.0", "Content-Type": "application/json", ...headers },
body: body === undefined ? undefined : JSON.stringify(body),
});
return { status: res.status, body: await res.json() };
}
function show({ status, body }) {
console.log("HTTP", status);
console.log(JSON.stringify(body, null, 2));
}
(async () => {
show(await call("GET", "/openapi.json"));
})();
Example response
HTTP 200
{
"openapi": "3.1.0",
"info": {
"title": "… booking payments",
"version": "1.0.0",
"description": "Pay for … bookings with x402 or MPP. Create the booking first with the MCP tool request_booking or POST /api/v1/businesses/{business_id}/bookings; use the payment_link it returns."
},
"servers": [
{
"url": "https://mcp.srct.tw"
}
],
"x-service-info": {
"categories": [
"booking",
"fitness",
"services"
],
"docs": {
"homepage": "https://mcp.srct.tw/",
"apiReference": "https://mcp.srct.tw/docs"
}
},
"paths": {
"/pay/{business_id}/{booking_id}": {
"post": {
"summary": "Pay for a booking (x402 or MPP)",
"parameters": [
{
"name": "business_id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "booking_id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "t",
"in": "query",
"required": true,
"schema": {
"type": "string"
},
"description": "pay token from the booking's payment_link"
}
],
"x-payment-info": {
"offers": [
{
"intent": "charge",
"method": "evm",
"amount": null,
"currency": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"description": "USDC on Base Sepolia (test network); also accepted as x402 exact"
},
{
"intent": "charge",
"method": "tempo",
"amount": null,
"currency": "0x20c0000000000000000000000000000000000000",
"description": "pathUSD on Tempo test network (Moderato)"
}
]
},
"responses": {
"200": {
"description": "Paid: booking and payment result"
},
"402": {
"description": "Payment Required: x402 PAYMENT-REQUIRED and MPP WWW-Authenticate challenges"
},
"409": {
"description": "Booking not payable"
},
"410": {
"description": "Payment window passed"
}
}
},
"get": {
"summary": "Pay for a booking (x402 or MPP)",
"parameters": [
{
"name": "business_id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "booking_id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "t",
"in": "query",
"required": true,
"schema": {
"type": "string"
},
"description": "pay token from the booking's payment_link"
}
],
"x-payment-info": {
"offers": [
{
"intent": "charge",
"method": "evm",
"amount": null,
"currency": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"description": "USDC on Base Sepolia (test network); also accepted as x402 exact"
},
{
"intent": "charge",
"method": "tempo",
"amount": null,
"currency": "0x20c0000000000000000000000000000000000000",
"description": "pathUSD on Tempo test network (Moderato)"
}
]
},
"responses": {
"200": {
"description": "Paid: booking and payment result"
},
"402": {
"description": "Payment Required: x402 PAYMENT-REQUIRED and MPP WWW-Authenticate challenges"
},
"409": {
"description": "Booking not payable"
},
"410": {
"description": "Payment window passed"
}
}
}
}
}
}Run against the live service (read-only)
Things to know when using it
- Write operations have an hourly limit; going over returns
rate_limited(429 over REST). - Before creating a booking, get the customer's agreement to the service, time, quantity and contact details; the name, phone number and email are sent only to that business.
- Don't put health or medical information in the
notefield. - A booking can be
confirmed(immediately),pending(waiting for the merchant) orawaiting_payment(waiting for payment); rely on thestatusfield in the response. - A merchant takes payment at the venue by default;
awaiting_paymentappears only when the merchant has turned on online payment.
Machine-readable resources
tools/liston the MCP endpoint returns every tool with its input schema./openapi.jsondescribes the machine-payment link (testnet) and marks it withx-payment-info.- There is no
llms.txtor/.well-known/ucpdocument yet.
Test your integration
- Read-only calls (
search_businesses,get_business,get_availability,tools/list) are safe to run against the live service. - Creating, changing or cancelling a booking acts on a real business's calendar. There is no public test business yet; contact us to arrange one.
- Send your own
User-Agentheader. Requests with the default Pythonurllibagent are blocked by the network edge.
Related pages
- Pay for a customer: Complete a booking payment with a credential the customer approved
- Accept agent bookings: For merchants: what agents can do, where you confirm, how refunds work
- Protocols and standards: How MCP, REST, UCP, MPP and x402 map to SOP