SOPSellers' Open Protocol

エージェント経由の予約を受ける

お客さまのAIエージェントが貴店の空き枠を確認し、予約・変更・キャンセルできるようにします。確認は受付確認デスクで行います。

公開中予約は提供中です。オンライン決済も提供中で、2026年10月22日に最初の事業者(パーソナルトレーナー)が有効にしました。

このページは、事業者の方と、事業者に代わって連携を担当する方向けです。

仕組み

サービス、料金、空き枠は、貴店が一つずつ確認してから公開します。お客さまが自分のAIに予約を頼むと、AIがSOPの予約インターフェースを通じて送信します。貴店は受付確認デスクで承認または辞退し、LINEの通知も届きます。

エージェントが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ツールREST
空き枠の確認get_availabilityGET availability
予約の作成request_bookingPOST bookings
予約の照会get_booking_statusGET bookings/{booking_id}
変更modify_bookingPATCH bookings/{booking_id}
キャンセルcancel_bookingDELETE bookings/{booking_id}
支払いcomplete_bookingPOST bookings/{booking_id}/complete

各操作のパラメーターとレスポンスは予約サービスに接続するをご覧ください。以下のサンプルで、事業者側が目にする内容を説明します。

空き枠の確認

エージェントはまず貴店の空き枠を確認します。各時間帯には固定のslot_idがあり、予約の作成や変更ではこれを使います。レスポンスには残り枠と料金も含まれます。

予約可能な時間枠の確認

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です。枠は呼び出した日によって変わります。

予約の作成

お客さまがサービス、時間、人数、連絡先に同意すると、エージェントが予約を送信します。結果は貴店の設定によって変わります。店頭払いの予約は、すぐに確定(confirmed)するか、貴店の確認待ち(pending)になります。オンライン決済の予約は、まずawaiting_paymentになり、支払いが完了してから貴店に通知されます。

予約の作成(現地払い)

curl

# This creates a real booking at a real business. Use a slot value from the availability response.
curl -sS -X POST "https://mcp.srct.tw/api/v1/businesses/<BUSINESS_ID>/bookings" \
  -H "User-Agent: my-agent/1.0" \
  -H "Content-Type: application/json" \
  -d '{"service_id":"pt","slot":"<SLOT>","quantity":1,"customer_name":"Example Customer","phone":"0900000000","email":"customer@example.com"}'

Python

# This creates a real booking at a real business. Use a slot value from the availability response.
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))


BUSINESS_ID = "<BUSINESS_ID>"

status, body = call("POST", f"/api/v1/businesses/{BUSINESS_ID}/bookings", {
    "service_id": "pt",
    "slot": "<SLOT>",  # the "slot" value from the availability response
    "quantity": 1,
    "customer_name": "Example Customer",
    "phone": "0900000000",
    "email": "customer@example.com",
})
show(status, body)
# Keep booking_id and status_token: every later call for this booking needs both.

Node.js

// This creates a real booking at a real business. Use a slot value from the availability response.
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 BUSINESS_ID = "<BUSINESS_ID>";

(async () => {
  show(await call("POST", `/api/v1/businesses/${BUSINESS_ID}/bookings`, {
    service_id: "pt",
    slot: "<SLOT>", // the "slot" value from the availability response
    quantity: 1,
    customer_name: "Example Customer",
    phone: "0900000000",
    email: "customer@example.com",
  }));
  // Keep booking_id and status_token: every later call for this booking needs both.
})();

レスポンス例

HTTP 201
{
  "ok": true,
  "booking": {
    "booking_id": "2QAC3NW5",
    "status": "pending",
    "status_text": "Waiting for the business to confirm",
    "business_id": "coach-dunken",
    "business_name": "Dunken教練",
    "service_id": "pt",
    "service_name": "PT一對一教練課",
    "slot": "2026-10-25T08:00",
    "start": "2026-10-25T08:00:00+08:00",
    "label": "10/25(日)08:00",
    "quantity": 1,
    "quantity_unit": "位",
    "payment": "Pay at the venue",
    "details": [
      "老師:Dunken"
    ],
    "why_pending": "First booking with this business from these contact details, so the business confirms it.",
    "confirm_by": "2026-10-22T01:00:00+08:00",
    "hold": "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.",
    "online_cancel_until": "2026-10-24T08:00:00+08:00"
  },
  "status_token": "<STATUS_TOKEN>",
  "status_token_note": "Needed with booking_id to check, pay, change or cancel this booking later. It gives access to this booking only, and … does not show it again.",
  "contact_note": "If this booking cannot be cancelled with cancel_booking, the customer can contact the business directly: …"
}

