Morffeus Docs
morffeus.com

Your first request

Three steps from an empty terminal to a read and a write: set your token, read your products, change a stock level. You need a token from User › Tokens in Admin and a terminal with curl.

Set your token#

Create a token in Admin under User › Tokens, as described in Authentication. Keep it in an environment variable, so it stays out of your code and out of the commands you share.

Every call goes to https://api.morffeus.com/api/v2 and carries the token in an Authorization: Bearer header. The samples on these pages read it from MORFFEUS_TOKEN.

Shell
export MORFFEUS_TOKEN="paste-your-token-here"

Read your products#

GET /api/v2/products

Your products the way your storefront sees them: priced for your token's market and currency, with texts in its language, and only active products. Unless your app is set to show everything, only products on stock are listed; send onlyOnStock=false to list every active product. You get 10 products unless you set limit.

Every response is JSON with the payload under data. Here data.products is the page you asked for, and product_attributes and product_data_values describe the filters a storefront can offer; a sync can ignore them. Add productListOnly=true and data is just the list of products. There is no page count or total; see Pagination & filtering.

The fields around id and code follow the product fields your app defines, so your products may carry more than the sample shows. Each variant has its own code, barcode and external_ref: the identifiers you match stock and orders by.

One identifier, one product. Send exactly one of code, barcode, slug or id and data is a single product object instead of a list, or {} when nothing matches.

GET/products
curl "https://api.morffeus.com/api/v2/products?limit=2" \
  -H "Authorization: Bearer $MORFFEUS_TOKEN"
const res = await fetch('https://api.morffeus.com/api/v2/products?limit=2', {
  headers: { Authorization: `Bearer ${process.env.MORFFEUS_TOKEN}` }
});
const { data } = await res.json();
console.log(data.products.map(p => p.code));
$ch = curl_init('https://api.morffeus.com/api/v2/products?limit=2');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'Authorization: Bearer ' . getenv('MORFFEUS_TOKEN'),
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$products = json_decode(curl_exec($ch), true)['data']['products'];
Response200 OK
{
  "data": {
    "products": [
      {
        "id": 48213,
        "code": "SKU-1001",
        "slug": "espresso-beans-1-kg",
        "status": 1,
        "measuring_unit": "kg",
        "variants": [
          {
            "id": 91120,
            "code": "SKU-1001",
            "barcode": "8600123456789",
            "external_ref": "ERP-778",
            "on_stock": true,
            "prices": [ { "price": 1890.0, "currency_id": 1, "market_id": 1, … } ],
            …
          }
        ],
        "prices": [ { "price": 1890.0, … } ],
        …
      },
      { "id": 48377, "code": "SKU-1002", … }
    ],
    "product_attributes": [ … ],
    "product_data_values": [ { "name": …, "type": …, "values": [ … ] } ]
  }
}

Change a stock level#

PUT /api/v2/int/stocks

Now a write. Take a variant code from the response above and the code of one of your warehouses from Admin, and add stock to it. The row names the variant and the warehouse the way your system knows them, so you never need our ids.

The answer is 200 with two lists: success holds the stock rows written, failed the rows that were not, each with the reason. Always read failed.

The quantity is added to what the warehouse holds. Run this twice and you add 5 twice. When your system sends totals, see Reconcile a warehouse.

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": 5 } ] }'
Response200 OK
{
  "data": {
    "success": [
      { "id": 5101, "product_variant_id": 91120, "warehouse_id": 17, "quantity": 47.0, "min_qty": 5.0, … }
    ],
    "failed": []
  }
}
Last updated 30 September 2026 · API v2 Something wrong on this page?