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_…"
200 OK
{
  "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.

Test mode
palmhollowgolfresort.example/book

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" }
  }'
How it works, events and signatures

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_id again 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.

  1. 1Your server creates a checkoutPOST /v1/checkouts with 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.
  2. 2Your page opens itSplitClub.button() or SplitClub.open() with the checkout id and your publishable key. SplitClub opens in a modal on your site, full screen on phones.
  3. 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.
  4. 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.
  5. 5Fully paid, you are paidWhen every share is held SplitClub captures them, your payout goes through Stripe Connect, you receive checkout.completed and the guest returns to your return_url. Not paid by pay_by? Every hold is released and you receive checkout.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.

FieldTypeNotes
amountintegerMinor units: 48000 is $480.00.
currencystringISO code, e.g. usd.
party_sizeinteger2 to 50 people, the organizer included.
pay_byISO 8601When the party must be fully held: 10 minutes to 6 days ahead (card holds last 7 days). After it, every hold is released.
descriptionstringWhat the guest sees: the booking, in your words.
return_urlhttps URLWhere the guest lands once the party is funded.
cancel_urlhttps URLOptional. Where the guest goes if they back out.
external_booking_idstringYour booking's id. Acts as the idempotency key.
offering_external_idstringOptional. The offering this booking is for.
metadataobjectOptional. 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.

StatusMeaning
openCreated. Waiting for the organizer.
collectingThe party is chosen; shares are being held.
fundedEvery share is held.
completedCaptured. Your payout is scheduled.
expiredNot funded by pay_by. Everything released.
canceledYou 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" }
  }'
201 Created
{
  "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" }
}
Your booking page
<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.

EventWhen
checkout.createdYou created a checkout.
checkout.collectingThe organizer chose the party; the shares exist.
share.heldOne person's share is held on their card.
checkout.fundedEvery share is held.
checkout.completedThe holds are captured and your payout is scheduled. Confirm the booking on this one.
checkout.expiredpay_by passed before the party was in. Every hold is released.
checkout.canceledYou 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 key
POST /api/v1/webhook_endpoints
curl 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_…" }
checkout.completed
{
  "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);
}

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.

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.

ToolWhat it does
describe_venueThe key's venue, mode and scopes, and the order of work. Start here.
validateDry-runs any object against a schema and returns every error with its JSON pointer and a fix.
upsert_offeringsCreates or updates up to 25 offerings, each with tiers, availability and policy.
set_price_tiersReplaces one offering's price tiers.
set_availabilityReplaces one offering's slots, recurring rules and blackout dates.
create_checkoutStarts Pay with SplitClub for a booking and returns the checkout's link.
get_checkoutOne checkout: its status and every share's.
list_bookingsThe venue's checkouts, newest first, by status.
claude mcp add --transport http splitclub https://splitclubapp.com/api/mcp \
  --header "Authorization: Bearer sc_test_…"
Prompt · paste into any assistant connected to SplitClub

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. 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. 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. 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. 4

    Validate until it's valid

    validate returns every problem at once. A tier sent as 185.5, "USD" and per_player comes 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. 5

    Send it, in test mode

    upsert_offerings, or POST /v1/offerings. SplitClub reviews it before anyone sees it.

  6. 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"
        }
      }'
    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. 7

    Run one test checkout end to end

    Pay all four shares with 4242 4242 4242 4242. When the last hold lands, the resort receives checkout.completed, signed with SplitClub-Signature, and marks the booking paid. If the party isn't paid by pay_by, checkout.expired arrives 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));
    }
  8. 8

    Go live

    SplitClub issues live keys once the resort is verified and its payouts are set up. The keys change; nothing else does.

