Skip to content

Shop and restaurant tills

A campground’s shop, kiosk or restaurant often runs on its own till. This recipe connects that till to Grondia: every sale is posted as it is rung up and recorded next to the campground’s own till sales, and you can read the campground’s article list to keep your own in step.

  • Each sale stored once. A sale you post becomes a confirmed till transaction in Grondia, marked as coming from an external till. Posting the same sale twice does not create a second one.
  • Lines tied to the campground’s articles. A line that names a known sku or barcode is linked to that article, and an article that tracks stock comes off the shelf.
  • A place to look. The administrator sees the recent sales and a sync log on the External POS system card in the admin.

What it does not do: Grondia is not the payment processor here. You post a sale that the guest has already paid for. The request has no booking and no guest, so it cannot charge a sale to a guest’s pitch, and there is no way to return, void or cancel a sale through the API.

Scope Opens Needed for
pos:write POST pos/transactions/ Posting sales
pos:read GET pos/products/, GET pos/transactions/{external_ref}/ Reading the article list and reconciling

The campground’s administrator creates a till key on the External POS system card (App Store, section “Operations & staff”): name the key, tick pos:write and, if your till reads articles or checks sales, pos:read. The key is shown once. The Developer API screen does not offer the pos:* scopes; this card does.

The standard sandbox key is read-only and carries only the read scopes for bookings, sites, invoices and customers. It can neither post a sale nor read the article list. For a test campground with a till key, write to Felix, felix@grondia.com, or to Siro, siro@grondia.com.

Anna Müller, on pitch A12, buys two espressos and a croissant at the Beispielsee kiosk and pays with TWINT at the till. Your till has already taken the money; it now tells Grondia.

  1. Your till to Grondia API: GET pos/products/. pos:read

  2. Grondia API to Your till: Active articles with sku and barcode

  3. Your till: Anna pays at the till

  4. Your till to Grondia API: POST pos/transactions/. With an external_ref (pos:write)

  5. Grondia API to Your till: 201 Created. idempotent_replay false

  6. Your till to Grondia API: Same POST again after a timeout

  7. Grondia API to Your till: 200 OK. idempotent_replay true, no second sale

  8. Your till to Grondia API: GET pos/transactions/{external_ref}/. pos:read

  9. Grondia API to Your till: The stored sale, for reconciliation

The request names neither Anna nor her pitch: this endpoint carries no guest and no booking.

external_ref is your till’s own id for the sale. It is the idempotency key: choose one that stays the same when you retry the same sale, and never reuse it for another (Retrying safely has the rules). Send JSON, since the lines are a list, and amounts as decimal strings (Amounts you send).

Terminal window
curl https://api.campone.ch/api/v1/public/pos/transactions/ \
-H "Authorization: Bearer ck_a1b2cXXXXXXXXXXXXXXXXXXXXXXXX" \
-H "Content-Type: application/json" \
-d '{
"external_ref": "kiosk-2026-07-15-000417",
"payment_method": "TWINT_QR",
"total_amount": "11.50",
"items": [
{"description": "Espresso", "quantity": "2", "unit_price": "3.50",
"line_total": "7.00", "sku": "CAFE-ESP"},
{"description": "Croissant", "quantity": "1", "unit_price": "4.50",
"line_total": "4.50", "barcode": "2000000000008"}
]
}'

The answer is 201 Created the first time and 200 OK for a repeat of the same external_ref. Treat both as success. A repeat returns the sale as first stored, even if the body you sent differs; nothing is updated. Both carry the stored sale and idempotent_replay:

{
"id": 18231,
"transaction_number": "TXN-20260715084210-9F3A21C7",
"source": "external_pos",
"external_ref": "kiosk-2026-07-15-000417",
"payment_method": "TWINT_QR",
"total_amount": "11.50",
"status": "CONFIRMED",
"created_at": "2026-07-15T08:42:10.512903Z",
"items": [
{
"id": 40112,
"description": "Espresso",
"quantity": "2.000",
"unit_price": "3.50",
"line_total": "7.00",
"sku": "CAFE-ESP"
},
{
"id": 40113,
"description": "Croissant",
"quantity": "1.000",
"unit_price": "4.50",
"line_total": "4.50",
"sku": "BAK-CRS"
}
],
"idempotent_replay": false
}

