Skip to content

Inventory API

If you run a hosted SmartCut store, you can manage its inventory from your own systems — an ERP, a warehouse system, a stock-control spreadsheet, a nightly script. Everything the admin Inventory page does to materials and stock is available over HTTPS, through the same validated path, so nothing the API does can put your store into a state the admin couldn’t.

The full endpoint reference, with request/response schemas and a try-it console, is at /api-docs/ecommerce. This page explains the model and the things worth knowing before you write against it.

Inventory has two levels:

  • A material is a product line — “18mm Oak MFC”. It carries a code that is unique within your store, plus the defaults shared by everything beneath it: price, colour, grain, category, edge banding.
  • A stock item is one cuttable sheet or length of that material, with its own dimensions and quantity.

One material has many stock items — typically one per thickness, or per sheet size.

Material OAK-18 "18mm Oak MFC" cost 42.50
├── Stock 2440 × 1220 × 18 q 40
└── Stock 3050 × 1220 × 18 q 12

Stock is always addressed under its material, so the relationship comes from the URL and is never something you have to send:

Terminal window
POST /ecommerce/api/materials/{materialId}/stock

You authenticate with your existing SmartCut API key — the same one the optimisation API uses.

  1. Sign in at smartcut.dev/account. You need an active e-commerce subscription.
  2. Copy your API key from the account page.
  3. Send it as an Authorization header. The value is the raw key — no Bearer prefix.
Terminal window
curl https://api.smartcut.dev/ecommerce/api/materials \
-H 'Authorization: YOUR_API_KEY'

If your account belongs to more than one store, add x-smartcut-org: <slug-or-id> to say which. Without it, ambiguous requests return 409 and list your options rather than guessing.

A material needs a code and a name. Everything else is optional and inherited by its stock.

Terminal window
curl -X POST https://api.smartcut.dev/ecommerce/api/materials \
-H 'Authorization: YOUR_API_KEY' -H 'Content-Type: application/json' \
-d '{ "code": "OAK-18", "name": "18mm Oak MFC", "t": [18], "cost": 42.50 }'

Then add stock beneath it. Set ecommerce: true for anything you sell — those rows are validated more strictly and require a positive cost.

Terminal window
curl -X POST https://api.smartcut.dev/ecommerce/api/materials/{id}/stock \
-H 'Authorization: YOUR_API_KEY' -H 'Content-Type: application/json' \
-d '{ "l": 2440, "w": 1220, "t": 18, "q": 40, "cost": 42.50, "ecommerce": true }'

Send an array to create a batch — { "materials": [ … ] } or { "stock": [ … ] }. Batches are all or nothing: if any row fails, nothing is written and the response tells you which row index was at fault.

Stock inherits cost, grain, category, finish, edge banding and the other shared fields from its material. Set only what differs. Reads always return the values actually in effect, so you never have to resolve the hierarchy yourself.

Two rules worth knowing:

  • Setting an inherited field on a stock item pins it. That sheet keeps your value, and later edits to the material no longer move it. This is automatic — you don’t manage a flag.
  • Setting it to null un-pins it, and the item resumes following its material.
Terminal window
# this 3050mm sheet costs more than the material default
curl -X PATCH .../stock/{id} -d '{ "cost": 49.99 }'
# changed our minds — follow the material again
curl -X PATCH .../stock/{id} -d '{ "cost": null }'

Use adjust, not PATCH, for stock movements:

Terminal window
curl -X POST https://api.smartcut.dev/ecommerce/api/stock/{id}/adjust \
-H 'Authorization: YOUR_API_KEY' -H 'Content-Type: application/json' \
-d '{ "delta": -5, "reason": "goods out" }'

delta is relative — negative removes, positive adds. It’s a single atomic operation, so two systems adjusting the same item at the same moment can’t oversell it. A PATCH of q reads and writes separately and can lose a concurrent update.

Two behaviours to rely on:

  • The row is never deleted at zero. It stays at 0, so your next sync still finds it.
  • Going below zero is refused with 409 and reports how many are actually available, rather than silently clamping. If you see this, your stock model and SmartCut’s have diverged.

Items marked unlimitedQuantity return 200 with adjusted: false — their quantity isn’t tracked.

Use PUT /ecommerce/api/materials/by-code/{code} — it creates the material if it’s absent and updates it if it’s present, keyed on your own product code. The same request can be replayed safely, so a nightly job needs no state and never has to check existence first.

Terminal window
curl -X PUT https://api.smartcut.dev/ecommerce/api/materials/by-code/OAK-18 \
-H 'Authorization: YOUR_API_KEY' -H 'Content-Type: application/json' \
-d '{ "name": "18mm Oak MFC", "t": [18], "cost": 44.95 }'

It responds 201 when it created and 200 when it updated; the created flag tells you which. URL-encode codes containing spaces or slashes.

To find stock without tracking SmartCut ids, filter the flat list by material code: GET /ecommerce/api/stock?code=OAK-18.

Errors carry a machine-readable code and name the field. Unrecognised fields are rejected, not ignored — a misspelled key fails loudly instead of returning success without changing anything.

{
"error": "Validation failed",
"code": "VALIDATION_FAILED",
"errors": [
{ "field": "quantiy", "code": "UNKNOWN_FIELD",
"message": "Unknown field \"quantiy\" for stock. Accepted fields: cost, discount, …" }
]
}
code Meaning
UNKNOWN_FIELD Not a field on this resource. The message lists the accepted set.
READONLY_FIELD Real, but managed by SmartCut — id, area, and a stock item’s material name.
FORBIDDEN_FIELD Internal. Most often db_materialId — create stock under its material instead.
REQUIRED_FIELD / INVALID_VALUE Missing or out of range. The message names the value.
DUPLICATE_CODE Another material already uses this code. In a batch, reported against the row index.
INSUFFICIENT_QUANTITY An adjust would go below zero. Carries available.

Branch on code; the message text is for people and may be reworded.

A few things live on the material rather than on stock, and the error will say so: product code, the thickness list, and the form (sheet / linear / roll). A bonded board is one material thickness written "18,36"; creating it produces a stock item for each half.

Inventory calls get their own budget, well above general read traffic, so a catalogue sync of a few hundred rows is fine. Exceeding it returns 429 with a Retry-After header — wait that many seconds and continue.

  • Endpoint reference — every route, schema and response, with a try-it console.
  • Store config MCP — the same operations from an LLM client, if you’d rather ask than integrate.
  • Hosted store — the storefront, analytics, webhooks and order APIs.