ローカル環境で実行済み:本番と同じ項目で返るよう、本番事業者のサービスを写したコネクターのローカルコピーで実行しました。status_tokenは<STATUS_TOKEN>に置き換えています。新しい連絡先での初回予約は事業者の確認待ち(statusはpending)になります。

変更

エージェントはお客さまに代わって、時間帯または人数を変更できます。元の枠は解放されます。オンラインで支払い済みの予約は、時間帯のみ変更できます。人数を変えると金額が変わるため、いったんキャンセルして予約し直す必要があります。変更とオンラインでのキャンセルは、同じ締め切りまで可能です。

予約の日時変更

curl

curl -sS -X PATCH "https://mcp.srct.tw/api/v1/businesses/<BUSINESS_ID>/bookings/<BOOKING_ID>" \
  -H "User-Agent: my-agent/1.0" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <STATUS_TOKEN>" \
  -d '{"slot":"<NEW_SLOT>"}'

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))


BUSINESS_ID = "<BUSINESS_ID>"
BOOKING_ID = "<BOOKING_ID>"      # from the create response
STATUS_TOKEN = "<STATUS_TOKEN>"  # from the create response (shown once)
AUTH = {"Authorization": f"Bearer {STATUS_TOKEN}"}
NEW_SLOT = "<NEW_SLOT>"  # another "slot" value from the availability response

show(*call("PATCH", f"/api/v1/businesses/{BUSINESS_ID}/bookings/{BOOKING_ID}", {"slot": NEW_SLOT}, AUTH))

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 BUSINESS_ID = "<BUSINESS_ID>";
const BOOKING_ID = "<BOOKING_ID>"; // from the create response
const STATUS_TOKEN = "<STATUS_TOKEN>"; // from the create response (shown once)
const AUTH = { Authorization: `Bearer ${STATUS_TOKEN}` };
const NEW_SLOT = "<NEW_SLOT>"; // another "slot" value from the availability response

(async () => {
  show(await call("PATCH", `/api/v1/businesses/${BUSINESS_ID}/bookings/${BOOKING_ID}`, { slot: NEW_SLOT }, AUTH));
})();

レスポンス例

HTTP 200
{
  "ok": true,
  "changed": {
    "from": {
      "slot": "2026-10-25T08:00",
      "quantity": 1
    },
    "to": {
      "slot": "2026-10-26T08:00",
      "quantity": 1
    }
  },
  "booking": {
    "booking_id": "2QAC3NW5",
    "status": "pending",
    "status_text": "Waiting for the business to confirm",
    "business_id": "coach-dunken",
    "business_name": "Dunken教練",
    "service_id": "pt",
    "service_name": "PT一對一教練課",
    "slot": "2026-10-26T08:00",
    "start": "2026-10-26T08:00:00+08:00",
    "label": "10/26(一)08:00",
    "quantity": 1,
    "quantity_unit": "位",
    "payment": "Pay at the venue",
    "details": [
      "老師:Dunken"
    ],
    "why_pending": "First booking with this business from these contact details, so the business confirms it.",
    "confirm_by": "2026-10-22T01:00:00+08:00",
    "hold": "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.",
    "online_cancel_until": "2026-10-25T08:00:00+08:00"
  }
}

ローカル環境で実行済み:コネクターのローカルコピーで実行。元の枠は解放され、新しい枠が確保されます。レスポンスにfromとtoが含まれます。

キャンセル

お客さまがAIからキャンセルすると、枠が解放され、受付確認デスクに記録が残ります。オンラインでキャンセルできる期限は貴店が設定します。期限を過ぎたら、お客さまは貴店に直接連絡します。お客さまがオンラインで支払い済みの予約をキャンセルした場合は、決済手数料とプラットフォーム手数料を差し引いて返金します。

予約のキャンセル

curl

curl -sS -X DELETE "https://mcp.srct.tw/api/v1/businesses/<BUSINESS_ID>/bookings/<BOOKING_ID>" \
  -H "User-Agent: my-agent/1.0" \
  -H "Authorization: Bearer <STATUS_TOKEN>"

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))


BUSINESS_ID = "<BUSINESS_ID>"
BOOKING_ID = "<BOOKING_ID>"      # from the create response
STATUS_TOKEN = "<STATUS_TOKEN>"  # from the create response (shown once)
AUTH = {"Authorization": f"Bearer {STATUS_TOKEN}"}

show(*call("DELETE", f"/api/v1/businesses/{BUSINESS_ID}/bookings/{BOOKING_ID}", headers=AUTH))

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 BUSINESS_ID = "<BUSINESS_ID>";
const BOOKING_ID = "<BOOKING_ID>"; // from the create response
const STATUS_TOKEN = "<STATUS_TOKEN>"; // from the create response (shown once)
const AUTH = { Authorization: `Bearer ${STATUS_TOKEN}` };

