# Pushing surplus from your POS (/docs/surplus-from-pos)



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 [#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 [#quick-example]

```bash
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.

```json
{
  "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 [#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 [#scenarios]

**End-of-day push.** A job at 19:00 pushes everything above a threshold:

```typescript
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 [#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"`.

```json
{
  "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 [#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 [#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.
