Skip to content
v26.3

Customer Account: Balance & Loyalty Points

Every customer (api/v1/customers) maintains two separate, permanent accounts:

  • Prepaid balance (accountBalance) — a real-money balance that the customer pays in advance and then uses as a means of payment for purchases. Can be paid out.
  • Loyalty points / bonus (loyaltyPoints / bonusBalance) — points/bonus that are credited and redeemed with purchases. Cannot be paid out (the bonus is forfeited when the balance is paid out).

This page corrects an older inaccuracy in the REST API overview: there is no POST /customers/{id}/credit and no GET /customers/{id}/transactions — these routes do not exist in the code. The actual routes are documented below.

Endpoints

CustomersController — base api/v1/customers:

Method Endpoint Purpose
GET /{id}/account-transactions Balance/account movements (prepaid ledger)
GET /{id}/point-transactions Loyalty point movements
POST /{id}/payout Pay out remaining balance (only accountBalance, not bonus)
POST /{id}/settle Settle an open balance (e.g. a Disco IOU) via a payment
GET /card/{cardId} Resolve a customer by customer card
GET /{id}/invoice-summary The customer's invoice/subscription overview

Both transaction endpoints optionally accept dateFrom/dateTo as query parameters to narrow the period.

Account Movements (account-transactions)

GET /api/v1/customers/cus_a1b2c3/account-transactions?dateFrom=2026-07-01&dateTo=2026-07-31
Authorization: Bearer eyJ...
{
  "success": true,
  "data": [
    {
      "id": "cat_001",
      "customerId": "cus_a1b2c3",
      "customerName": "Max Mustermann",
      "receiptId": "rec_9f8e7d",
      "type": 0,
      "amount": 50.00,
      "balanceAfter": 50.00,
      "bonusBalanceAfter": 2.50,
      "description": "Balance top-up: Balance 50€ x 1",
      "personalId": "usr_kasse1",
      "transactionDate": "2026-07-05T18:22:00Z"
    }
  ]
}

type is an int without its own public enum schema; values observed in practice: 0 top-up/ deposit, 1 use (payment with balance), 2 correction (manual void adjustment), 3 bonus credit, 4 bonus use/forfeiture, 5 payout.

Point Movements (point-transactions)

GET /api/v1/customers/cus_a1b2c3/point-transactions
Authorization: Bearer eyJ...
{
  "success": true,
  "data": [
    {
      "id": "cpt_001",
      "customerId": "cus_a1b2c3",
      "customerName": "Max Mustermann",
      "receiptId": "rec_9f8e7d",
      "articleId": "art_cola",
      "articleName": "Cola 0,3l",
      "type": 0,
      "points": 3.5,
      "pointsAfter": 42.5,
      "description": "Points for sale",
      "transactionDate": "2026-07-05T18:22:00Z"
    }
  ]
}

type: 0 = credited (Earned), 1 = redeemed (Redeemed), 2 = correction (Correction).

Paying Out (payout)

Pays out balance from accountBalance (not from the bonus — which is forfeited in the process).

POST /api/v1/customers/cus_a1b2c3/payout
Authorization: Bearer eyJ...
Content-Type: application/json

{ "amount": 20.00 }
{ "success": true, "data": { "success": true, "accountBalance": 30.00, "bonusBalance": 0 } }

Error case (insufficient balance) — 400 with errorCode: "INSUFFICIENT_BALANCE".

Settling a Balance (settle)

For an open (negative) balance — e.g. a Disco IOU (an uncovered remaining amount on leaving, see Disco API) — the customer pays in, and the balance moves toward zero:

POST /api/v1/customers/cus_a1b2c3/settle
Authorization: Bearer eyJ...
Content-Type: application/json

{ "amount": 15.00, "method": "EC" }

method is free-form (default "Bar") and only ends up in the description of the posting — there is no link to a payment terminal.

Topping Up Balance — the Real Way

There is no dedicated /credit endpoint. Instead, a prepaid balance is topped up exactly like any other sale: via the normal payment endpoints (POST /api/v1/payments/direct or POST /api/v1/payments/table), with two conditions:

  1. The article sold has extraOption: 1 (KundenAufladung) — a "top-up article" (e.g. "Balance 50 €"), created via the normal article API (POST /api/v1/articles).
  2. The payment is linked to the customer via customerId.

The article price (× quantity) is then automatically credited to the customer's accountBalance — in addition to the actual payment processing (the guest pays the top-up amount completely normally via payments[], e.g. cash or card). If a top-up bonus is configured on the article (cardUploadPercent/cardUploadAmount), a bonus is automatically credited on top.

POST /api/v1/payments/direct
Authorization: Bearer eyJ...
Idempotency-Key: 6f1c2a9e-3b7d-4e21-9b0a-1f2e3d4c5b6a
Content-Type: application/json

{
  "items": [ { "articleId": "art_aufladung50", "quantity": 1 } ],
  "payments": [ { "method": "Cash", "amount": 50.00 } ],
  "customerId": "cus_a1b2c3"
}

The top-up then appears as a type: 0 entry in account-transactions (see above), linked via receiptId to the resulting POS receipt.

Flow

  1. Create a customerPOST /api/v1/customers (minimum: name/customerNumber).
  2. Top up balance — sell a KundenAufladung article with customerId (POST /api/v1/payments/direct, see above).
  3. Book to the account — later purchases by the customer with payments[].method = "CustomerAccount" deduct from accountBalance (charged, not topped up).
  4. CheckGET /{id}/account-transactions reviews the history.
  5. Points — are credited/redeemed in the normal sales process, viewable via GET /{id}/point-transactions.
  6. Pay out/settlePOST /{id}/payout (return remaining balance) or POST /{id}/settle (settle an open balance/IOU).

Distinction: Customer Account Balance ↔ Disco Card Balance

Two different concepts that are easily confused:

Customer Account Balance Disco Card Balance
Endpoint payments/* + customers/* POST /api/v1/disco/guest/{cardId}/topup
Binding to the customer record, permanent to the physical card, for one visit
Payout POST /{id}/payout remaining balance at checkout (disco/guest/{cardId}/checkout)

→ Details on the card variant: Disco API (Cashless Club).