(async () => {
  show(await call("DELETE", `/api/v1/businesses/${BUSINESS_ID}/bookings/${BOOKING_ID}`, undefined, AUTH));
})();

レスポンス例

HTTP 200
{
  "ok": true,
  "booking": {
    "booking_id": "2QAC3NW5",
    "status": "cancelled",
    "status_text": "Cancelled",
    "business_id": "coach-dunken",
    "business_name": "Dunken教練",
    "service_id": "pt",
    "service_name": "PT一對一教練課",
    "slot": "2026-10-26T08:00",
    "start": "2026-10-26T08:00:00+08:00",
    "label": "10/26(一)08:00",
    "quantity": 1,
    "quantity_unit": "位",
    "payment": "Pay at the venue",
    "details": [
      "老師:Dunken"
    ],
    "cancelled_by": "customer"
  }
}

ローカル環境で実行済み:コネクターのローカルコピーで実行。現地払いの予約には返金項目はありません。オンライン決済はrefund_resultを参照してください。

構造化されたエラー

エラーは常にcodeとmessageを返し、エージェントが何が起きたか、再試行してよいかを判断できます。たとえば、お客さまが選んだ時間帯がちょうど埋まった場合は、代わりの時間帯を添えてcapacity_full(RESTでは409)を返します。エージェントは別の時間帯で予約し直せます。

構造化エラーの処理:枠が埋まっている場合

curl

# This creates a real booking at a real business. Use a slot value from the availability response.
curl -sS -X POST "https://mcp.srct.tw/api/v1/businesses/<BUSINESS_ID>/bookings" \
  -H "User-Agent: my-agent/1.0" \
  -H "Content-Type: application/json" \
  -d '{"service_id":"pt","slot":"<FULL_SLOT>","quantity":1,"customer_name":"Example Customer","phone":"0900000000","email":"customer@example.com"}'

Python

# This creates a real booking at a real business. Use a slot value from the availability response.
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))


BUSINESS_ID = "<BUSINESS_ID>"

status, body = call("POST", f"/api/v1/businesses/{BUSINESS_ID}/bookings", {
    "service_id": "pt",
    "slot": "<FULL_SLOT>",  # a slot that is already full
    "quantity": 1,
    "customer_name": "Example Customer",
    "phone": "0900000000",
    "email": "customer@example.com",
})
show(status, body)
# status 409, body["error"]["code"] == "capacity_full": offer the customer one of body["next_slots"] and call again.

Node.js

// This creates a real booking at a real business. Use a slot value from the availability response.
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 BUSINESS_ID = "<BUSINESS_ID>";

(async () => {
  const result = await call("POST", `/api/v1/businesses/${BUSINESS_ID}/bookings`, {
    service_id: "pt",
    slot: "<FULL_SLOT>", // a slot that is already full
    quantity: 1,
    customer_name: "Example Customer",
    phone: "0900000000",
    email: "customer@example.com",
  });
  show(result);
  // status 409, body.error.code === "capacity_full": offer the customer one of body.next_slots and call again.
})();

レスポンス例

HTTP 409
{
  "error": {
    "code": "capacity_full",
    "message": "Only 0 left in this slot. Choose another slot."
  },
  "next_slots": [
    "2026-10-22T08:00",
    "2026-10-22T09:00",
    "2026-10-22T10:00"
  ]
}

ローカル環境で実行済み:コネクターのローカルコピーで実行。各実行の前に、別の顧客がその枠を予約済みにしました。next_slotsに代わりの空き枠が入ります。

コード意味RESTステータス
capacity_fullこの時間帯の残り枠が足りません409
invalid_slot時間帯が存在しない、または予約できません409
duplicate同じ連絡先で、同じサービスと時間帯がもう一度送信されました409
too_late_to_cancelオンラインでのキャンセル期限を過ぎています409
payment_declined支払いが拒否されました。別の支払い方法で再試行できます402
payment_expired支払い期限を過ぎ、枠は解放されました410
rate_limited回数の上限を超えました429

支払い後に予約が成立しなかった場合:自動返金

オンラインで支払い済みの予約は、次の場合にその場で全額返金します:貴店が辞退またはキャンセルした、貴店が時間内に確認しなかった。お客さまが締め切り前にオンラインでキャンセルした場合、カード決済の返金額は、決済手数料とプラットフォーム手数料を差し引いた額になります。返金に失敗した場合は、システムが5分ごとに再試行します。

