You are viewing a non-production environment — the API surface may differ from production.

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
  1. Find or create the item. Pass an externalId (your SKU or PLU) with the item details. Karma looks the item up by externalId at this location and creates it if it does not exist.
  2. Set the sell window. Pass a date (midnight until the store's closing time) or an explicit validFrom and validUntil.
  3. Upsert the surplus entry. If you already pushed this item for the day, the quantity is replaced. Pushing quantity: 3 at 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

FieldRequiredDescription
itemIdeitherAn existing Karma item id. Mutually exclusive with item.
itemeitherItem definition for find-or-create.
item.externalIdwith itemYour stable product identifier. Used for the idempotent lookup.
item.titlewith itemName shown to customers.
item.basePriceCentswith itemFull, undiscounted price in cents.
item.description, item.imageUrlnoDescription and a publicly reachable image URL.
item.categorynoA category tag key configured for your account, e.g. FOOD, BAKERY, BEVERAGE.
item.allergensnoAllergen tag keys, e.g. GLUTEN, MILK, NUTS, EGG, FISH.
quantityyesUnits available. Replaces any existing quantity for this item and day.
discountPercentagenoPercentage off basePriceCents, up to 90. The floor is the location's surplus floor, 50 by default. Mutually exclusive with overridePriceCents.
overridePriceCentsnoFixed surplus price in cents instead of a percentage.
notenoInternal note, not shown to customers.
dateeitherYYYY-MM-DD. Sell window is midnight to closing time.
validFrom, validUntileitherExplicit 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 externalId stable. It is the mapping between your product and Karma's item. A new externalId creates a duplicate item.
  • Push quantities, not deltas. Always send the absolute number remaining.
  • Subscribe to surplus.added if another system needs to know when surplus goes live. See the Surplus API events.

On this page