Skip to main content

Till (cash drawer)

Open the till with the cash in the drawer, put cash in or take it out during the day, and close it with what you counted. These are the same tills the POS screen opens and closes, so a till opened here shows as open on the POS and in the staff app.

Cash sales, cash refunds and cash security deposits are never sent here. They are read from the payments already recorded while the till is open. Each one counts at the till it was taken at, not at the booking's pickup location: a cash payment taken at one location for a booking picked up at another counts at the first.

Should be in the drawer = started with + cash sales - cash refunds + deposits held - deposits returned + cash put in - cash taken out.

Which till a payment counts at​

A payment, refund or deposit recorded while a till is open is linked to that till when it is recorded:

  • Send location_id with Add a payment or a security deposit record, release or capture, and it counts at the till open at that location. A location from another account returns 422.
  • Without location_id, it counts at the shop's open till when exactly one is open. With two or more open, it is recorded but counted at no till.
  • A payment a customer makes online never counts at a till.

Payments recorded before a till was opened, or before tills existed, are not counted.

Required scopes: bookings:read to read, bookings:write to open, move cash and close.

Locations​

A shop with one location has one till: leave location_id out. A shop with two or more locations has a till per location, and location_id is required. A location from another account returns 422.

GET /api/v1/till​

The till at a location: whether it is open, its figures, and what the next opening would be filled in with.

Query parameters​

ParameterTypeDescription
location_idintegerThe till's location. Required when the shop has two or more locations.

Response​

{
"data": {
"enabled": true,
"location_id": null,
"till": {
"id": 41,
"status": "open",
"location_id": null,
"location_name": null,
"opened_at": "2026-10-16T15:02:00+00:00",
"opened_at_local": "8:02 AM",
"opened_by": "Jane Doe",
"closed_at": null,
"closed_by": null,
"note": null,
"currency": "USD",
"started_with": 200,
"cash_sales": 486.5,
"cash_sales_count": 14,
"cash_refunds": 20,
"cash_refunds_count": 1,
"deposits_held": 100,
"deposits_held_count": 1,
"deposits_returned": 50,
"deposits_returned_count": 1,
"not_counted": 40,
"not_counted_count": 2,
"cash_in": 0,
"cash_out": 15,
"movements_net": -15,
"movements_count": 1,
"movements": [
{ "id": 7, "direction": "out", "amount": 15, "reason": "Postage", "at": "2026-10-16T19:10:00+00:00" }
],
"expected": 701.5,
"counted": null,
"difference": null,
"left_for_tomorrow": null,
"to_bank": null
},
"opened_today": true,
"suggested_float": 200,
"suggested_float_from_last_close": true,
"leave_for_tomorrow_default": 200,
"summary_email_to": "owner@example.test",
"summary_email_to_name": "Jane"
}
}
FieldDescription
enabledWhether the shop opens and closes its till (Settings > Point of sale). Off until the owner switches it on.
tillThe open till, or null when none is open here.
till.deposits_heldSecurity deposits taken in cash at this till, and deposits_held_count how many. A deposit kept for damage stays in the drawer.
till.deposits_returnedCash deposits handed back at this till, and deposits_returned_count how many. Not part of movements.
till.not_countedCash taken today that no till counted (for a closed till, on the day it closed), and not_counted_count how many payments. This happens when cash was taken before a till was open, or when several tills were open and nothing said which one. Every till shows the same figure that day, and it is not part of expected. A team member limited to some locations sees only cash for bookings at those locations. POST /api/v1/till/add-not-counted counts it in a till.
opened_todayA till was opened here today, in the shop's own day.
suggested_floatWhat the opening is filled in with: what was left at the last close, else the shop's opening float, else null.
leave_for_tomorrow_defaultWhat a close fills in for the cash left for tomorrow: the shop's opening float, else what this till started with. null when no till is open.
summary_email_toWhere the end-of-day summary goes, and summary_email_to_name who that is. On the staff app, the address is null for anyone without Point of sale settings; they still get the name.

POST /api/v1/till/open​

Open the till. If a till is already open at this location, nothing new is opened and that till comes back with already_open: true and status 200, so a repeated request is safe.

FieldTypeDescription
amountnumberRequired. The cash in the drawer. Zero or more.
location_idintegerSee Locations.

Returns 201 with the same body as GET /till plus already_open.

POST /api/v1/till/cash​

Put cash in or take it out of the open till.

FieldTypeDescription
directionstringRequired. in or out.
amountnumberRequired. Above zero.
reasonstringOptional. Up to 120 characters, e.g. "Change from the bank".
location_idintegerSee Locations.

Returns 201 with the till's new figures. 409 till_not_open when no till is open here.

POST /api/v1/till/close​

Close the open till with what was counted. "Should be in the drawer" is saved with it, so a payment edited later never changes a day that was already counted. The end-of-day summary is emailed when the till closes.

FieldTypeDescription
countednumberRequired. The cash counted in the drawer.
left_for_tomorrownumberOptional. Cash left in the drawer for the next opening. Left out means nothing is left. Cannot be more than counted.
notestringOptional. Up to 1,000 characters.
location_idintegerSee Locations.

Response​

{
"data": {
"closed": {
"id": 41,
"status": "closed",
"expected": 701.5,
"counted": 697.5,
"difference": -4,
"left_for_tomorrow": 200,
"to_bank": 497.5
},
"summary_emailed_to": "owner@example.test",
"enabled": true,
"till": null,
"opened_today": true,
"suggested_float": 200,
"suggested_float_from_last_close": true
}
}

summary_emailed_to is null when the summary did not go out (no address, or the send failed). The till is closed either way.

difference is counted minus expected: below zero is short, above zero is over. (closed carries every field of a till; some are left out above.)

POST /api/v1/till/add-not-counted​

Count today's cash that no till counted (not_counted) in the open till at this location, so expected includes it. A second call moves nothing new, and added.count is 0. Bank transfers and other non-cash payments are never moved.

FieldTypeDescription
location_idintegerThe till's location. Required when you have more than one location.
{
"data": {
"added": { "amount": 40, "count": 2 },
"till": { "expected": 240, "not_counted": 0, "not_counted_count": 0 }
}
}

The rest of the response is the same as GET /api/v1/till. A till that is not open returns 409 till_not_open. One closed by another device while this request runs returns 409 till_closed.

Errors​

StatusCodeWhen
409till_not_openCash in or out, or close, with no till open at this location.
409till_offThe shop has switched opening and closing the till off.
409till_busyAnother device was saving to this till at the same moment. Send the request again.
422validation_errorA missing or negative amount, a location that is not the shop's, or leaving more than was counted.

Staff app​

The staff app calls the same endpoints under /api/v1/mobile/till with the signed-in person's token. It needs the Record payments permission, and opens and closes are written under that person's name. Through an API key they carry no name.