Skip to main content

Create a split

You need a connected Stripe account first: shares are opened directly on it, so a split cannot be created without one. See Become a merchant.

GET /api/merchants/me reports stripe_charges_enabled: true once onboarding is complete, and that is the flag to gate your own checkout on. Note that POST /api/checkout-splits itself only checks that a connected account exists, not that Stripe has enabled charges on it, so a half-onboarded account fails later at Stripe with the raw Stripe message rather than a clean 400. The hosted checkout endpoints do check the flag.

From the dashboard

Go to /merchant/splits/new, add each participant's email and amount, and optionally an order reference. There is no order-total field: the total is the sum of the amounts you enter, shown on the submit button. Every row needs an email and a positive amount before the form will submit. The form does not check the £1 minimum share, so a row under £1 submits and is then rejected by the API with Each share must be at least £1. Then share the generated pay links.

From the API

This endpoint requires a signed-in SharePay session belonging to the merchant's owner, in an Authorization: Bearer <supabase token> header. Passing a merchantId you do not own returns 403 Not your merchant. There is no API-key path to this endpoint: if you want a flow your server can drive with an API key, use the hosted checkout session instead, which is a separate endpoint.

POST /api/checkout-splits
{
"merchantId": "<your merchant id>",
"orderReference": "ORDER-123",
"currency": "gbp",
"participants": [
{ "email": "alex@example.com", "amountMinor": 4000 },
{ "email": "sam@example.com", "amountMinor": 4000 },
{ "email": "jo@example.com", "amountMinor": 4000 }
]
}
This endpoint takes pence

amountMinor is in minor units, so £40.00 is 4000. It must be a positive integer of at least 100, because each share must be at least £1. The hosted checkout endpoint is the other way round and takes its amount in pounds.

amountMinor is the person's share. Their card is charged that plus SharePay's flat 20p service fee, so a 4000 share is a £40.20 charge, and you receive the £40. See what SharePay charges.

Returns 201 with { id, status: "pending", participants: [...] }. Each entry in participants is:

FieldNotes
idThe participant row's id
emailAs you sent it
amountMinorTheir share, in pence
feeMinorThe SharePay service fee on their charge, in pence. Their card is charged amountMinor + feeMinor
paymentIntentIdThe Stripe PaymentIntent holding their share
clientSecretFor confirming that hold yourself, if you are not using the hosted pay page
payTokenWhat their pay link is built from
The casing changes between endpoints

This create response returns payToken, camelCase. The list and detail endpoints in Manage splits return the same value as pay_token, snake_case. Reading pay_token off the create response gives you undefined.

A 201 also means we have emailed every participant except the creator their invite, with a pay link in it. See what we email your customers.

Request fields

FieldRequiredNotes
merchantIdYesMust be a merchant the signed-in account owns
orderReferenceYesAny string. It is not unique and nothing dedupes on it. It appears on the Stripe charge description, in participant emails, and in webhook payloads
currencyNoDefaults to gbp. It is not validated here. Anything you send is passed straight to Stripe, so a value other than gbp either opens charges in that currency on a GB account or fails with a raw Stripe error. Send gbp
participantsYesNon-empty array of { email, amountMinor }

Limits and validation

  • No cap on participant count. The hosted checkout caps a split at 20 people; this endpoint does not cap it at all. Shares are opened as one live Stripe PaymentIntent per participant in a sequential loop, so a large array is slow and every element costs a real Stripe call. Size accordingly.
  • Each share must be at least £1. amountMinor must be a positive integer of 100 or more, and anything smaller is rejected with 400 Each share must be at least £1. before any Stripe call is made, so nobody is charged and no hold is placed. There is no maximum per share, and no order total to check the sum against: the split's total is whatever the shares add up to.
  • email is only checked for being a non-empty string. It is not validated as an address, and an unreachable one simply means that person never gets their invite.

If your request times out

Neither create endpoint is idempotent, and neither accepts an idempotency key. A blind retry after a timeout creates a second split with a second set of card holds on your customers' cards. (The Stripe idempotency key used internally is scoped to a single call, so it stops a duplicate PaymentIntent within one request and nothing more.)

Record your orderReference against the returned id before retrying, and if you are unsure whether the first attempt landed, check GET /api/checkout-splits first, which is ordered newest first.

Errors

StatusBodyCause
401Missing or invalid Authorization headerNo Bearer token
401Invalid tokenExpired or bad session token
403{"code":"consent_required","error":"Consent required", ...}The signed-in account has not accepted the current terms or privacy policy
400Validation failedmerchantId or orderReference not strings, or participants missing or empty
400Each participant needs an email and a positive integer amountMinorA participant failed the per-row check
400Each share must be at least £1.A participant's amountMinor is under 100
404Merchant not foundUnknown merchantId
403Not your merchantThe merchantId belongs to another account
400A checkout split needs at least one participantEmpty participant list reaching the engine
400Merchant has no Stripe connected accountNo stripe_account_id on the merchant
400the raw Stripe error messageAny PaymentIntent failure. Nothing is captured on this path, so nobody is charged. We try to cancel the holds already placed in that call, and the split is left at creation_failed. See creation_failed