SOPSellers' Open Protocol

予約サービスに接続する

エージェントはMCPまたはREST APIで事業者を検索し、空き枠を確認し、予約を作成・管理します。

公開中予約と照会の機能は提供中です。予約時のオンライン決済はお客さまに代わって支払うをご覧ください。2026年10月22日、最初の事業者(パーソナルトレーナー)がオンライン決済を有効にしました。

このページは、連携を担当するエンジニア向けです。事業者の方は読む必要はありません。事業者側の仕組みはエージェント経由の予約を受けるをご覧ください。

仕組み

SOPの予約コネクターは、MCPサーバーとREST APIの2つのインターフェースを提供します。どちらも同じ予約の中核処理を共有しており、動作は同じです。エージェントの典型的な流れは次のとおりです:事業者を検索し、事業者のデータと料金を読み、空き枠を確認して予約を作成します。オンライン決済が必要なときは支払いを行い、その後で照会・変更・キャンセルができます。

エージェントがSOP経由でサービスを予約し支払う
お客様エージェントSOP事業者サービスを依頼事業者と空き枠を検索サービス、料金、空き枠を返す枠を選ぶrequest_booking枠を確保:awaiting_payment、金額、期限、payment_handlersウォレットで金額を承認支払い認証情報でcomplete_booking1回だけ決済。事業者が辞退・未確認なら全額返金新しい予約を通知初回の予約は事業者が確認。期限内に確認がなければ解放して返金予約状況(確定または確認待ち)と決済結果を返す確定、または事業者の確認待ちを伝える
手順
  1. お客様 → エージェント:サービスを依頼
  2. エージェント → SOP:事業者と空き枠を検索
  3. SOP → エージェント:サービス、料金、空き枠を返す
  4. お客様 → エージェント:枠を選ぶ
  5. エージェント → SOP:request_booking
  6. SOP → エージェント:枠を確保:awaiting_payment、金額、期限、payment_handlers
  7. お客様 → エージェント:ウォレットで金額を承認
  8. エージェント → SOP:支払い認証情報でcomplete_booking
  9. SOP → SOP:1回だけ決済。事業者が辞退・未確認なら全額返金
  10. SOP → 事業者:新しい予約を通知
  11. 事業者 → SOP:初回の予約は事業者が確認。期限内に確認がなければ解放して返金
  12. SOP → エージェント:予約状況(確定または確認待ち)と決済結果を返す
  13. エージェント → お客様:確定、または事業者の確認待ちを伝える

MCPサーバー

エンドポイントはhttps://mcp.srct.tw/mcpです。Streamable HTTPを使い、ステートレスで、レスポンスはJSONです。接続したら、まずinitializeを呼び、次にtools/listでツール一覧を取得します。

MCPセッションの開始: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));
})();

レスポンス例

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
}

本番サービスで実行済み(読み取りのみ)

ツール一覧の取得: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));
})();

レスポンス例

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
}

本番サービスで実行済み(読み取りのみ):表示用に省略しています。…は省略部分を示します。9個のツールが返されます:search_businesses、get_business、get_availability、request_booking、complete_booking、modify_booking、get_booking_status、cancel_booking、get_booking_link。ここでは最初の1つだけを示します。

ツールは全部で9つあります。

ツール用途読み取り専用
search_businessesキーワードまたはカテゴリーで事業者を検索します(最大10件)。はい
get_business事業者の情報、予約・キャンセルのルール、予約できるサービスの料金と所要時間を取得します。はい
get_availabilityあるサービスの今後1〜14日の空き枠と残り枠を取得します。はい
request_bookingお客さまの予約を作成します。いいえ
complete_bookingお客さまが承認した支払い認証情報で、オンライン決済が必要な予約を完了します。いいえ
modify_booking予約の時間帯または人数を変更します。いいえ
get_booking_status予約の状態を取得します(支払いと返金の情報を含む)。はい
cancel_booking予約をキャンセルします。オンラインで支払い済みの場合は返金し、お客さま自身のキャンセルでは決済手数料とプラットフォーム手数料を差し引きます。いいえ
get_booking_link事業者が自社の予約システムを使っている場合に、そのシステムへのリンクを取得します。いいえ(クリックを1回記録します)

書き込み系のツールはidempotency_keyを受け取り、再試行を安全にできます。お客さまに代わって支払うをご覧ください。

search_businessesは、キーワード(事業者名、市区町村、クラスの種類)とカテゴリーで検索できます。get_businessは公開情報と予約できるサービスを返します。business_idとservice_idは、これらのツールが返した値をそのまま使い、自分で組み立てないでください。

事業者の検索: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));
})();

レスポンス例

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
}

本番サービスで実行済み(読み取りのみ):表示用に省略しています。…は省略部分を示します。result.content[0].text(同じJSONの文字列版)は省略しています。

事業者とサービスの取得: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));
})();

レスポンス例

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
}

本番サービスで実行済み(読み取りのみ):表示用に省略しています。…は省略部分を示します。result.content[0].text(同じJSONの文字列版)と事業者の住所・連絡先は省略しています。

空き枠の確認

get_availabilityにはbusiness_idとservice_idが必要で、days(1〜14、既定は14)とteacherも指定できます。返される各時間帯には、固定のslot_id(slotと同じ値)、残り枠、料金が含まれます。タイムゾーンはAsia/Taipeiです。

予約可能な時間枠の確認

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"));
})();

レスポンス例

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"
      }
    },
    …
  ]
}

本番サービスで実行済み(読み取りのみ):表示用に省略しています。…は省略部分を示します。price.amountは通貨の最小単位(台湾ドルの100倍)です。時刻はAsia/Taipeiです。枠は呼び出した日によって変わります。

REST API

同じ中核処理をRESTでも呼び出せます。パスはすべて/api/v1/businesses/{business_id}/の下です。予約に関するエンドポイントはAuthorization: Bearer <status_token>で認証します。status_tokenは予約作成時に1回だけ返されます。

メソッドとパス用途
GET availability?service_id=…&days=…空き枠の確認
POST bookings予約の作成(内容はrequest_bookingと同じ)
GET bookings/{booking_id}予約の取得(金額、期限、支払い方法を含む)
POST bookings/{booking_id}/complete支払い認証情報で予約を完了
PATCH bookings/{booking_id}時間帯または人数の変更
DELETE bookings/{booking_id}予約のキャンセル(支払い済みなら返金。お客さま自身のキャンセルでは決済手数料とプラットフォーム手数料を差し引きます)

エラーは常に{"error": {"code", "message"}}と対応するHTTPステータスで返ります。主なコードはエージェント経由の予約を受けるをご覧ください。

ディスカバリー文書

https://mcp.srct.tw/openapi.jsonは現在、マシンペイメントの支払いリンクだけを記述し、支払いできる操作にx-payment-infoを付けています。現在使われるのはテストネットのマシンペイメントだけです。マシンペイメントをご覧ください。

ディスカバリー文書の取得:/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"));
})();

レスポンス例

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"
          }
        }
      }
    }
  }
}

本番サービスで実行済み(読み取りのみ)

利用時の注意

機械可読なリソース

連携をテストする