Create a split yourself
Use this when you already know who is paying and how much — a quote you agreed over the phone, a booking, an invoice you are splitting between named people. You enter the emails and the amounts, and everyone gets a pay link.
It is the opposite way round from hosted checkout, where the buyer decides who to invite and for how much. Pick by who knows the split:
| You set it up | The buyer sets it up | |
|---|---|---|
| Use | This page | Hosted checkout |
| Who is invited is decided by | You, up front | The buyer, on our page |
| Started from | The dashboard, or your own signed-in session | Your server, with your API key |
Each person's share is charged straight to your own Stripe account, so there
has to be one. GET /api/merchants/me shows stripe_charges_enabled: true
once you are ready — gate your own checkout on that. See Become a
merchant.
Be aware this endpoint only checks that you have started connecting Stripe,
not that Stripe has finished approving you. A half-finished account therefore
fails later, and passes Stripe's own error message straight back to you instead
of a tidy one. Hosted checkout checks properly and gives you
Finish Stripe onboarding before accepting checkouts.
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
POST /api/checkout-splits
Sign in as Owner, Admin or Member and send that session as
Authorization: Bearer <supabase token>. A Finance colleague cannot create
splits, and sending a merchantId for a business you are not in returns
403 Not your merchant.
Your API key does not work here — it only opens hosted checkout sessions. If you want your server to start a split on its own, that is the page you want.
{
"merchantId": "<your merchant id>",
"orderReference": "ORDER-123",
"currency": "gbp",
"participants": [
]
}
amountMinor is in pence, so £40.00 is 4000. It must be a whole number
of at least 100, because each share must be at least £1. Hosted
checkout 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 less your merchant fee and Stripe's processing fees. See
what SharePay charges.
Returns 201 with { id, status: "pending", participants: [...] }. Each entry
in participants is:
| Field | Notes |
|---|---|
id | The participant row's id |
email | As you sent it |
amountMinor | Their share, in pence |
feeMinor | The SharePay service fee on their charge, in pence. Their card is charged amountMinor + feeMinor |
paymentIntentId | The Stripe payment holding their share |
clientSecret | Only needed if you are building your own pay page rather than using ours |
payToken | What their pay link is built from |
This create response returns payToken, camelCase. The list and detail
endpoints in Refunds, disputes and order status 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
| Field | Required | Notes |
|---|---|---|
merchantId | Yes | Must be a merchant the signed-in account owns |
orderReference | Yes | Any string. It is not unique and nothing dedupes on it. It appears on the Stripe charge description, in participant emails, and in webhook payloads |
currency | No | Defaults 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 |
participants | Yes | Non-empty array of { email, amountMinor } |
Limits and validation
- No cap on how many people. Hosted checkout stops at 20; this endpoint does not stop you at all. We place each person's card hold one at a time, and each one is a real call to Stripe, so a long list is slow and every extra name adds to it. Keep it sensible.
- Each share must be at least £1.
amountMinormust be a positive integer of100or more, and anything smaller is rejected with400 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. emailis 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
Retrying is not safe. Neither way of creating a split can tell a retry apart from a new request, and there is no key you can send to make it. So if a request times out and you simply send it again, you get a second split, and a second card hold on every customer.
If you are not sure whether the first attempt worked, look before you retry:
GET /api/checkout-splits lists your splits newest first. Record the returned
id against your own order reference as soon as you have it.
Errors
| Status | Body | Cause |
|---|---|---|
401 | Missing or invalid Authorization header | No Bearer token |
401 | Invalid token | Expired or bad session token |
403 | {"code":"consent_required","error":"Consent required", ...} | The signed-in account has not accepted the current terms or privacy policy |
400 | Validation failed | merchantId or orderReference not strings, or participants missing or empty |
400 | Each participant needs an email and a positive integer amountMinor | A participant failed the per-row check |
400 | Each share must be at least £1. | A participant's amountMinor is under 100 |
404 | Merchant not found | Unknown merchantId |
403 | Not your merchant | The merchantId belongs to another account |
400 | A checkout split needs at least one participant | The participant list arrived empty |
400 | Merchant has no Stripe connected account | You have not started connecting Stripe |
400 | {"code":"billing_card_required", ...} | Your processor (Adyen) needs a card on file in Settings, Billing, in the same mode, before it takes splits |
400 | {"code":"billing_paused", ...} | A SharePay bill has been unpaid for 14 days. Pay it in Settings, Billing and splits work again at once |
400 | Stripe's own error message | A card hold could not be placed. Nothing is ever taken on this path, so nobody is charged. We try to release the holds already placed, and the split ends at creation_failed. See creation_failed |