Spoonity for Panasonic POSView as markdown

Panasonic POS integration

This guide is for Panasonic engineers connecting a Panasonic POS to Spoonity loyalty and promotions. The POS sends Spoonity each check. Spoonity works out the discounts and returns them item by item. When the check closes, Spoonity records the sale in the customer's loyalty account.

The POS never calculates a Spoonity discount, never chooses a reward, and never calls any other Spoonity API. Three calls cover the whole integration: check, close and void.

Base URL: https://panasonic.spoonity.com

Contents

  1. Status
  2. The flow
  3. Authentication
  4. Identifiers
  5. The check: items, modifiers and combos
  6. POST /partners/pos/check
  7. Applying the discounts
  8. POST /partners/pos/close
  9. POST /partners/pos/void
  10. Repeating calls safely
  11. Timeouts and failures
  12. Errors
  13. Testing
  14. Checklist

1. Status

ItemStatus
check, close and void, item-level discounts, combos, holds, repeat-safe callsLive
Authorization: Bearer with the merchant's API keyLive for keys Spoonity has issued; merchant API keys in development
Rewards the customer picked in the app, found from the scanned codeIn development
Spoonity posts every closed check to the loyalty program (points and reward redemption)In development
Voiding a closed check also reverses it in the loyalty programIn development

Until an item is live, what this guide says about it is the contract we are building to.

2. The flow

  1. Before the till. The customer opens the merchant's app and picks the reward they want to use. The app tells Spoonity directly. The POS is not involved and never sees a list of rewards.
  2. Scan. At any point while the order is being taken, the cashier scans the customer's code from the app. The POS recognises it as a Spoonity code by its prefix (see Identifiers) and holds it on the check. No call is made at scan time.
  3. Pay Now. The POS sends the whole check to POST /partners/pos/check with the scanned code. Spoonity finds the customer and the reward they picked, applies the merchant's promotions, holds the reward for this check, and returns each discount with the item it applies to.
  4. Cancel and change. If the cashier cancels out of Pay Now, the POS removes the Spoonity discounts. No call is needed. When Pay Now is pressed again, the POS sends check again with the full new check, and that answer replaces the previous one.
  5. Close. When the check is paid and closed, the POS sends POST /partners/pos/close. Spoonity answers at once, so the POS can finish. Spoonity then posts the sale to the loyalty program in the background: the customer earns points and the reward is used up.

Send check and close for every check, including checks with no code scanned. A check with no code is priced for a guest: promotions open to everyone still apply, and no points are earned.

Customer app        POS terminal                    Spoonity
     |                    |                              |
     | pick a reward ---------------------------------->| saved for today
     |                    |                              |
     | show code -------->| scan: code held on check     |
     |                    | Pay Now: POST /check ------->| find the pick, price, hold
     |                    |<-----------------------------| discounts by item
     |                    | cancel, change, Pay Now:     |
     |                    | POST /check ---------------->| re-price, replace
     |                    |<-----------------------------| discounts by item
     |                    | apply discounts, take payment|
     |                    | check closed: POST /close -->| commit, answer at once
     |                    |<-----------------------------| { committed: true }
     |                    |                              | post sale to loyalty

3. Authentication

Send the merchant's API key as a bearer token on every request:

Authorization: Bearer MERCHANT_API_KEY

4. Identifiers

FieldRule
store_idThe store number. Letters, digits, _ . -, at most 60 characters, no colon.
check_idThe POS order number prefixed with the business date: YYYYMMDD-<order number>, for example 20261103-104202. Same character rules as store_id. Spoonity needs it to be unique per store for as long as the check can be refunded, and the date prefix guarantees that.
terminal_idOptional. Logged only.
pos_transaction_idYour unique id for the closed transaction. Required on close and void. 1 to 128 characters.
customer.customer_idThe code scanned from the customer's app, exactly as scanned, prefix included.

Recognising a Spoonity code. Spoonity codes start with a fixed prefix that Spoonity gives you during setup. Configure the POS to treat any scanned code with that prefix as a loyalty code. What follows the prefix is Spoonity's business: the POS sends the whole code and never interprets it.

5. The check: items, modifiers and combos

All money is integer cents (1149 is $11.49) and before tax. A decimal is a 400. Spoonity does not calculate tax.

Items. One line per item on the check: { line_id, sku, name?, price, quantity, categories?, modifiers?, discounts? }.

Modifiers. List each modifier on the item it belongs to, with its own unit price and quantity: { id, name?, price, quantity }. A modifier with no charge has price: 0.

Combos. Send a combo as one line with its parts in components:

FieldNotes
priceThe combo price, per combo.
quantityNumber of combos.
components[].component_idYour id for the part, unique within the combo. Spoonity uses it to say which part a discount applies to.
components[].skuThe part's PLU.
components[].priceThe part's a la carte price. It is not charged; Spoonity uses it to split the combo price across the parts.
components[].quantityUnits of the part in one combo.
components[].modifiersModifiers on that part. A combo line carries no modifiers of its own.

