Pushing surplus from your POS
Push end-of-day surplus from your own POS or inventory system so it appears in the Karma app at a discount.
Surplus is food that would otherwise go to waste at the end of a service. By pushing it to Karma you sell it, reduce waste, and keep Karma's stock in sync with your own, all from your own system.
How it works
One helper endpoint does everything:
POST /api/v1/locations/{locationId}/helpers/surplus/add- Find or create the item. Pass an
externalId(your SKU or PLU) with the item details. Karma looks the item up byexternalIdat this location and creates it if it does not exist. - Set the sell window. Pass a
date(midnight until the store's closing time) or an explicitvalidFromandvalidUntil. - Upsert the surplus entry. If you already pushed this item for the day, the quantity is replaced. Pushing
quantity: 3at 15:00 means three are left, not three more.
You need an API key scoped to the location with permission to create surplus.
Quick example
curl -X POST "$COMMON_API/api/v1/locations/1234/helpers/surplus/add" \
-H "X-API-Key: $KARMA_API_KEY" -H 'Content-Type: application/json' \
-d '{
"item": {
"externalId": "sku-pasta-bolognese",
"title": "Pasta Bolognese",
"description": "Classic slow-cooked bolognese with tagliatelle",
"imageUrl": "https://your-cdn.example.com/pasta-bolognese.jpg",
"basePriceCents": 12900,
"category": "FOOD",
"allergens": ["GLUTEN", "MILK"]
},
"quantity": 5,
"discountPercentage": 50,
"date": "2026-09-24"
}'The first push for an item on a day returns 201; later pushes return 200.
{
"status": "success",
"data": {
"surplusEntry": {
"id": 88421,
"itemId": 56789,
"locationId": 1234,
"ministock": 5,
"validFrom": "2026-09-24T00:00:00.000Z",
"validUntil": "2026-09-24T21:00:00.000Z",
"discountPercentage": 50,
"overridePriceCents": null
},
"item": { "id": 56789, "created": true }
}
}Request reference
| Field | Required | Description |
|---|---|---|
itemId | either | An existing Karma item id. Mutually exclusive with item. |
item | either | Item definition for find-or-create. |
item.externalId | with item | Your stable product identifier. Used for the idempotent lookup. |
item.title | with item | Name shown to customers. |
item.basePriceCents | with item | Full, undiscounted price in cents. |
item.description, item.imageUrl | no | Description and a publicly reachable image URL. |
item.category | no | A category tag key configured for your account, e.g. FOOD, BAKERY, BEVERAGE. |
item.allergens | no | Allergen tag keys, e.g. GLUTEN, MILK, NUTS, EGG, FISH. |
quantity | yes | Units available. Replaces any existing quantity for this item and day. |
discountPercentage | no | Percentage off basePriceCents, up to 90. The floor is the location's surplus floor, 50 by default. Mutually exclusive with overridePriceCents. |
overridePriceCents | no | Fixed surplus price in cents instead of a percentage. |
note | no | Internal note, not shown to customers. |
date | either | YYYY-MM-DD. Sell window is midnight to closing time. |
validFrom, validUntil | either | Explicit sell window, date or datetime. |
Scenarios
End-of-day push. A job at 19:00 pushes everything above a threshold:
const today = new Date().toISOString().slice(0, 10)
for (const item of remaining.filter((i) => i.quantity >= 2)) {
await fetch(`${COMMON_API}/api/v1/locations/1234/helpers/surplus/add`, {
method: 'POST',
headers: { 'X-API-Key': KARMA_API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
item: { externalId: item.sku, title: item.name, basePriceCents: item.priceCents },
quantity: item.quantity,
discountPercentage: 50,
date: today,
}),
})
}Updating quantity during the day. You pushed 10 at open; 4 sold through Karma and 3 in store. Push quantity: 3 with the same externalId and date. Karma replaces the number.
An item you already know. Skip find-or-create and pass itemId with overridePriceCents and a note.
A lunch-only window. Pass validFrom: "2026-09-24" and validUntil: "2026-09-24T14:00:00.000Z" instead of date.
Bulk upload
For nightly syncs of many products, POST .../helpers/surplus/add-bulk takes up to 500 entries in items[], each with the same shape as a single request. Items are processed in order, and errors are per item: one bad entry does not abort the rest. The response is 207 with results[] and errors[]; when anything failed, status is "partial".
{
"status": "partial",
"data": {
"results": [{ "index": 0, "surplusId": 88421, "itemId": 56789, "itemCreated": true, "surplusCreated": true }],
"errors": [{ "index": 1, "error": "Provide either date or validFrom" }]
}
}Listing, updating and removing
GET .../helpers/surplus lists everything you have pushed with each entry's item.externalId and a computed status of upcoming, active or expired. Filter with from and to.
To change an entry, PUT /api/v1/locations/{locationId}/surplus/{id} with ministock (the new quantity) and optionally discountPercentage, overridePriceCents, note, validUntil. To remove it, DELETE the same path; it returns 204 and the item disappears from the app immediately. An entry whose quantity reaches zero through sales becomes inactive on its own; you only delete surplus you decide not to sell after all.
Good habits
- Keep
externalIdstable. It is the mapping between your product and Karma's item. A newexternalIdcreates a duplicate item. - Push quantities, not deltas. Always send the absolute number remaining.
- Subscribe to
surplus.addedif another system needs to know when surplus goes live. See the Surplus API events.