SOPSellers' Open Protocol

Accept agent bookings

Let customers' AI agents check your time slots, book, change and cancel; you confirm on the Order Desk.

LiveBooking is live. Online payment is live too; the first merchant, a personal trainer, turned on online payment on 2026-10-22.

This page is written for merchants and for the people who build the integration for them.

How it works

Your services, prices and time slots go live only after you confirm each item. A customer asks their own AI to book, and the AI sends it through SOP's booking interface; you confirm or decline on the Order Desk and get a LINE notification.

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

What agents can do

OperationMCP toolREST
Check availabilityget_availabilityGET availability
Create a bookingrequest_bookingPOST bookings
Look up a bookingget_booking_statusGET bookings/{booking_id}
Change a bookingmodify_bookingPATCH bookings/{booking_id}
Cancelcancel_bookingDELETE bookings/{booking_id}
Paycomplete_bookingPOST bookings/{booking_id}/complete

Parameters and responses for each operation are in Connect to bookings; the examples below show what the merchant side encounters.

Check availability

An agent first checks your open slots. Every slot has a stable slot_id, used by the later calls that create or change a booking; the response also includes remaining places and the price.

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.

Create a booking

After the customer agrees to the service, time, quantity and contact details, the agent sends the booking. The result depends on your settings: a pay-at-venue booking is confirmed immediately (confirmed) or waits for you (pending); an online-payment booking starts as awaiting_payment, and you are notified only once payment completes.

Create a booking (pay at the venue)

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

Example response

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

Run against a local copy of the service: Run against a local copy of the connector that holds a copy of the live business's services, so the response has the same fields as production. status_token is replaced by <STATUS_TOKEN>. A first booking from new contact details waits for the business to confirm (status pending).

Change a booking

An agent can change a customer's slot or quantity, and the old place is released. A booking paid online can change its slot only; changing the quantity changes the amount, so the customer must cancel and book again. Changes and online cancellation are allowed up to the same cutoff.

Change a booking's time

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

Example response

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

Run against a local copy of the service: Local copy of the connector. The old slot is freed and the new slot is held; the response shows from and to.

Cancel

When a customer cancels through their AI, the place is released and your Order Desk keeps a record. You set the online cancellation cutoff; after it, the customer has to contact you directly. When a customer cancels a booking paid online, it is refunded minus the payment processing fee and the platform fee.

Cancel a booking

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

Example response

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

Run against a local copy of the service: Local copy of the connector. A booking paid at the venue has no refund fields; for online payment see refund_result.

Structured errors

Every error returns a code and a message, so the agent knows what happened and whether to retry. For example, if the slot the customer chose was just taken, the response is capacity_full (409 over REST) with a few alternative slots, and the agent can book another one.

Handle a structured error: slot already taken

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

Example response

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

Run against a local copy of the service: Local copy of the connector; before each run the slot was booked by another customer. next_slots lists open alternatives.

CodeMeaningREST status
capacity_fullNot enough places left in this slot409
invalid_slotThe slot doesn't exist or can't be booked409
duplicateThe same contact details were sent again for the same service and slot409
too_late_to_cancelPast the online cancellation cutoff409
payment_declinedThe payment was declined; retry with another payment method402
payment_expiredThe payment window has passed and the place was released410
rate_limitedToo many requests429

Paid but not booked: automatic refund

A booking paid online is refunded in full, immediately, in these cases: you decline or cancel, or you don't confirm within the time limit. When the customer cancels online before the cutoff, a card payment is refunded minus the payment processing fee and the platform fee. A refund that fails is retried every 5 minutes.

When a paid booking is cancelled or doesn't go through, SOP refunds it
AgentSOPSellercomplete_booking withthe credentialThe charge succeedsThe businessdeclines, or doesn'tconfirm in timeRefund in full,automaticallyOr: the customercancels before thecutoffRefund minus thepayment processingfee and the platformfeeReports the refundamount and the fees
Steps
  1. Agent → SOP: complete_booking with the credential
  2. SOP → SOP: The charge succeeds
  3. Seller → SOP: The business declines, or doesn't confirm in time
  4. SOP → SOP: Refund in full, automatically
  5. Agent → SOP: Or: the customer cancels before the cutoff
  6. SOP → SOP: Refund minus the payment processing fee and the platform fee
  7. SOP → Agent: Reports the refund amount and the fees

Customer cancels a paid booking: refund minus fees

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

Example response

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

Response taken from the service's automated tests: Taken from the connector's test setup: the connector code runs unchanged, and Stripe is replaced by the test suite's fake Stripe server, so transaction_id (pi_1) and the processing fee (USD 1.20, shown as NT$39) are test values. Nothing was charged anywhere.

What you see on the Order Desk

Merchant settings

Online payments are processed by Seller Tech U.S.A. LLC through Stripe; fees and settlement timing are stated in the quotation.

Status today

The booking interface is live. A merchant can take payment at the venue or online; the first merchant, a personal trainer, turned on online payment on 2026-10-22.

Before you begin

Retries are safe

Agents can retry a request after a network error without creating a second booking or a second charge: they send an Idempotency-Key, and SOP returns the original result for 48 hours. A card charge also carries its own idempotency key with Stripe.

Before you go live

We set up your listing with you and you check every item before it goes live. Online payment is turned on only after you ask for it.