Spoonity splits the combo price across the parts in proportion to their a la carte prices, and the parts always add up to the combo price exactly. A reward on a part inside a combo takes off that part's share of the combo price, not its a la carte price.

Your own discounts. Discounts the POS already applied (employee, senior, manager) go on the line as discounts: [{ id, name?, amount }], whole-line cents. By default Spoonity does not add its own item discount to a line you already discounted, and order-level discounts are calculated after yours, so the same money is never discounted twice.

Limits (a request over any of them is a 400):

6. POST /partners/pos/check

Send when Pay Now is pressed, and again every time Pay Now is pressed after a change.

FieldTypeNotes
check_idstring, requiredSee Identifiers.
store_idstring, requiredSee Identifiers.
terminal_idstringLogged only.
customerobject{ "customer_id": "<scanned code>" }. Omit when no code was scanned.
codesstring[]Coupon or promo codes the cashier entered or scanned. At most 50.
linesarray, requiredSee section 5.

Example: a combo with a large drink, and a cookie. The customer picked a free taters reward in the app, and the merchant runs $5 off checks of $12 or more.

{
  "check_id": "20261103-104202",
  "store_id": "1042",
  "terminal_id": "02",
  "customer": { "customer_id": "SPN6040123412341234" },
  "lines": [
    { "line_id": "101", "sku": "2PCCOMBO", "name": "2 Pc Combo", "price": 1149, "quantity": 1,
      "components": [
        { "component_id": "101-1", "sku": "2PC", "name": "2 Pc Chicken", "price": 899, "quantity": 1 },
        { "component_id": "101-2", "sku": "TATERS", "name": "Taters", "price": 399, "quantity": 1 },
        { "component_id": "101-3", "sku": "FOUNTAIN", "name": "Fountain Drink", "price": 279, "quantity": 1,
          "modifiers": [{ "id": "LRG", "name": "Large", "price": 50, "quantity": 1 }] }
      ] },
    { "line_id": "102", "sku": "COOKIE", "name": "Cookie", "price": 199, "quantity": 1 }
  ]
}

Response 200:

{
  "evaluation_id": "1042:20261103-104202",
  "check_id": "20261103-104202",
  "customer": {
    "customer_id": "2114003",
    "balances": [{ "currency_id": "loyalty", "balance": 4200 }]
  },
  "subtotal": 1398,
  "subtotal_discount": 791,
  "total": 607,
  "discounts": [
    { "pos_reference": "SPN-FREETATERS", "offer_id": "reward_free_taters", "name": "Free Taters (reward)",
      "amount": 291, "scope": "item", "line_id": "101", "component_id": "101-2", "quantity": 1 },
    { "pos_reference": "SPN-5OFF", "offer_id": "promo_5_off", "name": "$5 off $12",
      "amount": 500, "scope": "order",
      "allocations": [
        { "line_id": "101", "component_id": "101-1", "amount": 296 },
        { "line_id": "101", "component_id": "101-3", "amount": 114 },
        { "line_id": "102", "amount": 90 }
      ] }
  ],
  "offers": [
    { "offer_id": "reward_free_taters", "name": "Free Taters (reward)", "status": "applied",
      "source": { "type": "redemption", "vendor_perk_redemption_id": 501 },
      "pos_reference": "SPN-FREETATERS", "discount_total": 291 },
    { "offer_id": "promo_5_off", "name": "$5 off $12", "status": "applied",
      "source": { "type": "promotion_code" }, "pos_reference": "SPN-5OFF", "discount_total": 500 }
  ],
  "receipt_lines": ["Free Taters (reward) applied, you saved $2.91", "$5 off $12 applied, you saved $5.00"],
  "redemption_id": "ored_5a58c78e-fbb4-4674-8bc5-827d72884dea",
  "hold_expires_at": "2026-11-03T17:45:00.000Z"
}

7. Applying the discounts

Every Spoonity discount is applied to an item, so tax is calculated on the discounted item. There can be more than one Spoonity discount on a check.

How the POS represents the discount is up to you. One generic "Spoonity Discount" item, sold as a negative-priced modifier on the item it discounts, works for every Spoonity discount. You do not need an item per offer. pos_reference identifies the offer for close and for reporting.

8. POST /partners/pos/close

Send once the check is paid and closed, for every check, even when check failed, timed out or was never sent.

FieldTypeNotes
check_id, store_idrequiredSame as on check.
pos_transaction_idstring, requiredYour unique id for this closed transaction. A repeated close with the same id is answered without doing anything twice.
terminal_idstringLogged only.
closed_atISO 8601 with an offset (Z or +hh:mm)When the POS closed the check.
receipt.linesarray, requiredThe final lines, same shape and limits as on check.
receipt.discountsarray, requiredThe Spoonity discounts you applied: { pos_reference, amount, name?, line_id?, component_id? }. For an order discount, send one entry per allocation or one entry for the whole amount. Empty array if you applied none.
receipt.totalinteger cents, requiredThe receipt total before tax.

