Morffeus Docs
morffeus.com

Stock

Stock in Morffeus is a quantity per product variant per warehouse. Your ERP or warehouse system stays the source of truth. Send changes with PUT /int/stocks, or set a warehouse to the quantities you counted with POST /stock/reconcile. Reads give you the numbers back per stock row, or summed across warehouses and stores.

How rows are matched#

You never send our ids. Every row names the variant and the warehouse the way your system knows them, and we resolve them:

Each row succeeds or fails on its own. A row that names a variant or a warehouse we cannot find, or that would take a quantity below its minimum, comes back in failed with the reason. The other rows are written. The response is 200 either way, so always read failed.

The stock object#

What PUT /int/stocks returns for every row it wrote. One object per variant per warehouse.

FieldDescription
idintegerOur id of the stock row.
product_variant_idintegerOur id of the variant.
product_idintegerOur id of the product the variant belongs to.
warehouse_idintegerOur id of the warehouse. The warehouse_code you sent is on the warehouse object.
quantitynumberQuantity on hand in the variant's unit. Decimals are allowed.
min_qtynumber · default 0The lowest quantity this row can reach. A change that would take quantity below it fails, so with the default 0 stock cannot go negative.
track_stockbooleantrue for rows created through this API.
app_idintegerYour app.
updated_by_user_idinteger · nullableThe user behind the last change, when we know it. null after a change through PUT /int/stocks.
inserted_at, updated_atdatetime · ISO 8601, UTCWhen the row was created and last changed. Sent without an offset, for example 2026-09-30T09:41:12.

Update stock levels#

PUT /api/v2/int/stocks

Changes the stock of one or many variants in one or many warehouses. A row for a variant that has no stock in that warehouse yet creates the stock row with the quantity you send. A row for an existing stock row adds the quantity you send to it. Rows you leave out are untouched.

The quantity is added, not set. Sending 5 for a row that holds 40 leaves 45, and sending the same batch twice counts it twice. To send the totals your system holds, use Reconcile a warehouse, or send the difference from what you sent last.

The stock reads, GET /stocks and GET /stocks/aggregate, show the change at once. Product listings, and the storefront built on them, read stock from a search cache that picks the change up on its next scheduled refresh. No stock movement is recorded; when you need the history, book stock movements instead.

Body parameters

ParameterDescription
stocksrequiredarray of objectsOne entry per variant per warehouse.
stocks[].codeone ofstringVariant code, the same value as code on the product variant.
stocks[].barcodeone ofstringA barcode stored on the variant.
stocks[].external_refone ofstringYour own id for the variant, as sent with the product.
stocks[].warehouse_coderequiredstringCode of the warehouse. A code we cannot find fails the row.
stocks[].quantityrequirednumberFor a new stock row, its quantity. For an existing row, the amount to add; send a negative number to take stock away.
stocks[].min_qtynumber · default 0Minimum for a new stock row. Ignored when the row exists.

Returns

data.success: the stock objects written. data.failed: one entry per row that was not written, with the row you sent in input and the reason in error.message, for example product variant or warehouse not found. Neither list keeps the order you sent, so match failures by input.

Errors

StatusWhen
200Also when some or all rows failed. They are in data.failed.
400The body has no stocks array.
401Missing or revoked token. The body names the token field.
PUT/int/stocks
curl -X PUT "https://api.morffeus.com/api/v2/int/stocks" \
  -H "Authorization: Bearer $MORFFEUS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "stocks": [
      { "code": "SKU-1001", "warehouse_code": "WH-NS", "quantity": 42, "min_qty": 5 },
      { "barcode": "8600123456789", "warehouse_code": "WH-BG", "quantity": -3 },
      { "external_ref": "ERP-778", "warehouse_code": "WH-NS", "quantity": 12.5 }
    ]
  }'
const res = await fetch('https://api.morffeus.com/api/v2/int/stocks', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${process.env.MORFFEUS_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    stocks: [
      { code: 'SKU-1001', warehouse_code: 'WH-NS', quantity: 42, min_qty: 5 },
      { barcode: '8600123456789', warehouse_code: 'WH-BG', quantity: -3 },
      { external_ref: 'ERP-778', warehouse_code: 'WH-NS', quantity: 12.5 },
    ],
  }),
});
const { data } = await res.json();
if (data.failed.length) console.warn(data.failed);
Response200 OK
{
  "data": {
    "success": [
      {
        "id": 5102,
        "product_variant_id": 91388,
        "product_id": 48377,
        "warehouse_id": 17,
        "quantity": 20.5,
        "min_qty": 0.0,
        "track_stock": true,
        "app_id": 12,
        "updated_by_user_id": null,
        "inserted_at": "2026-08-14T07:02:55",
        "updated_at": "2026-09-30T09:41:12"
      },
      { "id": 5101, "product_variant_id": 91120, "quantity": 42.0, "min_qty": 5.0, … }
    ],
    "failed": [
      {
        "input": { "barcode": "8600123456789", "warehouse_code": "WH-BG", "quantity": -3 },
        "error": { "message": "product variant or warehouse not found" }
      }
    ]
  }
}
Error401 Unauthorized
{
  "errors": {
    "token": "API.Autorization.Unauthorized"
  }
}