Offering · validates against offering.json
{
  "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
  }
}
validate → errors an agent can act on
[
  {
    "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.
  • share.jsonOne person's part. Never a phone number or an email.
  • 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".

ProviderForFields confirmedWebhooks
Club Prophet GolfDocs not publicwebhooks not documented
foreUP Golf32 of 35webhooks, not signed
GolfNow Golf20 of 31webhooks not documented
Lightspeed Golf Golf26 of 30no webhooks
Boulevard Spas and studios29 of 31signed webhooks (x-blvd-oauth-hmac-sha256)
Mindbody Spas and studios48 of 52signed webhooks (X-Mindbody-Signature)
Zenoti Spas and studios38 of 42webhooks; signature scheme not documented
DICE Tickets and events16 of 30no webhooks
Eventbrite Tickets and events25 of 34webhooks, not signed
FareHarbor Tours and rentals37 of 38webhooks, not signed
Peek Pro Tours and rentals34 of 35webhooks, not signed
OpenTable Restaurants31 of 35webhooks, not signed
Resy RestaurantsDocs not publicwebhooks not documented
SevenRooms RestaurantsDocs not publicwebhooks not documented
Tock Restaurants21 of 26webhooks, not signed
Square Point of sale36 of 37signed webhooks (x-square-hmacsha256-signature)
Toast Point of sale30 of 30signed 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

Base URLhttps://splitclub.com/api/v1
  • 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.

GET /api/v1/me
curl https://splitclub.com/api/v1/me \
  -H "Authorization: Bearer sc_live_…"
200 OK
{
  "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.

60posts per hour
600reads per hour
500experiences waiting for review at once

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

FieldTypeNotes
titlerequiredstringUp to 120 characters.
starts_atrequiredISO 8601In the future, at most two years ahead. Include the offset.
external_idstringYour own ID, up to 128 characters. Posting the same one again returns the original with "duplicate": true and changes nothing, so retries are safe.
descriptionstringUp to 4,000 characters.
categorystringOne of the categories from /me.
ends_atISO 8601After starts_at.
price_per_personnumberUS dollars, 0 to 100,000.
capacityintegerPeople, 1 to 5,000.
photo_urlsarrayUp to 10 https image URLs.
booking_urlstringAn 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"
  }'
201 Created
{
  "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"
  }'

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.

GET /api/v1/experiences
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.

FieldTypeNotes
external_idrequiredstringYour own ID, up to 128 characters, no spaces.
kindrequiredstringtee_time, class, rental, table, cabana, daybed, spa_treatment, boat_day, ticketed_event, package, ride or dining.
titlerequiredstringWhat it is, as a guest would say it.
tiersrequiredarray1 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.
availabilityobjectA 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.
policyobjectcancellation refund tiers (hours_before, refund_pct), min_age (0, 18 or 21), a dress_code, an arrival_window_minutes and notes.
categorystringOne of the categories from /me.
mediaarrayUp to 10 https images or videos, each with optional alt text.
descriptionstring
booking_urlstringAn https link to it on your site.
metadataobjectUp 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"
  }'
Sync many, read back
# 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.

FieldTypeNotes
external_idrequiredstringYour id for the boat, up to 128 characters, no spaces.
namerequiredstringThe boat's name or listing title, up to 120 characters.
typerequiredstringmotor_yacht, sport_yacht, catamaran, sailboat, center_console, pontoon, jet_ski or other.
length_ftrequirednumberLength overall in feet, 5 to 600.
capacityrequiredintegerMost guests aboard for a day trip, crew not counted.
captainrequiredstringincluded, optional, required_extra (required and charged on top) or bareboat.
locationrequiredobjectcity (required), region, country (ISO code), marina, address, and lat/lng together. Where guests board.
pricingrequiredarray1 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_urlsrequiredarray1 to 20 https image URLs in the order to show them. The first is the cover.
makestringBuilder, e.g. Azimut. With model and year.
cabinsinteger0 to 50.
deposit_amountintegerSecurity deposit in cents. deposit_note and fees_note say the rest.
amenitiesarrayUp to 40, e.g. bathroom, air_conditioning, bluetooth_audio, paddleboard.
descriptionstringUp to 4,000 characters.
availabilityobjectAn ical_url, a url, or blocks of starts_at/ends_at: whichever your system has.
source_platformstringWhere the listing lives, as a slug (your platform's name, or own), with its source_url.
booking_urlstringAn https link to book it on your site.
activebooleanfalse takes the boat out of everything at once. true brings it back; an unchanged boat that was approved returns approved.
metadataobjectUp to 20 string keys of your own, echoed back on every read.
extraobjectAnything 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.

POST /api/v1/boats
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"]
    }
  ]
}'
200 OK · one created, one to fix
{
  "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": [ … ]
}
Deactivate and read back
# 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.

MetricWhat it counts
viewsTimes the experience's page was opened in the SplitClub app.
unique_viewersDifferent people who opened it.
booking_startsDifferent people who started booking.
savesTimes it was liked or saved for later.
bookingsPaid places.
guestsPeople those bookings bring, including the guests they add.
booked_valueUS dollars paid for those places, before SplitClub's service fee.
checked_inBookings checked in at the door.
view_to_booking_rateBookings divided by unique viewers.
show_rateCheck-ins divided by bookings.
venue_page_viewsTimes your venue's own page was opened.

Figures are totals. They never identify a person, and test payments are left out.

GET /api/v1/analytics
curl "https://splitclub.com/api/v1/analytics?from=2026-09-01T00:00:00Z&to=2026-10-01T00:00:00Z" \
  -H "Authorization: Bearer sc_live_…"
200 OK · example figures, for shape only
{
  "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.

ProviderPlatforms
Reservations and events
sevenroomsopentableresytocktripleseat
Point of sale
toastsquarecloverlightspeedspotontouchbistrosimphony
Your own systemcustom: 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.

POST /api/v1/integrations
curl https://splitclub.com/api/v1/integrations \
  -H "Authorization: Bearer sc_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "provider": "sevenrooms" }'
201 Created
{
  "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.

StatusCodeWhen
400invalid_jsonThe body isn't JSON.
401unauthorizedThe key is missing, wrong or revoked.
404not_foundNo such route, or the experience isn't yours.
405method_not_allowedThe route doesn't take that method.
413payload_too_largeThe body is over 64 KB (1 MB for POST /boats).
422invalid_requestA field failed validation; see field and message.
429rate_limitedOver the hourly limit; wait Retry-After seconds.
500internal_errorOur side. Retry with backoff.
422 Unprocessable
{
  "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.