Partner API v1 · REST and webhooks
Integrate your AI today.
Connect the reservation, point-of-sale and events systems your venues already run, or post JSON from your own code. Your nights, tables and experiences reach people on SplitClub as group plans they split, and every experience reports back.
- Base URL
- https://splitclub.com/api/v1
- Auth
- Bearer key
- Version
- v1
curl https://splitclub.com/api/v1/me \
-H "Authorization: Bearer sc_live_…"const res = await fetch("https://splitclub.com/api/v1/me", {
headers: { Authorization: "Bearer sc_live_…" },
});
const { data } = await res.json();
console.log(data.venue.name, data.limits, data.categories);import requests
res = requests.get(
"https://splitclub.com/api/v1/me",
headers={"Authorization": "Bearer sc_live_…"},
)
data = res.json()["data"]
print(data["venue"]["name"], data["limits"], data["categories"]){
"data": {
"venue": { "id": "9b2dc857-…", "name": "Your venue" },
"limits": { "writes_per_hour": 60, "reads_per_hour": 600 },
"categories": ["adventure", "holidays", "nightlife", "social", "wellness"]
}
}One key per venue. Calls run server to server, never from a browser.
Pay with SplitClub
Try it: one booking, the whole party pays.
This is a venue's booking page with the official button. Press it and the real SplitClub modal opens: sign in, pick your party, hold your share with a test card, and watch everyone else's fill in. Your server code and the webhooks you receive are on the right.
Palm Hollow Golf Resort
Tee time for four
Saturday, 9:20 AM
- 18 holes
- Cart included
- 4 players
Total for 4
$480
$120 each
if split equally
Pay your share. Your party pays theirs. Nobody is charged until everyone's in.
The party, live
Press Pay with SplitClub. Sign in (any number, the code fills in), pick your party, hold your share with a test card, and watch the others fill in.
curl https://splitclub.com/api/v1/checkouts \
-H "Authorization: Bearer sc_test_…" \
-H "Idempotency-Key: teesheet_20261017_0920_4" \
-H "Content-Type: application/json" \
-d '{
"amount": 48000,
"currency": "usd",
"party_size": 4,
"pay_by": "2026-10-16T09:00:00-04:00",
"description": "Tee time for four · 18 holes, cart included · Sat 9:20 AM",
"external_booking_id": "teesheet_20261017_0920_4",
"return_url": "https://palmhollow.example/booked",
"cancel_url": "https://palmhollow.example/book",
"metadata": { "tee_sheet": "foreup", "slot": "0920" }
}'// Your server, when a group books. The amount comes from you, never from the page.
const res = await fetch("https://splitclub.com/api/v1/checkouts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SPLITCLUB_SECRET_KEY}`, // sc_test_… / sc_live_…
"Idempotency-Key": booking.id,
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: 48000, // minor units: $480.00
currency: "usd",
party_size: 4,
pay_by: "2026-10-16T09:00:00-04:00",
description: "Tee time for four · 18 holes, cart included · Sat 9:20 AM",
external_booking_id: booking.id,
return_url: "https://palmhollow.example/booked",
cancel_url: "https://palmhollow.example/book",
}),
});
const checkout = await res.json(); // 201 { id: "chk_…", url, status: "open", … }import os, requests
res = requests.post(
"https://splitclub.com/api/v1/checkouts",
headers={
"Authorization": f"Bearer {os.environ['SPLITCLUB_SECRET_KEY']}", # sc_test_… / sc_live_…
"Idempotency-Key": booking.id,
},
json={
"amount": 48000, # minor units: $480.00
"currency": "usd",
"party_size": 4,
"pay_by": "2026-10-16T09:00:00-04:00",
"description": "Tee time for four · 18 holes, cart included · Sat 9:20 AM",
"external_booking_id": booking.id,
"return_url": "https://palmhollow.example/booked",
"cancel_url": "https://palmhollow.example/book",
},
)
checkout = res.json() # 201 {"id": "chk_…", "url": …, "status": "open", …}<script src="https://splitclub.com/js/v1/splitclub.js"></script>
<script>SplitClub.button("#pay", { checkout: "{{ checkout.id }}", publishableKey: "pk_test_…", onComplete: () => location.assign("/booked") })</script>Run the demo. Every event your endpoint would receive appears here, signed: share.held, checkout.funded, checkout.completed.
Integrations
Connect the platforms your venues already use, and they deliver to SplitClub on their own. No new software for your staff to learn.
Reservations and events
Your bookable nights, private events and group dining.
- SevenRooms
- OpenTable
- Resy
- Tock
- Tripleseat
Point of sale
Orders and spend from the floor, for each venue.
- Toast
- Square
- Clover
- Lightspeed
- SpotOn
- TouchBistro
- Oracle Simphony
Your own system
An in-house booking engine, events CMS or data platform posts JSON directly to the API.
- REST API
- Webhooks
Product names are trademarks of their owners. Some platforms switch on outbound webhooks through their partner team; we set that up with you.
Built for groups with many venues
Set up with your team
We link every venue in your group, issue each one its own keys and connections, and turn on the platform webhooks with your engineers.
Safe to retry
Posting the same
external_idagain changes nothing, and a redelivered webhook is stored once. Square deliveries are signature-checked.Every venue reports
Views, booking starts, saves, bookings, guests, booked value and show rate, by experience and by day. Figures are totals and never identify a person.
Room to grow
Limits are per key, and we raise them for higher volume. Up to ten platform connections per venue.
Your keys, your control
Create and revoke keys yourself in the Partner console. Calls run server to server, never from a browser.
A stable contract
This is version 1. We only add fields; anything that would break an integration ships as a new version.
Pay with SplitClub
Your guest books with you, and SplitClub collects each person's share from their party. One call from your server, one line on your page. SplitClub runs the sign-in, the party, the split, the holds, capture, refunds and your payout. You never handle a guest's card and never compute a share.
- 1Your server creates a checkout
POST /v1/checkoutswith your secret key: the total, the party size, the date it must be paid by and where to send the guest back. The amount only ever comes from your server. - 2Your page opens it
SplitClub.button()orSplitClub.open()with the checkout id and your publishable key. SplitClub opens in a modal on your site, full screen on phones. - 3Your guest picks their partyThey sign in with a phone code, then pick friends, a channel or anyone by phone, and split it equally or by amount.
- 4Every share is heldEach person pays their own share on their own card, as a hold. Nothing is charged until the whole party is in.
- 5Fully paid, you are paidWhen every share is held SplitClub captures them, your payout goes through Stripe Connect, you receive
checkout.completedand the guest returns to yourreturn_url. Not paid bypay_by? Every hold is released and you receivecheckout.expired.
Test keys (sc_test_…, pk_test_…) run everything end to end with Stripe test cards, such as 4242 4242 4242 4242. Live keys (sc_live_…, pk_live_…) are issued once your venue is verified and its Stripe payouts are connected. Get your API key: the sandbox is ready in a minute.
Create a checkout, open it
Create the checkout from your server with a secret key and the checkouts:write scope. Send an Idempotency-Key header (or rely on external_booking_id), so a retry returns the same checkout. return_url and cancel_url must be on an origin registered for your venue.
| Field | Type | Notes |
|---|---|---|
amount | integer | Minor units: 48000 is $480.00. |
currency | string | ISO code, e.g. usd. |
party_size | integer | 2 to 50 people, the organizer included. |
pay_by | ISO 8601 | When the party must be fully held: 10 minutes to 6 days ahead (card holds last 7 days). After it, every hold is released. |
description | string | What the guest sees: the booking, in your words. |
return_url | https URL | Where the guest lands once the party is funded. |
cancel_url | https URL | Optional. Where the guest goes if they back out. |
external_booking_id | string | Your booking's id. Acts as the idempotency key. |
offering_external_id | string | Optional. The offering this booking is for. |
metadata | object | Optional. Up to 20 string keys, returned on every event. |
On your page, load https://splitclub.com/js/v1/splitclub.js and render the button, or call SplitClub.open({ checkout, publishableKey, onComplete, onCancel }) from your own. The publishable key can only open checkouts your server already created. The modal talks to your page by postMessage, checked against both origins, and closes with Escape.
| Status | Meaning |
|---|---|
open | Created. Waiting for the organizer. |
collecting | The party is chosen; shares are being held. |
funded | Every share is held. |
completed | Captured. Your payout is scheduled. |
expired | Not funded by pay_by. Everything released. |
canceled | You canceled it. Everything released. |
Read it back with GET /v1/checkouts/:id or list them with GET /v1/checkouts?status=&limit=&before=. Each share comes back as a display name, an amount and a status (invited, held, captured, released, refunded, failed); never a phone number or an email.
curl https://splitclub.com/api/v1/checkouts \
-H "Authorization: Bearer sc_test_…" \
-H "Idempotency-Key: teesheet_20261017_0920_4" \
-H "Content-Type: application/json" \
-d '{
"amount": 48000,
"currency": "usd",
"party_size": 4,
"pay_by": "2026-10-16T09:00:00-04:00",
"description": "Tee time for four · 18 holes, cart included · Sat 9:20 AM",
"external_booking_id": "teesheet_20261017_0920_4",
"return_url": "https://palmhollow.example/booked",
"cancel_url": "https://palmhollow.example/book",
"metadata": { "tee_sheet": "foreup", "slot": "0920" }
}'// Your server, when a group books. The amount comes from you, never from the page.
const res = await fetch("https://splitclub.com/api/v1/checkouts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SPLITCLUB_SECRET_KEY}`, // sc_test_… / sc_live_…
"Idempotency-Key": booking.id,
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: 48000, // minor units: $480.00
currency: "usd",
party_size: 4,
pay_by: "2026-10-16T09:00:00-04:00",
description: "Tee time for four · 18 holes, cart included · Sat 9:20 AM",
external_booking_id: booking.id,
return_url: "https://palmhollow.example/booked",
cancel_url: "https://palmhollow.example/book",
}),
});
const checkout = await res.json(); // 201 { id: "chk_…", url, status: "open", … }import os, requests
res = requests.post(
"https://splitclub.com/api/v1/checkouts",
headers={
"Authorization": f"Bearer {os.environ['SPLITCLUB_SECRET_KEY']}", # sc_test_… / sc_live_…
"Idempotency-Key": booking.id,
},
json={
"amount": 48000, # minor units: $480.00
"currency": "usd",
"party_size": 4,
"pay_by": "2026-10-16T09:00:00-04:00",
"description": "Tee time for four · 18 holes, cart included · Sat 9:20 AM",
"external_booking_id": booking.id,
"return_url": "https://palmhollow.example/booked",
"cancel_url": "https://palmhollow.example/book",
},
)
checkout = res.json() # 201 {"id": "chk_…", "url": …, "status": "open", …}{
"id": "chk_7Hq2LmX9pR4sTw",
"object": "checkout",
"mode": "test",
"status": "open",
"amount": 48000,
"currency": "usd",
"party_size": 4,
"pay_by": "2026-10-16T09:00:00-04:00",
"description": "Tee time for four · 18 holes, cart included · Sat 9:20 AM",
"venue": { "id": "ven_…", "name": "Palm Hollow Golf Resort" },
"external_booking_id": "teesheet_20261017_0920_4",
"url": "https://checkout.splitclubapp.com/c/chk_7Hq2LmX9pR4sTw",
"shares": [],
"funded_amount": 0,
"created_at": "2026-10-13T18:04:11-04:00",
"metadata": { "tee_sheet": "foreup", "slot": "0920" }
}<script src="https://splitclub.com/js/v1/splitclub.js"></script>
<script>SplitClub.button("#pay", { checkout: "{{ checkout.id }}", publishableKey: "pk_test_…", onComplete: () => location.assign("/booked") })</script>Checkout events and signatures
Register an endpoint with POST /v1/webhook_endpoints; the signing secret (whsec_…) is returned once. Every delivery carries SplitClub-Signature: t=<unix>,v1=<hex>, an HMAC-SHA256 of t + "." + body with that secret. Check it against the raw body and reject old timestamps. Failed deliveries retry with exponential backoff for 72 hours.
| Event | When |
|---|---|
checkout.created | You created a checkout. |
checkout.collecting | The organizer chose the party; the shares exist. |
share.held | One person's share is held on their card. |
checkout.funded | Every share is held. |
checkout.completed | The holds are captured and your payout is scheduled. Confirm the booking on this one. |
checkout.expired | pay_by passed before the party was in. Every hold is released. |
checkout.canceled | You canceled it (POST /v1/checkouts/:id/cancel). Every hold is released. |
Every event carries the checkout as data.object and, when the status changed, data.previous_status. If one person's hold lapses before capture, the checkout goes back from funded to collecting and that person holds their share again; confirm the booking on checkout.completed, not checkout.funded.
The demo above shows each event as your endpoint would receive it. Try it
Get your API keycurl https://splitclub.com/api/v1/webhook_endpoints \
-H "Authorization: Bearer sc_test_…" \
-H "Content-Type: application/json" \
-d '{ "url": "https://palmhollow.example/webhooks/splitclub",
"events": ["share.held", "checkout.completed", "checkout.expired"] }'
# 201: the signing secret is shown once
{ "id": "we_…", "url": "…", "events": […], "secret": "whsec_…" }{
"id": "evt_9mQ4rT2wXk7LpZ",
"object": "event",
"type": "checkout.completed",
"mode": "test",
"created_at": "2026-10-14T19:42:07-04:00",
"data": {
"object": {
"id": "chk_7Hq2LmX9pR4sTw",
"object": "checkout",
"status": "completed",
"amount": 48000,
"currency": "usd",
"external_booking_id": "teesheet_20261017_0920_4",
"shares": [
{ "id": "shr_…", "amount": 12000, "status": "captured", "is_organizer": true, "display_name": "Alex R." },
…
],
"funded_amount": 48000,
"completed_at": "2026-10-14T19:42:06-04:00",
…
},
"previous_status": "funded"
}
}import crypto from "node:crypto";
// header: the SplitClub-Signature value. rawBody: the request body exactly as received.
export function verify(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => {
const i = p.indexOf("=");
return [p.slice(0, i), p.slice(i + 1)];
}));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(parts.v1 ?? "", "hex");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}import hashlib, hmac, time
def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t = int(parts.get("t", "0"))
if not t or abs(time.time() - t) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))For AI agents
Ask your assistant to connect your venue. It finds SplitClub on its own, reads one typed vocabulary for what you sell, and every error it meets names the field, what was expected and a value that works, so it fixes the request and retries without guessing. SplitClub is the payment system: the assistant describes your offerings, and SplitClub runs the group checkout, each person's hold, capture and your payout.
- llms.txtThe short index an assistant finds first.
- llms-full.txtThe whole integration guide as plain text.
- JSON SchemasThe typed objects every request is checked against.
- openapi.jsonOpenAPI 3.1, generated from the schemas.
- MCP serverTyped tools for Claude, ChatGPT, Cursor and any MCP client.
The MCP server speaks Streamable HTTP at https://splitclubapp.com/api/mcp and takes your key as a bearer token. Build with your sc_test_ key: it can use every tool and never touches live money. Each tool calls the same API route your server would, so validation, scopes, limits and idempotency are identical.
| Tool | What it does |
|---|---|
describe_venue | The key's venue, mode and scopes, and the order of work. Start here. |
validate | Dry-runs any object against a schema and returns every error with its JSON pointer and a fix. |
upsert_offerings | Creates or updates up to 25 offerings, each with tiers, availability and policy. |
set_price_tiers | Replaces one offering's price tiers. |
set_availability | Replaces one offering's slots, recurring rules and blackout dates. |
create_checkout | Starts Pay with SplitClub for a booking and returns the checkout's link. |
get_checkout | One checkout: its status and every share's. |
list_bookings | The venue's checkouts, newest first, by status. |
claude mcp add --transport http splitclub https://splitclubapp.com/api/mcp \
--header "Authorization: Bearer sc_test_…"{
"mcpServers": {
"splitclub": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://splitclubapp.com/api/mcp",
"--header", "Authorization:${SPLITCLUB_AUTH}"],
"env": { "SPLITCLUB_AUTH": "Bearer sc_test_…" }
}
}
}{
"mcpServers": {
"splitclub": {
"url": "https://splitclubapp.com/api/mcp",
"headers": { "Authorization": "Bearer sc_test_…" }
}
}
}{
"type": "mcp",
"server_label": "splitclub",
"server_url": "https://splitclubapp.com/api/mcp",
"headers": { "Authorization": "Bearer sc_test_…" },
"require_approval": "always"
}Connect <your venue> to SplitClub so groups can book and split the payment. Use my SplitClub test key and the SplitClub MCP server (https://splitclubapp.com/api/mcp), and read https://splitclub.com/developers/llms-full.txt first. We run <your booking system>: read its mapping at https://splitclub.com/schemas/v1/mappings/. Describe the venue, then write one offering per thing we sell, with our prices in cents, our party sizes, when it can be booked and our cancellation policy. Validate each one until it's valid, upsert them, then create a test checkout for a sample group booking and give me its link.
Hook up a golf resort
“Can we hook up the golf resort to SplitClub for our payments?” Here is what the assistant does for Coral Ridge Golf Resort, whose tee sheet runs on foreUP, with the real JSON.
- 1
Confirm the key
It reads llms.txt and llms-full.txt, then calls
describe_venue: the venue, test mode, and the order of work. - 2
Read the tee sheet's mapping
It asks which system the resort runs and reads mappings/foreup.json: the course becomes an offering of kind
tee_time, rates become price tiers (decimal dollars to integer cents), tee times become slots. Anything the mapping marks unverified, it confirms with the resort. - 3
Write the offering
Three tiers: weekday $145.00, weekend $185.00 and twilight $85.00 a player, 7% tax added; tee times every ten minutes from 7:00; closed Thanksgiving and Christmas; a full refund 48 hours out and half at 24.
- 4
Validate until it's valid
validatereturns every problem at once. A tier sent as185.5,"USD"andper_playercomes back as three errors, each with its field and a fix: an integer in cents,"usd", and one of the four units. Nothing is sent until it's clean. - 5
Send it, in test mode
upsert_offerings, orPOST /v1/offerings. SplitClub reviews it before anyone sees it. - 6
Put Pay with SplitClub on the booking page
A foursome books Saturday at 7:30: 4 × $185.00 plus tax is $791.80. The resort's server creates the checkout; the page opens it. The organizer picks the other three and each pays $197.95 as a hold.
curl https://splitclub.com/api/v1/checkouts \ -H "Authorization: Bearer sc_test_…" \ -H "Idempotency-Key: foreup-res-88412" \ -H "Content-Type: application/json" \ -d '{ "amount": 79180, "currency": "usd", "party_size": 4, "pay_by": "2026-11-06T19:30:00-05:00", "description": "Tee time for four, Sat 7 Nov, 07:30, Championship 18", "return_url": "https://coralridge.example/booking/confirmed", "cancel_url": "https://coralridge.example/tee-times", "external_booking_id": "foreup-res-88412", "offering_external_id": "coral-ridge-champ-18", "metadata": { "tee_time": "2026-11-07T07:30:00-05:00", "tier": "weekend" } }'{ "name": "create_checkout", "arguments": { "amount": 79180, "currency": "usd", "party_size": 4, "pay_by": "2026-11-06T19:30:00-05:00", "description": "Tee time for four, Sat 7 Nov, 07:30, Championship 18", "return_url": "https://coralridge.example/booking/confirmed", "cancel_url": "https://coralridge.example/tee-times", "external_booking_id": "foreup-res-88412", "offering_external_id": "coral-ridge-champ-18", "metadata": { "tee_time": "2026-11-07T07:30:00-05:00", "tier": "weekend" } } }The booking page <script src="https://splitclub.com/js/v1/splitclub.js"></script> <button id="pay">Pay with SplitClub</button> <script> document.getElementById("pay").onclick = () => SplitClub.open({ checkout: "chk_7Hc2Qm9xLp", // from your server publishableKey: "pk_test_…", onComplete: () => location.assign("/booking/confirmed"), }); </script> - 7
Run one test checkout end to end
Pay all four shares with
4242 4242 4242 4242. When the last hold lands, the resort receivescheckout.completed, signed withSplitClub-Signature, and marks the booking paid. If the party isn't paid bypay_by,checkout.expiredarrives instead and every hold is released.import crypto from "node:crypto"; // header: the SplitClub-Signature request header; rawBody: the body, unparsed export function verifySplitClub(rawBody, header, secret) { const p = Object.fromEntries(String(header).split(",").map((kv) => kv.split("=", 2))); const t = Number(p.t); if (!t || Math.abs(Date.now() / 1000 - t) > 300 || !p.v1) return false; const want = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"); return want.length === p.v1.length && crypto.timingSafeEqual(Buffer.from(want), Buffer.from(p.v1)); }import hashlib, hmac, time def verify_splitclub(raw_body: bytes, header: str, secret: str) -> bool: p = dict(kv.split("=", 1) for kv in header.split(",")) t = int(p.get("t", "0")) if not t or abs(time.time() - t) > 300 or "v1" not in p: return False want = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(want, p["v1"]) - 8
Go live
SplitClub issues live keys once the resort is verified and its payouts are set up. The keys change; nothing else does.
{
"external_id": "coral-ridge-champ-18",
"kind": "tee_time",
"title": "18 holes, Championship Course",
"description": "Par 72, 7,100 yards. Cart and range balls included.",
"media": [
{
"url": "https://coralridge.example/img/18th-green.jpg",
"alt": "The 18th green at dusk"
}
],
"booking_url": "https://coralridge.example/tee-times",
"tiers": [
{
"external_id": "weekday",
"label": "Weekday",
"amount": 14500,
"currency": "usd",
"unit": "per_person",
"min_party": 2,
"max_party": 4,
"days": [
"mon",
"tue",
"wed",
"thu",
"fri"
],
"fees": [
{
"type": "tax",
"label": "Sales tax",
"percent": 7,
"included": false
}
]
},
{
"external_id": "weekend",
"label": "Weekend",
"amount": 18500,
"currency": "usd",
"unit": "per_person",
"min_party": 2,
"max_party": 4,
"days": [
"sat",
"sun"
],
"fees": [
{
"type": "tax",
"label": "Sales tax",
"percent": 7,
"included": false
}
]
},
{
"external_id": "twilight",
"label": "Twilight",
"amount": 8500,
"currency": "usd",
"unit": "per_person",
"min_party": 1,
"max_party": 4,
"time_window": {
"from": "15:00",
"to": "19:00"
},
"fees": [
{
"type": "tax",
"label": "Sales tax",
"percent": 7,
"included": false
}
]
}
],
"availability": {
"timezone": "America/New_York",
"recurring": [
{
"rrule": "FREQ=DAILY;BYHOUR=7,8,9,10,11,12,13,14,15,16;BYMINUTE=0,10,20,30,40,50",
"starts_at": "2026-11-01T07:00:00-05:00",
"until": "2027-03-31",
"duration_minutes": 270,
"capacity": 4
}
],
"blackout_dates": [
"2026-11-26",
"2026-12-25"
],
"lead_time_minutes": 60,
"cutoff_minutes": 720
},
"policy": {
"cancellation": [
{
"hours_before": 48,
"refund_pct": 100
},
{
"hours_before": 24,
"refund_pct": 50
}
],
"dress_code": "Collared shirts, soft spikes only.",
"arrival_window_minutes": 30
}
}[
{
"code": "invalid_type",
"message": "`/amount` must be an integer in minor units (e.g. 14500 for $145.00); you sent 185.5.",
"field": "/amount",
"expected": "an integer in minor units (e.g. 14500 for $145.00)",
"example_fix": "\"amount\": 14500",
"doc_url": "https://splitclubapp.com/developers#schema-price-tier"
},
{
"code": "invalid_format",
"message": "`/currency` must be a three-letter ISO 4217 code in lower case (e.g. \"usd\"); you sent \"USD\".",
"field": "/currency",
"expected": "a three-letter ISO 4217 code in lower case (e.g. \"usd\")",
"example_fix": "\"currency\": \"usd\"",
"doc_url": "https://splitclubapp.com/developers#schema-price-tier"
}
]Schemas
One JSON Schema per object, at /schemas/v1/<name>.json. The API, the MCP tools and openapi.json all come from them. Money is an integer in minor units with a lowercase currency; times are ISO 8601 with an offset; unknown fields are refused.
- offering.jsonSomething sold, repeatedly, with its tiers, availability and policy.
- price_tier.jsonAn amount per person, group, hour or unit; days, time window, fees, deposit, minimum spend.
- availability.jsonSlots, RRULE recurrences, blackout dates, lead time and cut-off.
- policy.jsonCancellation steps, minimum age, dress code, arrival window.
- event.jsonA dated night, tournament or show, with GA/VIP tiers and an on-sale window.
- checkout_create.jsonThe body of POST /v1/checkouts.
- checkout.jsonA Pay with SplitClub session and its shares.
- webhook_event.jsonWhat SplitClub signs and sends to you.
- boat.jsonOne rental boat: specs, captain terms, marina, prices by duration, photos, amenities.
- error.jsonEvery failure: field, expected, example fix, doc link.
Provider mappings
If a venue already runs a tee sheet, a spa system, a ticketing or reservation platform or a POS, its mapping file says which of their fields fills which of ours, how money and times convert, and how their webhooks are signed. Every field cites the provider's public developer docs; anything those docs don't confirm is marked "unverified".
| Provider | For | Fields confirmed | Webhooks |
|---|---|---|---|
| Club Prophet | Golf | Docs not public | webhooks not documented |
| foreUP | Golf | 32 of 35 | webhooks, not signed |
| GolfNow | Golf | 20 of 31 | webhooks not documented |
| Lightspeed Golf | Golf | 26 of 30 | no webhooks |
| Boulevard | Spas and studios | 29 of 31 | signed webhooks (x-blvd-oauth-hmac-sha256) |
| Mindbody | Spas and studios | 48 of 52 | signed webhooks (X-Mindbody-Signature) |
| Zenoti | Spas and studios | 38 of 42 | webhooks; signature scheme not documented |
| DICE | Tickets and events | 16 of 30 | no webhooks |
| Eventbrite | Tickets and events | 25 of 34 | webhooks, not signed |
| FareHarbor | Tours and rentals | 37 of 38 | webhooks, not signed |
| Peek Pro | Tours and rentals | 34 of 35 | webhooks, not signed |
| OpenTable | Restaurants | 31 of 35 | webhooks, not signed |
| Resy | Restaurants | Docs not public | webhooks not documented |
| SevenRooms | Restaurants | Docs not public | webhooks not documented |
| Tock | Restaurants | 21 of 26 | webhooks, not signed |
| Square | Point of sale | 36 of 37 | signed webhooks (x-square-hmacsha256-signature) |
| Toast | Point of sale | 30 of 30 | signed webhooks (Toast-Signature) |
On request
With approvalThese aren't public yet. We build and switch them on for partners who need them, after a review of the use case. Tell us which ones matter to you once your sandbox is running: partners@splitclubapp.com.
- Manage experiencesUpdate, reschedule, cancel and post in bulk, and change capacity as tables sell, instead of only creating.
- Group and multi-venue keysOne key across every venue in your group, with per-venue scopes, plus team roles and single sign-on in the Partner console.
- Tables and packagesPublish table menus, minimums, bottle and cabana packages for each venue and night, priced per person for the split.
- Event webhooks to youGet notified when a group forms, fills, books, cancels or checks in, signed so you can verify every delivery.
- Bookings and check-inRead group bookings and check guests in at the door. Member details are shared only with their consent, and only for your venue.
- Payouts and reconciliationSettlements, payouts and refunds by venue and day, plus exports that match your accounting.
- DistributionApproved SplitClub experiences for hotel concierge, travel and ticketing partners, with deep links and a "Split with SplitClub" button for your own site.
- Data exportsScheduled analytics exports to your warehouse or storage bucket, with campaign attribution.
- Sandbox and test keysA test environment with sc_test_ keys, so your engineers can build before going live.
- Enterprise controlsIP allowlists, audit logs, higher rate limits, a data processing agreement, and data deletion on request.
API reference
- GET/meYour venue, your limits, and the categories you can post under.
- POST/experiencesSubmit an experience for review.
- GET/experiencesYour submissions, newest first.
- GET/experiences/{id}One submission and its review status.
- POST/offeringsCreate or update what you sell every day (tee times, bays, classes), or up to 25 at once. Each is reviewed.
- GET/offeringsYour offerings, most recently changed first, by review status.
- GET/offerings/{id}One offering and its review status.
- POST/boatsCreate or update one boat, or sync up to 100 at once. Each is reviewed.
- GET/boatsYour fleet, most recently changed first, by review status.
- GET/boats/{id}One boat and its review status.
- POST/boats/{id}/deactivateTake a boat out of everything at once.
- GET/analyticsPerformance for every experience at your venue, by experience and by day.
- POST/integrationsConnect a reservation or POS platform; returns its webhook URL.
- GET/integrationsYour connections.
- POST/hooks/{provider}/{token}Where a connected platform delivers. No key; the URL is the secret.
Get your API key
Sign up with your work email, confirm the code we send, and your sandbox is ready: a test key (sc_test_…) and your first request, filled in. Test keys run everything end to end with test cards and never touch real money. Live keys come from the same place once your venue is verified and its Stripe payouts are connected. Manage, rotate and revoke keys in the Partner console. A key is shown once, so store it as a secret on your servers.
Authentication
Send the key as a bearer token on every request. Calls go from your servers, never from a browser or an app your customers download.
curl https://splitclub.com/api/v1/me \
-H "Authorization: Bearer sc_live_…"{
"data": {
"venue": { "id": "9b2dc857-…", "name": "Your venue" },
"limits": { "writes_per_hour": 60, "reads_per_hour": 600 },
"categories": ["adventure", "holidays", "nightlife", "social", "wellness"]
}
}Rate limits
Limits are per key, over a rolling hour.
Every response says where you stand: X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (Unix seconds). Over the limit, you get 429 with a Retry-After header in seconds. If your volume needs more, tell your SplitClub contact.
Post an experience
A post is a submission. SplitClub curates what each city shows, so every experience is reviewed before it reaches anyone; approved ones go live at your venue, in your city. The response comes back at once with an id and "status": "pending".
| Field | Type | Notes |
|---|---|---|
titlerequired | string | Up to 120 characters. |
starts_atrequired | ISO 8601 | In the future, at most two years ahead. Include the offset. |
external_id | string | Your own ID, up to 128 characters. Posting the same one again returns the original with "duplicate": true and changes nothing, so retries are safe. |
description | string | Up to 4,000 characters. |
category | string | One of the categories from /me. |
ends_at | ISO 8601 | After starts_at. |
price_per_person | number | US dollars, 0 to 100,000. |
capacity | integer | People, 1 to 5,000. |
photo_urls | array | Up to 10 https image URLs. |
booking_url | string | An https link to the experience on your site. |
A new submission answers 201; a repeated external_id answers 200. Either way the body is the submission.
The same endpoint takes a typed body too: tiers with amounts in cents, media, and a policy with refund tiers, a dress code and a minimum age. The golf and Pilates examples use it: a twilight scramble with dinner, and a sunrise reformer class that ends on a boat at the sandbar. Send one shape or the other in a request, not both.
curl https://splitclub.com/api/v1/experiences \
-H "Authorization: Bearer sc_live_…" \
-H "Content-Type: application/json" \
-d '{
"external_id": "evt_20261108_supper",
"title": "Saturday supper club",
"description": "Five courses, then the room turns into a party at eleven.",
"category": "nightlife",
"starts_at": "2026-11-08T20:00:00-05:00",
"ends_at": "2026-11-09T01:00:00-05:00",
"price_per_person": 145,
"capacity": 40,
"photo_urls": ["https://yourcdn.example/supper-club.jpg"],
"booking_url": "https://yoursite.example/supper-club"
}'const res = await fetch("https://splitclub.com/api/v1/experiences", {
method: "POST",
headers: {
Authorization: "Bearer sc_live_…",
"Content-Type": "application/json",
},
body: JSON.stringify({
external_id: "evt_20261108_supper",
title: "Saturday supper club",
description: "Five courses, then the room turns into a party at eleven.",
category: "nightlife",
starts_at: "2026-11-08T20:00:00-05:00",
ends_at: "2026-11-09T01:00:00-05:00",
price_per_person: 145,
capacity: 40,
photo_urls: ["https://yourcdn.example/supper-club.jpg"],
booking_url: "https://yoursite.example/supper-club",
}),
});
const { data, duplicate } = await res.json(); // 201 new, 200 duplicateimport requests
res = requests.post(
"https://splitclub.com/api/v1/experiences",
headers={"Authorization": "Bearer sc_live_…"},
json={
"external_id": "evt_20261108_supper",
"title": "Saturday supper club",
"description": "Five courses, then the room turns into a party at eleven.",
"category": "nightlife",
"starts_at": "2026-11-08T20:00:00-05:00",
"ends_at": "2026-11-09T01:00:00-05:00",
"price_per_person": 145,
"capacity": 40,
"photo_urls": ["https://yourcdn.example/supper-club.jpg"],
"booking_url": "https://yoursite.example/supper-club",
},
)
body = res.json() # 201 new, 200 duplicate{
"data": {
"id": "a2daa945-58ea-490f-99e0-d79519c3f01a",
"external_id": "evt_20261108_supper",
"status": "pending",
"title": "Saturday supper club",
"starts_at": "2026-11-09T01:00:00+00:00",
"price_per_person": 145,
"capacity": 40,
"experience_id": null,
"review_note": null,
"created_at": "2026-10-12T21:58:23Z",
"reviewed_at": null
},
"duplicate": false
}curl https://splitclub.com/api/v1/experiences \
-H "Authorization: Bearer sc_live_…" \
-H "Content-Type: application/json" \
-d '{
"external_id": "scramble-20261114",
"title": "Twilight scramble and dinner",
"description": "Nine holes in foursomes at 3:30, then dinner on the terrace. Bring your group or join one.",
"category": "social",
"starts_at": "2026-11-14T15:30:00-05:00",
"ends_at": "2026-11-14T20:00:00-05:00",
"capacity": 48,
"tiers": [{"label": "Golf and dinner", "amount": 16500, "currency": "usd", "unit": "per_person"}],
"media": [
{
"url": "https://yourcdn.example/golf/twilight-scramble.jpg",
"alt": "Foursomes on the 9th green at sunset"
}
],
"policy": {
"cancellation": [{"hours_before": 72, "refund_pct": 100}],
"dress_code": "Collared shirts, soft spikes only."
},
"booking_url": "https://yourclub.example/events/twilight-scramble"
}'curl https://splitclub.com/api/v1/experiences \
-H "Authorization: Bearer sc_live_…" \
-H "Content-Type: application/json" \
-d '{
"external_id": "reformer-sandbar-20261115",
"title": "Sunrise reformer, then the sandbar",
"description": "A 50-minute reformer class at 8, then a boat to the sandbar until 1. Towels and water on board.",
"category": "wellness",
"starts_at": "2026-11-15T08:00:00-05:00",
"ends_at": "2026-11-15T13:00:00-05:00",
"capacity": 12,
"tiers": [{"label": "Class and boat", "amount": 9500, "currency": "usd", "unit": "per_person"}],
"media": [
{
"url": "https://yourcdn.example/pilates/sunrise-reformer.jpg",
"alt": "Reformers by the water at sunrise"
}
],
"policy": {
"cancellation": [{"hours_before": 48, "refund_pct": 100}, {"hours_before": 24, "refund_pct": 50}],
"min_age": 18
},
"booking_url": "https://yourstudio.example/events/sunrise-sandbar"
}'Read them back
List your submissions, or fetch one to follow its review. status moves from pending to approved, with the live experience_id, or to rejected, with a review_note saying why.
Lists return up to 100 at a time. When there may be more, the response includes next_before; pass it back as ?before= for the next page.
curl "https://splitclub.com/api/v1/experiences?status=approved&limit=50" \
-H "Authorization: Bearer sc_live_…"
# One submission
curl https://splitclub.com/api/v1/experiences/a2daa945-58ea-490f-99e0-d79519c3f01a \
-H "Authorization: Bearer sc_live_…"Offerings: golf, Pilates and anything bookable
An experience is one dated night. An offering is what you sell every day: a golf resort's tee sheet, a bay at a golf entertainment venue like Topgolf, Drive Shack or Puttshack, a studio's class schedule, a cabana or a spa treatment. Send it once with its prices, when it can be booked and its cancellation policy, and groups on SplitClub can book a time and split it.
Each offering is upserted on your external_id: a new one answers 201; sending it again answers 200 with "duplicate" or "updated" set. Send { "offerings": [...] } to sync up to 25 per request. Every offering is reviewed before it reaches anyone, like an experience.
| Field | Type | Notes |
|---|---|---|
external_idrequired | string | Your own ID, up to 128 characters, no spaces. |
kindrequired | string | tee_time, class, rental, table, cabana, daybed, spa_treatment, boat_day, ticketed_event, package, ride or dining. |
titlerequired | string | What it is, as a guest would say it. |
tiersrequired | array | 1 to 20 prices. Each has an amount in cents, a currency and a unit: per_person, per_group, per_hour or per_unit. A tier can hold to certain days or a time_window (a twilight rate from 15:00), a party size, fees, a deposit or a minimum spend. |
availability | object | A timezone, then recurring rules (an RFC 5545 rrule, the first start, minutes and capacity), exact slots, blackout_dates, a lead_time_minutes and a cutoff_minutes by which the group must have paid. |
policy | object | cancellation refund tiers (hours_before, refund_pct), min_age (0, 18 or 21), a dress_code, an arrival_window_minutes and notes. |
category | string | One of the categories from /me. |
media | array | Up to 10 https images or videos, each with optional alt text. |
description | string | |
booking_url | string | An https link to it on your site. |
metadata | object | Up to 20 string keys of your own, echoed back on every read. |
Something wrong comes back as 422 naming the field, what it must be and an example fix. The full schema is at /schemas/v1/offering.json.
curl https://splitclub.com/api/v1/offerings \
-H "Authorization: Bearer sc_live_…" \
-H "Content-Type: application/json" \
-d '{
"external_id": "ocean-course-18",
"kind": "tee_time",
"title": "18 holes on the Ocean Course",
"description": "Championship course, cart and range balls included. Book a foursome, or the open spots in one.",
"category": "social",
"media": [
{
"url": "https://yourcdn.example/golf/ocean-course-18.jpg",
"alt": "The 18th fairway along the water"
}
],
"tiers": [
{
"external_id": "prime",
"label": "Prime",
"amount": 32500,
"currency": "usd",
"unit": "per_person",
"max_party": 4,
"time_window": { "from": "07:00", "to": "14:50" },
"fees": [{ "type": "service", "label": "Resort fee", "amount": 2500, "included": false }]
},
{
"external_id": "twilight",
"label": "Twilight",
"amount": 18500,
"currency": "usd",
"unit": "per_person",
"max_party": 4,
"time_window": { "from": "15:00", "to": "17:30" }
}
],
"availability": {
"timezone": "America/New_York",
"recurring": [
{
"rrule": "FREQ=DAILY;BYHOUR=7,8,9,10,11,12,13,14,15,16,17;BYMINUTE=0,10,20,30,40,50",
"starts_at": "2026-11-01T07:00:00-05:00",
"duration_minutes": 270,
"capacity": 4
}
],
"blackout_dates": ["2026-11-26"],
"lead_time_minutes": 60,
"cutoff_minutes": 1440
},
"policy": {
"cancellation": [{"hours_before": 48, "refund_pct": 100}, {"hours_before": 24, "refund_pct": 50}],
"dress_code": "Collared shirts, soft spikes only.",
"arrival_window_minutes": 30
},
"booking_url": "https://yourresort.example/tee-times"
}'curl https://splitclub.com/api/v1/offerings \
-H "Authorization: Bearer sc_live_…" \
-H "Content-Type: application/json" \
-d '{
"external_id": "hitting-bay",
"kind": "rental",
"title": "Hitting bay for up to six",
"description": "A heated bay with ball tracking, games for every level and food and drinks to the bay. Priced per bay, per hour.",
"category": "social",
"media": [{"url": "https://yourcdn.example/golf/hitting-bay.jpg", "alt": "A hitting bay at night"}],
"tiers": [
{
"external_id": "weekday-day",
"label": "Weekdays before 5",
"amount": 4500,
"currency": "usd",
"unit": "per_hour",
"max_party": 6,
"days": ["mon", "tue", "wed", "thu", "fri"],
"time_window": { "from": "09:00", "to": "16:59" }
},
{
"external_id": "peak",
"label": "Evenings and weekends",
"amount": 9500,
"currency": "usd",
"unit": "per_hour",
"max_party": 6,
"deposit": { "percent": 25}
}
],
"availability": {
"timezone": "America/New_York",
"recurring": [
{
"rrule": "FREQ=DAILY;BYHOUR=9,10,11,12,13,14,15,16,17,18,19,20,21,22;BYMINUTE=0",
"starts_at": "2026-11-01T09:00:00-05:00",
"duration_minutes": 60,
"capacity": 40
}
],
"lead_time_minutes": 30
},
"policy": { "cancellation": [{ "hours_before": 24, "refund_pct": 100 }], "arrival_window_minutes": 15},
"booking_url": "https://yourvenue.example/bays"
}'curl https://splitclub.com/api/v1/offerings \
-H "Authorization: Bearer sc_live_…" \
-H "Content-Type: application/json" \
-d '{
"external_id": "reformer-flow-50",
"kind": "class",
"title": "Reformer flow, 50 minutes",
"description": "All levels, twelve reformers. Grip socks required; there are pairs at the desk.",
"category": "wellness",
"media": [
{
"url": "https://yourcdn.example/pilates/reformer-flow.jpg",
"alt": "The reformer studio in the morning"
}
],
"tiers": [
{
"external_id": "drop-in",
"label": "Drop-in",
"amount": 3800,
"currency": "usd",
"unit": "per_person",
"max_party": 4
}
],
"availability": {
"timezone": "America/New_York",
"recurring": [
{
"rrule": "FREQ=WEEKLY;BYDAY=MO,WE,FR;BYHOUR=7,12,18;BYMINUTE=0",
"starts_at": "2026-11-02T07:00:00-05:00",
"duration_minutes": 50,
"capacity": 12
},
{
"rrule": "FREQ=WEEKLY;BYDAY=SA,SU;BYHOUR=9,10;BYMINUTE=30",
"starts_at": "2026-11-07T09:30:00-05:00",
"duration_minutes": 50,
"capacity": 12
}
],
"lead_time_minutes": 60,
"cutoff_minutes": 120
},
"policy": { "cancellation": [{ "hours_before": 12, "refund_pct": 100 }], "arrival_window_minutes": 10},
"booking_url": "https://yourstudio.example/schedule"
}'# Up to 25 at once, upserted on external_id: a nightly sync of the whole tee sheet is safe.
curl https://splitclub.com/api/v1/offerings \
-H "Authorization: Bearer sc_live_…" \
-H "Content-Type: application/json" \
-d '{ "offerings": [ { … }, { … } ] }'
curl "https://splitclub.com/api/v1/offerings?status=approved&limit=100" \
-H "Authorization: Bearer sc_live_…"Boats: sync your fleet
For boat-rental platforms and fleet operators. Boatsetter, GetMyBoat, Click&Boat, Sailo, SamBoat or your own fleet system can sync every boat, with everything SplitClub needs to show it properly: specs, captain terms, the marina, prices by duration, photos and amenities. Send one boat, or { "boats": [...] } with up to 100 per request, and run the same sync whenever your fleet changes.
Each boat is upserted on your external_id, so a nightly full sync is safe: an unchanged boat answers unchanged and nothing happens. Every boat is reviewed before SplitClub uses it, and approved boats appear only in the boat days and tie-ups SplitClub puts on in each city. Change a boat and it goes back to review; boats are never shown on a map.
| Field | Type | Notes |
|---|---|---|
external_idrequired | string | Your id for the boat, up to 128 characters, no spaces. |
namerequired | string | The boat's name or listing title, up to 120 characters. |
typerequired | string | motor_yacht, sport_yacht, catamaran, sailboat, center_console, pontoon, jet_ski or other. |
length_ftrequired | number | Length overall in feet, 5 to 600. |
capacityrequired | integer | Most guests aboard for a day trip, crew not counted. |
captainrequired | string | included, optional, required_extra (required and charged on top) or bareboat. |
locationrequired | object | city (required), region, country (ISO code), marina, address, and lat/lng together. Where guests board. |
pricingrequired | array | 1 to 20 rates for the whole boat: duration_hours, amount in cents (220000 is $2,200.00), currency, and optional label, captain_included, fuel_included, days. |
photo_urlsrequired | array | 1 to 20 https image URLs in the order to show them. The first is the cover. |
make | string | Builder, e.g. Azimut. With model and year. |
cabins | integer | 0 to 50. |
deposit_amount | integer | Security deposit in cents. deposit_note and fees_note say the rest. |
amenities | array | Up to 40, e.g. bathroom, air_conditioning, bluetooth_audio, paddleboard. |
description | string | Up to 4,000 characters. |
availability | object | An ical_url, a url, or blocks of starts_at/ends_at: whichever your system has. |
source_platform | string | Where the listing lives, as a slug (your platform's name, or own), with its source_url. |
booking_url | string | An https link to book it on your site. |
active | boolean | false takes the boat out of everything at once. true brings it back; an unchanged boat that was approved returns approved. |
metadata | object | Up to 20 string keys of your own, echoed back on every read. |
extra | object | Anything else your listing has, in your own shape. Seen by reviewers, not guests. |
A fleet is handled boat by boat. Valid boats are saved even when others in the same request aren't, and results lists each one in order: created, updated, unchanged or error, with errors naming the field, like /boats/1/length_ft. If nothing in the request was valid, the answer is 422. A single boat answers 201 when it's new and 200 otherwise.
status is pending, approved, rejected (with a review_note) or inactive. Read your fleet with GET /boats (?status=, ?limit=, and ?before= from next_before) or one boat with GET /boats/{id}. Keys need the boats:write and boats:read scopes; sandbox keys have them, and test boats never reach anyone. A request counts as one write however many boats it carries, bodies can be up to 1 MB here, and up to 5,000 boats can wait for review at once. The full schema is at schemas/v1/boat.json. Product names are trademarks of their owners.
curl https://splitclub.com/api/v1/boats \
-H "Authorization: Bearer sc_live_…" \
-H "Content-Type: application/json" \
-d '{
"boats": [
{
"external_id": "listing-88213",
"name": "55\u0027 Azimut \"Sea Breeze\"",
"type": "motor_yacht",
"make": "Azimut", "model": "55 Flybridge", "year": 2019,
"length_ft": 55, "capacity": 12, "cabins": 3,
"captain": "included",
"location": {
"city": "Miami", "region": "FL", "country": "US",
"marina": "Miami Beach Marina",
"address": "300 Alton Rd, Miami Beach, FL 33139",
"lat": 25.7712, "lng": -80.1397
},
"pricing": [
{ "label": "Half day", "duration_hours": 4, "amount": 220000,
"currency": "usd", "captain_included": true, "fuel_included": true },
{ "label": "Full day", "duration_hours": 8, "amount": 380000,
"currency": "usd", "captain_included": true, "fuel_included": true }
],
"deposit_amount": 100000,
"fees_note": "Captain gratuity and sales tax not included.",
"amenities": ["bathroom", "air_conditioning", "bluetooth_audio", "floating_mat"],
"photo_urls": [
"https://cdn.example.com/boats/88213/cover.jpg",
"https://cdn.example.com/boats/88213/flybridge.jpg"
],
"availability": { "ical_url": "https://example.com/boats/88213/calendar.ics" },
"source_platform": "own",
"source_url": "https://example.com/boats/88213"
},
{
"external_id": "listing-90417",
"name": "24\u0027 center console",
"type": "center_console",
"length_ft": "24 ft",
"capacity": 8, "captain": "optional",
"location": { "city": "Miami", "marina": "Bayside Marina" },
"pricing": [{ "duration_hours": 4, "amount": 65000, "currency": "usd" }],
"photo_urls": ["https://cdn.example.com/boats/90417/cover.jpg"]
}
]
}'{
"data": [
{ "id": "5f0c…", "object": "boat", "external_id": "listing-88213",
"status": "pending", "active": true, "name": "55' Azimut \"Sea Breeze\"", … }
],
"results": [
{ "index": 0, "external_id": "listing-88213", "id": "5f0c…",
"result": "created", "status": "pending" },
{ "index": 1, "external_id": "listing-90417", "result": "error",
"errors": [{
"code": "invalid_type",
"field": "/boats/1/length_ft",
"message": "`/boats/1/length_ft` must be a number, from 5 to 600; you sent \"24 ft\".",
"expected": "a number, from 5 to 600",
"example_fix": "\"length_ft\": 55",
"doc_url": "https://splitclub.com/developers#schema-boat"
}] }
],
"summary": { "created": 1, "updated": 0, "unchanged": 0, "failed": 1 },
"errors": [ … ]
}# Sold, laid up or delisted: send it with "active": false …
curl https://splitclub.com/api/v1/boats \
-H "Authorization: Bearer sc_live_…" \
-H "Content-Type: application/json" \
-d '{ "external_id": "listing-88213", …, "active": false }'
# … or by SplitClub's id
curl -X POST https://splitclub.com/api/v1/boats/5f0c…/deactivate \
-H "Authorization: Bearer sc_live_…"
# Your fleet, by review status
curl "https://splitclub.com/api/v1/boats?status=approved&limit=100" \
-H "Authorization: Bearer sc_live_…"Analytics
Analytics cover the experiences you posted, once they're approved, plus views of your venue's own page. Pass from and to (ISO 8601, up to 366 days apart; the default is the last 30 days), and experience_id to narrow it to one.
| Metric | What it counts |
|---|---|
views | Times the experience's page was opened in the SplitClub app. |
unique_viewers | Different people who opened it. |
booking_starts | Different people who started booking. |
saves | Times it was liked or saved for later. |
bookings | Paid places. |
guests | People those bookings bring, including the guests they add. |
booked_value | US dollars paid for those places, before SplitClub's service fee. |
checked_in | Bookings checked in at the door. |
view_to_booking_rate | Bookings divided by unique viewers. |
show_rate | Check-ins divided by bookings. |
venue_page_views | Times your venue's own page was opened. |
Figures are totals. They never identify a person, and test payments are left out.
curl "https://splitclub.com/api/v1/analytics?from=2026-09-01T00:00:00Z&to=2026-10-01T00:00:00Z" \
-H "Authorization: Bearer sc_live_…"{
"data": {
"from": "2026-09-01T00:00:00+00:00",
"to": "2026-10-01T00:00:00+00:00",
"totals": {
"experiences": 12, "views": 1840, "unique_viewers": 912, "booking_starts": 141,
"saves": 77, "bookings": 63, "guests": 118, "booked_value": 9135,
"checked_in": 57, "view_to_booking_rate": 0.0691, "show_rate": 0.9048,
"venue_page_views": 406
},
"experiences": [
{ "experience_id": "…", "title": "Saturday supper club", "starts_at": "…",
"views": 512, "unique_viewers": 270, "booking_starts": 44, "saves": 21,
"bookings": 19, "guests": 36, "booked_value": 2755, "checked_in": 18,
"view_to_booking_rate": 0.0704 }
],
"daily": [ { "date": "2026-09-01", "views": 54, "bookings": 2 } ]
}
}Connect a platform
Create a connection with your key; the response carries the connection's webhook URL, once. Paste it into the platform's webhook settings.
| Provider | Platforms |
|---|---|
| Reservations and events | sevenroomsopentableresytocktripleseat |
| Point of sale | toastsquarecloverlightspeedspotontouchbistrosimphony |
| Your own system | custom: the same JSON body as POST /experiences |
Every delivery is stored as it arrives and answered 200 straight away, and a redelivery of the same event changes nothing. A delivery that carries an experience in the POST /experiences shape (as custom, or under an experience key) becomes a submission and goes to review like any other. Reservations, orders and other platform events are kept for your venue and reviewed by SplitClub. For Square, pass "signing_secret" when you create the connection and every delivery's signature is checked. Up to ten active connections per venue; POST /integrations/{id}/revoke turns one off.
curl https://splitclub.com/api/v1/integrations \
-H "Authorization: Bearer sc_live_…" \
-H "Content-Type: application/json" \
-d '{ "provider": "sevenrooms" }'{
"data": {
"id": "…",
"provider": "sevenrooms",
"webhook_url": "https://splitclub.com/api/v1/hooks/sevenrooms/sc_hook_…",
"token_prefix": "sc_hook_1a2b",
"created_at": "…"
},
"webhook_token": "sc_hook_…"
}Errors
Errors share one shape. field appears when a specific input is wrong; quote request_id if you contact us.
| Status | Code | When |
|---|---|---|
| 400 | invalid_json | The body isn't JSON. |
| 401 | unauthorized | The key is missing, wrong or revoked. |
| 404 | not_found | No such route, or the experience isn't yours. |
| 405 | method_not_allowed | The route doesn't take that method. |
| 413 | payload_too_large | The body is over 64 KB (1 MB for POST /boats). |
| 422 | invalid_request | A field failed validation; see field and message. |
| 429 | rate_limited | Over the hourly limit; wait Retry-After seconds. |
| 500 | internal_error | Our side. Retry with backoff. |
{
"error": {
"code": "invalid_request",
"message": "must be in the future",
"field": "starts_at",
"request_id": "86f24adb-a41f-4320-bba1-00a9cdae696b"
}
}Versioning
This is version 1. We add fields to responses without notice, so ignore the ones you don't use; anything that would break an integration ships as a new version.
Your first request, in a minute.
Sign up, get a sandbox key, and call the API before you close this tab.