List stock#

GET /api/v2/stocks

Stock rows of the app, one per variant per warehouse, with the product's detail fields attached. Use it to check a sync, not to poll: read stock movements when you need what changed since a point in time.

Query parameters

ParameterDescription
warehouseIdintegerOnly rows of this warehouse. A warehouse of another app gives 400.
productIdintegerOnly rows of this product.
productVariantIdintegerOnly rows of this variant.
showPerWarehouseflagAdds warehouse_code to every row. Any value turns it on, false included, so leave it out when you do not want it.
offsetinteger · default 0Rows to skip and rows to return. See pagination.
limitinteger · default 30

Returns

data: a list of rows, ordered by product, each with id (the stock row), product_id, quantity, min_qty, measuring_unit_short (the unit's short name in your token's language), warehouse_code when you ask for it, and the product's detail fields as your app defines them. There is no page count or total: ask for the next page until you get fewer rows than limit.

GET/stocks
curl "https://api.morffeus.com/api/v2/stocks?warehouseId=17&showPerWarehouse=true&limit=2" \
  -H "Authorization: Bearer $MORFFEUS_TOKEN"
Response200 OK
{
  "data": [
    { "id": 5101, "product_id": 48213, "warehouse_code": "WH-NS", "quantity": 42.0, "min_qty": 5.0, "measuring_unit_short": "pcs", "name": "Espresso beans 1 kg" },
    { "id": 5102, "product_id": 48377, "warehouse_code": "WH-NS", "quantity": 20.5, "min_qty": 0.0, "measuring_unit_short": "kg", "name": "House blend, loose" }
  ]
}

Totals across warehouses#

GET /api/v2/stocks/aggregate

On-hand, reserved and available quantities for one variant, or for every active variant of a product, summed over the app's warehouses and split per variant and per store. Reserved is what checkouts in progress and stock write-offs hold. Available is on hand minus reserved, never below zero. This is the number to show when a product is not tied to a single store.

Query parameters

ParameterDescription
productVariantIdone ofintegerOne variant. Wins when you send both.
productIdone ofintegerEvery active variant of this product.

Returns

data.kpis: the totals, with the number of warehouses and stores that have stock rows for it. data.by_variant: the totals per variant, keyed by our variant id. data.app_locations: the totals per store, each with its warehouses. Without either parameter the call fails with 400.

Response200 OK
{
  "data": {
    "kpis": { "on_hand": 62.5, "reserved": 2.0, "available": 60.5, "warehouses": 2, "locations": 2 },
    "by_variant": {
      "91120": { "on_hand": 62.5, "reserved": 2.0, "available": 60.5 }
    },
    "app_locations": [
      {
        "id": 31, "name": "Novi Sad centre",
        "on_hand": 42.0, "reserved": 2.0, "available": 40.0,
        "warehouses": [ { "id": 17, "name": "Novi Sad", "on_hand": 42.0, "reserved": 2.0, "available": 40.0 } ]
      },
      { "id": 32, "name": "Belgrade west", … }
    ]
  }
}
Error400 Bad Request
{
  "errors": {
    "stocks": ["Stocks.ProductVariantIdRequired"]
  }
}

Reconcile a warehouse#

POST /api/v2/stock/reconcile

Sets the variants you list in one warehouse to the quantities you counted. We compare each with what the warehouse holds and book the differences as one stock movement, so the change keeps a history and product listings update straight away. Use it after a stock count, or whenever your system sends totals rather than changes.

Variants you leave out keep their quantity. Reconcile only sets what you list. It matches variants by their code only, sent as sku. A known code with no stock in the warehouse gets a stock row first, starting at zero.

Body parameters

ParameterDescription
warehouse_coderequiredstringThe warehouse to reconcile.
productsrequiredarray of objectsOne entry per variant.
products[].skurequiredstringVariant code.
products[].quantityrequirednumberThe quantity you counted.
stock_movement_numberstringYour reference for the stock movement. We make up a 12-character one if you leave it out.
descriptionstring · default "Product stock adjustment"Description of the stock movement.

Returns

data.failed: the entries we could not apply, each with the entry you sent in product and the reason in error, for example Could not find any product or variant with SKU: SKU-9999. Entries whose quantity already matches are skipped. An empty list means every entry was applied.

Errors

StatusWhen
404No warehouse with this warehouse_code in your app.
422warehouse_code is not a string, or products is not a list.
POST/stock/reconcile
curl -X POST "https://api.morffeus.com/api/v2/stock/reconcile" \
  -H "Authorization: Bearer $MORFFEUS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "warehouse_code": "WH-NS",
    "stock_movement_number": "COUNT-2026-09",
    "products": [
      { "sku": "SKU-1001", "quantity": 40 },
      { "sku": "SKU-1002", "quantity": 7 },
      { "sku": "SKU-9999", "quantity": 3 }
    ]
  }'
Response200 OK
{
  "data": {
    "failed": [
      {
        "product": { "sku": "SKU-9999", "quantity": 3 },
        "error": "Could not find any product or variant with SKU: SKU-9999"
      }
    ]
  }
}
Last updated 30 September 2026 · API v2 Something wrong on this page?