The croissant line named only a barcode. Grondia found the campground’s article for it and the answer shows that article’s sku. A line that matches nothing comes back with an empty sku.

payment_method is one of CASH, WORLDLINE, WALLEE, POSTCARD, TWINT_QR, ADYEN, BANK_TRANSFER, VOUCHER, AUFS_HAUS, STRIPE, CHARGE_TO_PITCH or CASH_PAYOUT. Send the one that says how the guest paid. They are labels on a recorded sale: posting CHARGE_TO_PITCH does not charge any pitch.

GET pos/products/ returns the campground’s active articles, ordered by category and name, in pages of 50 (up to 200 with ?page_size=200). Each has id, sku, barcode, name, category (SHOP, SERVICE, RESTAURANT or OTHER), unit_price, unit_name, tax_rate and is_active. Send a sku or barcode back on a sale line to link it. The prices you post are the ones Grondia stores: it never replaces an amount with the article’s price.

Situation What happens What to do
More requests than the key’s hourly limit (Rate limits) 429 with a Retry-After header Wait, then post again with the same external_ref. The limit is 1000 an hour unless the key was set otherwise, and posting and reading share it
The call times out, the connection drops or Grondia answers with a 5xx You do not know whether the sale was stored Post it again with the same external_ref, or read it back; a 404 means it was not stored
A line total, or the total, is off by more than 0.01 400, naming the field Fix the arithmetic. Within a cent, Grondia stores its own figures: each line as quantity times unit price, the total as the sum of the lines
A quantity, price or total is negative 400. There is no returns path Record a return at the campground’s own till
items is empty, external_ref is blank or longer than 128 characters, or payment_method is not in the list 400 Fix the request
A sku is unknown, or a barcode is malformed or has a bad check digit The sale is stored and the line is left unlinked. A sale is never refused for this Check the article list. If both are sent and name different articles, the sku wins
external_ref contains a / The sale is stored, but the read-back address cannot hold it Use letters, digits, - and _
occurred_at and cashier_external_id Accepted and ignored. created_at is when Grondia received the sale, not when it was rung up (Till transactions have no time of their own) An offline till keeps its own times and reconciles on external_ref
Who rang the sale up Grondia names the person who created the key, else an administrator of the campground Do not rely on a cashier name
The campground uses another currency There is no currency field: a sale takes the campground’s currency, CHF unless it is set up with another Price in the campground’s currency

A 400 names the field that failed, for example {"total_amount": [{"code": "invalid", "detail": "total_amount must equal the sum of line totals (11.50).", "params": {}}]}. An error in one line is keyed by the line’s position: {"items": {"0": {"line_total": ["line_total must equal quantity × unit_price (7.00)."]}}}. A key without pos:write gets 403, a missing or revoked key 401 (Credentials). The other error shapes are in Errors.

GET pos/transactions/{external_ref}/ returns a sale posted through this API, or 404 with {"detail": "not_found"}. It does not return sales rung up at the campground’s own till. There is no list of sales: keep your own external_ref log and check the ones you are unsure about.

  • The campground’s administrator created the till key on the External POS system card, with only the scopes you use, and you store the key per campground.
  • Every sale has an external_ref that is stable across retries and unique per sale, without a /.
  • You treat 200 and 201 alike, and you retry timeouts, 5xx and 429 with the same external_ref, after a pause.
  • Your arithmetic matches: lines are quantity times unit price, the total is the sum of the lines.
  • You send sku or barcode on lines that have an article, and you checked with the campground that its articles exist.
  • Returns and voids stay at the campground’s own till, and your staff know it.
  • You tried a test sale with the campground, and the administrator saw it under recent inbound transactions on the External POS system card.

When the till works, ask for the review: Partner program.