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.
This page explains the model and the things worth knowing before you write against it. Once you’re writing calls, the endpoint reference lists every parameter, schema and response, with a console to try them in.
The model
Section titled “The model”Inventory has two levels:
- A material is a product line, such as “18mm Oak MFC”. It carries a
codethat 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 12Stock is always addressed under its material, so the relationship comes from the URL and is never something you have to send:
POST /ecommerce/api/materials/{materialId}/stockCredentials
Section titled “Credentials”You authenticate with your existing SmartCut API key, the same one the optimisation API uses.
- Sign in at smartcut.dev/account. You need an active e-commerce subscription.
- Copy your API key from the account page.
- Send it as an
Authorizationheader. The value is the raw key, with noBearerprefix.
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.
Creating a product line
Section titled “Creating a product line”A material needs a code and a name. Everything else is optional and inherited by its stock.
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.
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.
Inheritance
Section titled “Inheritance”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, and there is no flag to manage.
- Setting it back to
nullun-pins it, and the item resumes following its material.
# this 3050mm sheet costs more than the material defaultcurl -X PATCH .../stock/{id} -d '{ "cost": 49.99 }'
# changed our minds — follow the material againcurl -X PATCH .../stock/{id} -d '{ "cost": null }'Changing quantities
Section titled “Changing quantities”Use adjust, not PATCH, for stock movements:
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 the response 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, because their quantity isn’t tracked.
Syncing from an ERP
Section titled “Syncing from an ERP”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.
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.
When something is wrong
Section titled “When something is wrong”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", and creating it produces a stock item for each half.
Status codes
Section titled “Status codes”| Status | When |
|---|---|
400 |
Validation failed. See the errors array above. |
401 |
Missing or unrecognised API key. |
403 |
Your account has no e-commerce access, or is blocked. |
404 |
No such material or stock in your store. |
409 |
An adjust would take quantity below zero. |
429 |
Rate limited. Wait for Retry-After seconds. |
404 also covers ids that exist in a different store. You’ll never get a 403 for those, so the response can’t be used to work out whether an id exists somewhere else. If you’re sure an id is right and still get 404, check you’re using the key for the store that owns it.
Warnings
Section titled “Warnings”A PATCH can succeed and still return a warnings array. Those are pre-existing problems on fields you didn’t touch, such as a row that predates a validation rule. Your change was applied. The warnings tell you the record has other issues without blocking you from fixing them one at a time.
Rate limits
Section titled “Rate limits”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.
See also
Section titled “See also”- Endpoint reference documents the same endpoints as this page, one at a time: every parameter, schema and response, with a console to try them in. It also covers the order-workflow endpoints, which this guide doesn’t. Read this page to understand the model, then go there for the exact shape of a call.
- Store config MCP does the same operations from an LLM client, if you’d rather ask than integrate.
- Hosted store covers the storefront, analytics and webhooks.