支払い済みの予約がキャンセル・不成立になった場合、SOPが返金
エージェントSOP事業者認証情報でcomplete_booking決済成功事業者が辞退、または期限内に未確認自動で全額返金または:お客様が期限前に自分でキャンセル決済手数料とプラットフォーム手数料を差し引いて返金返金額と差し引いた手数料を返す
手順
  1. エージェント → SOP:認証情報でcomplete_booking
  2. SOP → SOP:決済成功
  3. 事業者 → SOP:事業者が辞退、または期限内に未確認
  4. SOP → SOP:自動で全額返金
  5. エージェント → SOP:または:お客様が期限前に自分でキャンセル
  6. SOP → SOP:決済手数料とプラットフォーム手数料を差し引いて返金
  7. SOP → エージェント:返金額と差し引いた手数料を返す

お客様による支払い済み予約のキャンセル:手数料を差し引いて返金

curl

curl -sS -X DELETE "https://mcp.srct.tw/api/v1/businesses/<BUSINESS_ID>/bookings/<BOOKING_ID>" \
  -H "User-Agent: my-agent/1.0" \
  -H "Authorization: Bearer <STATUS_TOKEN>"

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))


BUSINESS_ID = "<BUSINESS_ID>"
BOOKING_ID = "<BOOKING_ID>"      # from the create response
STATUS_TOKEN = "<STATUS_TOKEN>"  # from the create response (shown once)

# When the customer cancels a card-paid booking before the cutoff, it is refunded minus the
# payment processing fee and the platform fee. If the business declines it or does not confirm it, it is refunded in full.
show(*call("DELETE", f"/api/v1/businesses/{BUSINESS_ID}/bookings/{BOOKING_ID}", headers={"Authorization": f"Bearer {STATUS_TOKEN}"}))

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 BUSINESS_ID = "<BUSINESS_ID>";
const BOOKING_ID = "<BOOKING_ID>"; // from the create response
const STATUS_TOKEN = "<STATUS_TOKEN>"; // from the create response (shown once)

// When the customer cancels a card-paid booking before the cutoff, it is refunded minus the
// payment processing fee and the platform fee. If the business declines it or does not confirm it, it is refunded in full.
(async () => {
  show(await call("DELETE", `/api/v1/businesses/${BUSINESS_ID}/bookings/${BOOKING_ID}`, undefined, { Authorization: `Bearer ${STATUS_TOKEN}` }));
})();

レスポンス例

HTTP 200
{
  "ok": true,
  "booking": {
    "booking_id": "WQEMHR2E",
    "status": "cancelled",
    "status_text": "Cancelled",
    "business_id": "example-studio",
    "business_name": "Example Studio (test data)",
    "service_id": "small_group",
    "service_name": "Small-group strength class",
    "slot": "2026-10-25T10:00",
    "start": "2026-10-25T10:00:00+08:00",
    "label": "10/25(日)10:00",
    "quantity": 1,
    "quantity_unit": "seat",
    "payment": "Refunded NT$518 of NT$600 (the Stripe processing fee and the platform fee were deducted because the customer cancelled)",
    "details": [],
    "payment_details": {
      "mode": "online",
      "status": "refunded",
      "amount": {
        "value": 60000,
        "currency": "twd",
        "text": "NT$600"
      },
      "collected_by": "Seller Tech U.S.A. LLC through Stripe",
      "paid_at": "2026-10-22T11:51:00+08:00",
      "card": {
        "brand": "visa",
        "last4": "4242"
      },
      "method": "stripe_spt",
      "transaction_id": "pi_1",
      "refund": "refunded",
      "refund_amount": {
        "value": 51800,
        "currency": "twd",
        "text": "NT$518"
      },
      "refund_fees": {
        "stripe": "NT$39",
        "platform": "NT$43",
        "reason": "customer_cancel"
      }
    },
    "cancelled_by": "customer"
  }
}

レスポンスはサービスの自動テストから取得:コネクターのテスト環境から取得しました。コネクターのコードはそのまま動かし、Stripeはテストスイートの偽Stripeサーバーに置き換えています。transaction_id(pi_1)と決済手数料(USD1.20、NT$39換算)はテスト値です。実際の課金は一切ありません。

受付確認デスクで見える内容

事業者側の設定

オンライン決済はSeller Tech U.S.A. LLCがStripeを通じて処理します。手数料と入金時期は見積書に記載します。

現在の状態

予約インターフェースは提供中です。事業者は店頭払いとオンライン決済から選べます。2026年10月22日、最初の事業者(パーソナルトレーナー)がオンライン決済を有効にしました。

始める前に

再送しても重複しません

エージェントはネットワークエラーの後にリクエストを再送しても、予約や課金が重複しません。エージェントがIdempotency-Keyを送ると、SOPは48時間、元の結果を返します。カードの課金にも、Stripe向けの冪等キーが別に付いています。

公開する前に

掲載内容は一緒に整え、すべての項目をご確認いただいてから公開します。オンライン決済は、ご依頼をいただいてから有効にします。