Inventory reference
Base URL: https://api.inventory.destesi.io
Authentication
Section titled “Authentication”Three ways in.
Browser session. Signing in through SSO sets a host-only session cookie on the API host. This is what the web app uses; send it with credentials included.
Personal Access Token. For the CLI, scripts and MCP, send an identity PAT plus the workspace slug:
Authorization: Bearer idn_pat_…X-Destesi-Workspace: acme-coThe PAT acts as its user, with that user’s workspace role and Inventory product roles. See CLI & API auth.
Integration credential. For another system, an administrator creates an integration in Settings → Connected applications and stores its one-time credential in that system’s secrets:
Authorization: Bearer inv_key_…The credential is bound to one workspace, so workspace headers are ignored. It
can run only the operations in its scopes, on the stock owners in its
owner_ids. Inventory stores only a hash of it; a lost credential is replaced,
never shown again. get_context returns the workspace, integration id, scopes
and owners it resolved to — compare it before storing a binding.
The operation contract
Section titled “The operation contract”Inventory’s API is one list of named operations, published at runtime:
| Method | Path | Purpose |
|---|---|---|
GET |
/v1/capabilities |
Version, supported and unsupported capabilities, and every operation with its input schema |
GET |
/v1/openapi.json |
The same operations as an OpenAPI 3.1 document |
GET |
/v1/query/{operation}?input={json} |
A list_ or get_ read. input is URL-encoded JSON |
POST |
/v1/query/{operation} |
The other reads: preview_import, preview_catalog_import, read_catalog_file |
POST |
/v1/commands/{operation} |
Every write |
Input rules, the same on every surface:
- The input is one JSON object. An unknown field or a
nullvalue is refused with400 invalid_input. - Quantities are decimal strings (
"2.5") interpreted in the item’s base unit unlessunitnames another. - Every command needs an
operation_keyof 1–200 characters. The same key with the same input returns the stored answer; with different input it is a409 conflict. Keys are scoped to the caller. - The body is limited to 1 MiB; 8 MiB for the catalog import operations and
36 MiB for
upload_catalog_file.
capabilities in the response lists: stock, units, owners,
conditions, reservations, partial_fulfillment, location_lifecycle,
linked_reversals, physical_returns, authenticated_context, changes,
catalog_import, codes, labels, item_images, file_attachments and
public_file_import. unsupported lists lots, serials,
expiry_tracking and transit.
Operations
Section titled “Operations”Who is what an unrestricted workspace member needs. Member means any member; admin means owner or admin. A permission in backticks is also what an integration scope or a product role must hold. A member with Inventory product roles may run reads plus exactly the permissions their roles hold; see Roles and permissions.
Catalog and codes
Section titled “Catalog and codes”| Operation | Kind | Who | Notes |
|---|---|---|---|
list_items |
read | member | Includes each item’s low_stock_at |
get_item |
read | member | Archived items too |
get_item_trace |
read | member | Every movement, code change and the creation, oldest first, with the balance after each |
create_item |
command | admin · items |
sku, name, base_unit, scale required; optional barcode is attached as a code |
update_item |
command | admin · items |
Rename, archive, metadata. Base unit, scale and conversions never change |
preview_catalog_import |
read (POST) | admin · items |
1–500 rows; returns per-row actions and a preview_hash |
apply_catalog_import |
command | admin · items |
The exact reviewed rows plus preview_hash. Never touches stock |
upload_catalog_file |
command | admin · items |
Base64 bytes into workspace Drive, up to 25 MiB |
fetch_catalog_file |
command | admin · items |
A public http(s) URL into Drive |
read_catalog_file |
read (POST) | admin · items |
CSV, Excel, JSON or PDF into reviewable rows. PDF extraction uses the suite’s AI model |
extract_catalog_image |
command | admin · items |
One embedded photo into Drive |
get_catalog_image |
read | member | A short-lived preview URL for a workspace image |
list_codes |
read | member | Filter by item_id or location_id |
get_code |
read | member | Unknown code answers found: false, not 404 |
get_labels |
read | member | SVG labels for 1–100 existing codes |
add_code |
command | admin · items |
Attach an existing printed code |
generate_code |
command | admin · items |
Mint code128 or ean13 |
remove_code |
command | admin · items |
History and stock untouched |
Item fields: sku, name, base_unit, scale (0–6), unit_conversions,
description (≤ 8000), source_reference (≤ 200), group (≤ 200),
attributes (≤ 8 pairs), listed_quantity, image_drive_file_ids (≤ 10).
On update an empty string, object or array clears a field; omitting it leaves
it alone.
Locations and owners
Section titled “Locations and owners”| Operation | Kind | Who | Notes |
|---|---|---|---|
list_locations |
read | member | |
create_location |
command | admin | |
update_location |
command | admin | Rename, archive, reactivate. Archive refused while holds or allocations remain |
list_owners |
read | member | Workspace-owned stock is the owner with id "" |
create_owner |
command | admin |
Stock and movements
Section titled “Stock and movements”| Operation | Kind | Who | Notes |
|---|---|---|---|
list_positions |
read | member | Filter by item_id, location_id |
list_movements |
read | member | item_id, kinds, after, limit (1–500, default 100), order (asc default, desc) |
get_movement |
read | member | |
record_movement |
command | by kind, below | |
reverse_movement |
command | admin · reverse |
id, quantity, reason (≤ 200); for an issue or fulfill, optional return_location_id and return_condition (default quarantine) |
get_issue_target |
read | member · issue |
Validates an item/location/owner mapping and unit without revealing balances |
preview_import |
read (POST) | admin | Validates up to 500 CSV opening-stock rows |
apply_import |
command | — | Refused to people (422 scan_required) and not grantable to integrations |
record_movement needs the permission named by its kind:
kind |
Needs | Extra fields |
|---|---|---|
receive |
member · receive |
source_reference: "scan:<code>" from a person; optional label_serials |
issue |
member · issue |
|
transfer |
member · transfer |
to_location_id |
resize |
member · transfer |
to_item_id; method (scan/manual), code, to_code |
adjust |
admin · adjust |
Signed quantity, required reason; upward needs a scan from a person |
owner |
admin · owner |
to_owner_id |
condition |
admin · condition |
to_condition |
Every kind takes item_id, location_id, quantity, and optionally
owner_id, condition (default usable), unit, reason and
source_reference.
Outbound orders
Section titled “Outbound orders”| Operation | Kind | Who | Notes |
|---|---|---|---|
list_reservations |
read | member | Integrations see only their own |
get_reservation |
read | member | Lines carry waiting, claimed, claimed_by |
create_reservation |
command | member · reserve |
expires_at (RFC 3339, future), 1–100 lines, optional source_reference |
confirm_reservation |
command | member · reserve |
Only a live hold. Integrations may send committed_at |
release_reservation |
command | member · reserve |
Optional lines of {id, quantity}; omit for everything left |
fulfill_reservation |
command | member · fulfill |
Optional lines of {id, quantity, method, code} |
dispatch_reservation |
command | member · dispatch |
Fulfilled or closed only; idempotent; moves no stock |
A reservation created by an integration is confirmed, released and dispatched
only by that integration (409 integration_owned otherwise).
| Operation | Kind | Who | Notes |
|---|---|---|---|
get_snapshot |
read | member | Items, locations, owners, positions, reservations, settings and a cursor; publication_prices when a Shopify store is linked |
list_changes |
read | member | after cursor, limit 1–500 (default 100). Returns changes and the next cursor |
get_context |
read | anyone | Open to every valid integration, whatever its scopes |
Settings, alerts and integrations
Section titled “Settings, alerts and integrations”| Operation | Kind | Who | Notes |
|---|---|---|---|
get_settings |
read | member | |
update_settings |
command | admin | Send any of the fields below |
list_alert_contacts |
read | admin | Linked Telegram chats |
update_alert_contact |
command | admin | label, categories; an empty list mutes |
remove_alert_contact |
command | admin | |
list_integrations |
read | admin | |
create_integration |
command | admin | name, scopes, optional owner_ids. The credential is in this one response |
revoke_integration |
command | admin | Suite-managed integrations answer 409 integration_managed |
Settings fields:
| Field | Values |
|---|---|
low_stock_threshold |
0–1000000 available units, default 5 |
low_stock_mode |
fixed or batch |
low_stock_batch_percent |
1–100, default 25; batch mode only |
store_category_kind |
ropa, ropa_dama, ropa_caballero, ropa_nino, calzado, otro, or empty. Used for sizes when Commerce has no category |
routines |
Up to 20 scheduled prompts for the assistant, replaced as a whole list. Inventory stores them; inventory-api does not run them |
Shopify
Section titled “Shopify”All administrator-only, and never available to an integration. Connect must be
wired to the deployment (503 connect_unavailable otherwise).
| Operation | Kind | Notes |
|---|---|---|
list_shopify_stores |
read | Stores connected in Connect, and which one is linked |
get_catalog_source |
read | The linked store, or null |
link_shopify_store |
command | account_id of a connected, active store |
unlink_shopify_store |
command | Loaded items stay; the Connect connection stays |
get_shopify_import_preview |
read | What a load would do; writes nothing. include_drafts optional |
apply_shopify_import |
command | Loads a page within a time budget; call again with next_cursor |
get_shopify_push_status |
read | Publishing on or off, paired locations, pending, failing and drifted items |
enable_shopify_push / disable_shopify_push |
command | Publishing off leaves existing Shopify products untouched |
map_shopify_location |
command | location_id, shopify_location_id; empty unpairs |
set_shopify_price |
command | item_id, price as a decimal string; empty clears |
publish_shopify_stock |
command | Quantity only, for item_ids already in Shopify; works with publishing off |
sync_shopify |
command | Publish everything pending now |
Other endpoints
Section titled “Other endpoints”| Method | Path | Purpose |
|---|---|---|
GET |
/healthz |
Liveness. Public |
GET |
/readyz |
Database and schema reachable. Public |
GET |
/v1/sso/callback |
SSO landing; sets the session cookie. Public |
GET |
/v1/auth/me |
The signed-in user, role, and Inventory permissions |
POST |
/v1/auth/logout |
Clears the session. Idempotent |
GET |
/v1/me/products |
Launcher entitlements, proxied from identity |
POST |
/v1/catalog/files |
Multipart upload (file) of a catalog document or photo into Drive |
GET |
/v1/catalog/market |
The shop category that decides sizes: Commerce’s, else Inventory’s own |
POST |
/v1/chat |
The assistant. Streams; pauses on an approval-gated operation |
POST |
/v1/chat/resume |
Approve or reject the held operation (tool_use_id, approved) |
POST |
/v1/alerts/telegram/pairing |
Admin. Mints the one-device Telegram link |
GET |
/v1/alerts/preview |
Sample alert texts per category and language |
PUT /v1/settings answers 501 use_configuration_commands; use
update_settings.
Vocabularies
Section titled “Vocabularies”| Field | Values |
|---|---|
| Condition | usable, quarantine, damaged |
| Movement kind | receive, issue, transfer, resize, adjust, owner, condition, fulfill, reversal, return |
| Reservation status | held, allocated, partially_fulfilled, fulfilled, closed, released, expired |
| Pick method | scan (with code), manual |
| Standard units | pc, each · g, kg · ml, l · cm, m |
| Code kind | gtin, internal, supplier, location |
| Generated code | code128: INV- (item) or LOC- (location) plus 8 characters; ean13: GS1 prefix 2 |
| Integration scope | read, items, receive, issue, transfer, adjust, reserve, fulfill, dispatch, reverse, owner, condition |
| Alert category | low_stock, outbound, adjustments |
A position’s quantities: on_hand, held, allocated, available
(usable only, never below zero), blocked (the non-usable part) and
backordered (promised beyond what is on hand).
A reservation line’s quantities: quantity, held, allocated,
fulfilled, released, and when relevant waiting, claimed and
claimed_by. A dispatched reservation carries dispatched_at,
dispatched_by and dispatched_via.
Error codes
Section titled “Error codes”Operation errors are {"error": "code", "detail": "…"}; a
permission_required body also names the permission.
| Code | Status | Meaning |
|---|---|---|
missing_session |
401 | No cookie and no bearer |
unknown_bearer_scheme |
401 | Bearer is neither an identity PAT nor an integration credential |
missing_workspace_header |
401 | PAT sent without X-Destesi-Workspace |
invalid_integration |
401 | Unknown or revoked inv_key_ credential |
admin_required |
403 | Needs a workspace owner or admin |
permission_required |
403 | Your Inventory product roles do not grant this permission |
forbidden_scope |
403 | The integration’s scopes or owners do not cover this |
csrf_rejected |
403 | Cookie mutation from an untrusted origin |
not_found |
404 | Unknown id — or one outside your scope or workspace |
unknown_operation |
404 | No such operation |
approval_missing |
404 | No pending assistant approval with that id |
invalid_input |
400 | Malformed input, unknown field, null, bad quantity or unit |
conflict |
409 | State conflict or reused operation_key; detail says which |
insufficient_stock |
409 | A line cannot be picked beyond what is on hand |
integration_owned |
409 | Only the integration that created the order may do this |
integration_managed |
409 | A suite app’s integration cannot be revoked |
item_archived / location_archived |
409 | New reservations refuse archived items and locations |
label_already_received |
409 | A label serial was already received |
no_catalog_source |
409 | Link a Shopify store first |
store_not_connected / store_not_active |
409 | The store is not connected, or not active, in Connect |
push_disabled |
409 | Turn on publishing to Shopify first |
no_location_mapping |
409 | Pair an Inventory location with a Shopify location first |
listed_quantity_needs_one_location |
409 | Items with a listed quantity need exactly one paired location |
scan_required |
422 | Stock enters only by scanning a code of the item |
quota_exceeded |
429 | The plan’s item limit is reached |
use_configuration_commands |
501 | Legacy settings write |
shopify_error |
502 | Shopify answered with an error |
identity_unavailable |
502 | Identity could not be reached |
store_unavailable |
503 | No database configured for this deployment |
chat_disabled |
503 | No AI model selected for Inventory |
connect_unavailable |
503 | Connect is not wired to this deployment |
uploads_disabled |
503 | Drive is not wired to this deployment |
market_unavailable |
503 | Commerce did not answer with the shop category; retry the size change |
authentication_unavailable |
503 | The integration credential could not be checked |
Quota refusals use the suite’s quota envelope rather than the flat one above. See Entitlements.
Quotas
Section titled “Quotas”| Capability | Counts |
|---|---|
max_items |
Items that are not archived, checked on create_item and apply_catalog_import |
If the plan cannot be read, Inventory admits the write: a stock ledger does not stop because billing is unreachable. A suspended workspace is refused.
MCP tools
Section titled “MCP tools”Available on Destesi’s hosted MCP server, and on any mcp binary you run with
INVENTORY_API_URL set. See Inventory MCP tools.
Every tool takes a workspace slug.
| Tool | Purpose |
|---|---|
inventory_capabilities |
The live operation list and input schemas |
inventory_query |
Run a read: list_, get_, preview_import, preview_catalog_import, read_catalog_file |
inventory_command |
Run a write, with its operation_key |
inventory_command refuses create_integration: a credential must never land
in a model transcript.
dst inventory needs an identity PAT and a workspace. See CLI for
install and login.
dst inventory capabilities # operation names and schemasdst inventory openapi # the OpenAPI document
dst inventory list_itemsdst inventory get_code --input '{"code":"7701234567890"}'dst inventory list_movements --input '{"order":"desc","limit":20}'
dst inventory record_movement --input '{ "operation_key": "move-2026-09-30-001", "kind": "transfer", "item_id": "…", "location_id": "…", "to_location_id": "…", "quantity": "3"}'
echo '{"operation_key":"rel-1","id":"…"}' | dst inventory release_reservation --input -
dst inventory upload_catalog_file --file catalog.xlsx \ --input '{"operation_key":"upload-catalog-1"}'The operation is the only argument; --input takes a JSON object or - for
stdin (up to 8 MiB). --file works only with upload_catalog_file, for a
regular file up to 25 MiB. Output is the JSON response. Pass --workspace to
override the active workspace.
The base URL is derived from your configured API URL and can be overridden with
--inventory-api-url or DESTESI_INVENTORY_API_URL.