Balance Payments and Refunds API
These are the API's write operations. They record external money movements, taken outside Seaty (cash on the door, a cheque, a card machine that is not Seaty's, or a bank transfer), against an event. They run the same validation and send the same email as recording a payment or refund by hand in the Seaty admin tools.
They do not take card payments or move money. They record that a payment or refund happened elsewhere, so your Seaty figures reflect reality.
| Method | Path | Records | Scope |
|---|---|---|---|
| POST | /v1/events/{id}/balance-payments | A payment received | write:balance_payments |
| POST | /v1/events/{id}/balance-refunds | A refund given | write:balance_payments |
Both endpoints share the same scope and the same request shape. The path is the only thing that says which direction the money went, so a single write:balance_payments key can record both payments and refunds and manage an attendee's balance end to end. A key without the scope receives 403 forbidden.
The request
Both endpoints take the same JSON body and an Idempotency-Key header (required, see below). To record a payment, post to balance-payments:
curl -X POST "https://developer.seaty.co.uk/v1/events/1024/balance-payments" \
-H "Authorization: Bearer sk_live_your_key_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 4d9c2f10-7e8a-4c2b-9a1e-2f6b3c8d5a01" \
-d '{
"payee_email": "buyer@example.com",
"amount": 1500,
"type_id": 1,
"notes": "Cash on the door"
}'
To record a refund, post the same body to balance-refunds:
curl -X POST "https://developer.seaty.co.uk/v1/events/1024/balance-refunds" \
-H "Authorization: Bearer sk_live_your_key_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 9b1e7c44-2a3f-4d6b-8c10-5f2b6c8d3a02" \
-d '{
"payee_email": "buyer@example.com",
"amount": 500,
"type_id": 4,
"notes": "Refunded by bank transfer"
}'
| Body field | Type | Required | Description |
|---|---|---|---|
payee_email | string | yes | Email of someone who already has an order on this event. |
amount | integer | yes | Amount in pence. Always positive for both payments and refunds. |
type_id | integer | yes | Method: 1 cash, 2 cheque, 3 card (taken outside Seaty), 4 bank transfer. |
notes | string | no | An optional note stored against the entry. |
date_payment_made | string (date) | no | ISO date the payment or refund was made. Defaults to today. |
amountis always positive on both endpoints. You never send a negative number for a refund: thebalance-refundspath is what records it as money going out.
Two rules they enforce
These are the same safeguards as the admin tools:
- The payee must already have an order on the event. You cannot record anything for an email that has never ordered. If you try, you get
422 unprocessable_entity. - A refund cannot exceed the total ever paid by that payee on the event. An over-refund returns
422 unprocessable_entity.
Idempotency: never record twice
An idempotency key is a value you generate to stand for one payment or refund you intend to make. You send it in the Idempotency-Key header, and Seaty uses it to guarantee that the same intended entry is recorded at most once, even if the request reaches the server more than once (a timeout, a dropped connection, an over-eager retry). It is required on both endpoints; a request without it gets 400 bad_request.
What shape it needs: any non-empty string, up to 200 characters. The value is opaque to Seaty, so the format is yours to choose, but a UUID (version 4) is the standard choice because it is effectively unique and collision-free (for example 4d9c2f10-7e8a-4c2b-9a1e-2f6b3c8d5a01). Keys are scoped to your organisation, so they only need to be unique within it.
How to use it: generate a new key for every genuinely new payment or refund, and reuse the same key only when retrying that exact one after a failure.
- If a request with a key succeeds, a later retry with the same key returns the original entry again rather than recording a second one.
- If a request is still in flight when a duplicate arrives, the duplicate gets
409 conflict(the first is still processing). - A failed request (for example a validation error) does not consume the key, so you can fix the problem and retry with the same key.
The response
Both endpoints return the recorded entry in the same shape. is_refund tells you which it was:
{
"id": 90233,
"guid": "f1c2...",
"event_id": 1024,
"payee_email": "buyer@example.com",
"amount": 1500,
"type_id": 1,
"is_refund": false,
"date_payment_made": "2026-06-04T00:00:00Z",
"notes": "Cash on the door"
}
| Field | Type | Description |
|---|---|---|
id | integer | The new entry's id. |
guid | string | null | Its public reference. |
event_id | integer | The event it was recorded against. |
payee_email | string | null | The payee. |
amount | integer | Amount in pence, always positive. |
type_id | integer | null | The method code. |
is_refund | boolean | false from balance-payments, true from balance-refunds. |
date_payment_made | string | When it was made (ISO 8601). |
notes | string | null | Any note recorded. |
The payee is also sent the usual Seaty payment or refund email, exactly as if it had been recorded by hand.
A balance refund does not refund a card payment. To refund money that was taken by card through Seaty, use Cancel Order in the admin tools instead.
Reading entries back
Whatever you record here appears in an order's payments (when it can be linked to a specific order) and contributes to the order's amount_paid, amount_refunded and balance. The two halves, recording and reading, are designed to reconcile.
Need help? Email support@seaty.co.uk.