Example, for the check above:

{
  "check_id": "20261103-104202",
  "store_id": "1042",
  "pos_transaction_id": "1042-20261103-104202",
  "terminal_id": "02",
  "closed_at": "2026-11-03T12:36:10-05:00",
  "receipt": {
    "lines": [
      { "line_id": "101", "sku": "2PCCOMBO", "name": "2 Pc Combo", "price": 1149, "quantity": 1,
        "components": [
          { "component_id": "101-1", "sku": "2PC", "name": "2 Pc Chicken", "price": 899, "quantity": 1 },
          { "component_id": "101-2", "sku": "TATERS", "name": "Taters", "price": 399, "quantity": 1 },
          { "component_id": "101-3", "sku": "FOUNTAIN", "name": "Fountain Drink", "price": 279, "quantity": 1,
            "modifiers": [{ "id": "LRG", "name": "Large", "price": 50, "quantity": 1 }] }
        ] },
      { "line_id": "102", "sku": "COOKIE", "name": "Cookie", "price": 199, "quantity": 1 }
    ],
    "discounts": [
      { "pos_reference": "SPN-FREETATERS", "amount": 291, "line_id": "101", "component_id": "101-2" },
      { "pos_reference": "SPN-5OFF", "amount": 296, "line_id": "101", "component_id": "101-1" },
      { "pos_reference": "SPN-5OFF", "amount": 114, "line_id": "101", "component_id": "101-3" },
      { "pos_reference": "SPN-5OFF", "amount": 90, "line_id": "102" }
    ],
    "total": 607
  }
}

Response 200:

{ "committed": true, "redemption_id": "ored_5a58c78e-fbb4-4674-8bc5-827d72884dea", "evaluation_id": "1042:20261103-104202" }

Spoonity answers before it posts the sale to the loyalty program, so close stays fast. The POS does not need to wait for, or check, the loyalty side.

If what you applied differs from what check returned, Spoonity still commits (the money has already left the till) and reports the difference in a mismatch object with the expected and received references and totals.

committed: false is still a 200, so the POS never retries it. The reason says why:

reasonMeaning
NO_PENDING_EVALUATIONSpoonity never saw a check for this check, for example because the POS timed out and went ahead without a discount. No reward is used. The sale is still posted for points.
EVALUATION_EXPIREDThe 15-minute hold had ended when the check closed. The reward was not used.
EVALUATION_CANCELLEDThe check was voided before it closed.
EVALUATION_ROLLED_BACKThe transaction was already voided.

9. POST /partners/pos/void

Optional. Use it if the merchant's POS can cancel a check after check, or refund a sale that had Spoonity discounts. Partial refunds are not supported.

{ "check_id": "20261103-104202", "store_id": "1042", "pos_transaction_id": "1042-20261103-104202" }

Response 200:

{ "voided": true, "redemption_id": "ored_5a58c78e-fbb4-4674-8bc5-827d72884dea", "evaluation_id": "1042:20261103-104202", "state": "rolled_back" }

10. Repeating calls safely

11. Timeouts and failures

12. Errors

Error bodies are { "error": { "code": "...", "message": "..." } }.

StatuscodeWhenPOS action
400INVALID_REQUESTThe body failed validation (issues[] says where): a decimal amount, a duplicate line_id or component_id, an unknown field, a colon in check_id, modifiers on a combo line, a limit exceeded.Fix the request. Close without a discount.
401UNAUTHORIZEDMissing or unknown API key.Configuration problem. Close without a discount.
409CHECK_CLOSEDcheck for a check that already closed or was refunded.Use a new check_id for a new sale.
409CHECK_REOPEN_LIMITThe check was restarted too many times.Use a new check_id.
409OFFERS_RESERVEDAnother check is using the same reward at that moment. Rare.Send check once more, then close without a discount.
503OFFERS_UNAVAILABLESpoonity could not read the merchant's promotions or look up the customer.Close without a discount.
500INTERNALAn error on Spoonity's side.Close without a discount.

13. Testing

Spoonity provides a test merchant and its API key on https://panasonic.spoonity.com, with a test customer code and rewards set up for the scenarios below.

  1. Guest check with no code: check, then close.
  2. Code scanned, reward picked in the app: discount returned on the right item and applied, close commits.
  3. Reward on a part of a combo: discount lands on that part.
  4. Order discount: allocations applied to their items and add up.
  5. Cancel out of Pay Now, change the order, Pay Now again: the second check replaces the first.
  6. Spoonity unreachable: the check closes without a discount, and close is still sent.
  7. Modifiers with and without a charge.
  8. A POS discount (employee or manager) on a line next to a Spoonity discount.

14. Checklist

For AI coding assistants

This guide is also available as plain markdown at /docs/index.md, and as one file at /docs/llms-full.txt. An index for language models is at /llms.txt.