SOPSellers' Open Protocol

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.

An agent books and pays for a service through SOP
CustomerAgentSOPSellerAsks for a serviceSearch businesses andopen slotsServices, prices andopen slotsChooses a slotrequest_bookingSlot held:awaiting_payment,amount, expires_at,payment_handlersApproves the exactamount in the walletcomplete_booking withthe payment credentialCharge once; if thebusiness declines ordoesn't confirm,refund in fullNotify the new bookingConfirms a firstvisit; if not intime, it is releasedand refundedBooking status(confirmed orpending) and paymentresultBooking confirmed, orwaiting for thebusiness
Steps
  1. Customer → Agent: Asks for a service
  2. Agent → SOP: Search businesses and open slots
  3. SOP → Agent: Services, prices and open slots
  4. Customer → Agent: Chooses a slot
  5. Agent → SOP: request_booking
  6. SOP → Agent: Slot held: awaiting_payment, amount, expires_at, payment_handlers
  7. Customer → Agent: Approves the exact amount in the wallet
  8. Agent → SOP: complete_booking with the payment credential
  9. SOP → SOP: Charge once; if the business declines or doesn't confirm, refund in full
  10. SOP → Seller: Notify the new booking
  11. Seller → SOP: Confirms a first visit; if not in time, it is released and refunded
  12. SOP → Agent: Booking status (confirmed or pending) and payment result
  13. 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:

ToolPurposeRead-only
search_businessesSearch businesses by keyword or category; returns up to 10.Yes
get_businessGet a business's profile, booking and cancellation rules, and its bookable services with prices and durations.Yes
get_availabilityList open slots and remaining places for one service over the next 1 to 14 days.Yes
request_bookingCreate a booking for the customer.No
complete_bookingComplete a booking that needs online payment, with a payment credential the customer approved.No
modify_bookingChange a booking's slot or quantity.No
get_booking_statusGet a booking's status, including payment and refund details.Yes
cancel_bookingCancel 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_linkFor 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 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 pathPurpose
GET availability?service_id=…&days=…Check availability
POST bookingsCreate a booking (same body as request_booking)
GET bookings/{booking_id}Get a booking, including amount, expiry and payment handlers
POST bookings/{booking_id}/completeComplete 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

Machine-readable resources

Test your integration