Inventory
Read product stock owned by your Meykt organization, including your goods at an active fulfillment partner. This endpoint requires inventory:read. It does not book stock, announce deliveries or write to your ERP or Shopify.
#Read and synchronize inventory
GET /v1/inventory — requires inventory:read.
curl 'https://api.meykt.com/v1/inventory?cursor=0&limit=100' \
-H "Authorization: Bearer $MEYKT_API_KEY"Start with cursor=0 and an empty local inventory projection. Apply each returned change in order, keyed by inventory_id. Keep the returned next_cursor only after storing that page successfully. When has_more is true, request the next page immediately; otherwise save the cursor for your next poll. Use next_cursor, not the separate revision field, as your continuation value. Applying a change means setting that inventory entry to the returned state, not adding its physical amount to the previous amount. Commit your local page updates and cursor together; otherwise a crash can skip or repeat data incorrectly.
For a full rebuild, construct a separate empty projection and follow every page from 0 before replacing your established local view. An early page or an intermediate historical entry is not the complete current warehouse. Keep independent cursors for each API connection; changing only the key on the same connection does not create a new inventory stream.
{
"data": {
"environment": "live",
"observed_at": "2026-10-18T20:00:00Z",
"revision": "12",
"next_cursor": "15f5fbb9-336c-4ddf-b80c-b0b66cb2c46f:12",
"has_more": false,
"changes": [{
"revision": "12",
"inventory_id": "7c794b49-3342-41cb-90fd-292845e58269",
"removed": false,
"inventory": {
"id": "7c794b49-3342-41cb-90fd-292845e58269",
"article_id": "0d937679-9930-4b9d-8b59-2f4a06d3b770",
"external_article_ids": ["ERP-CUP-7"],
"sku": "CUP-7",
"name": "Example cup",
"warehouse": { "id": "15b6810a-d674-4d45-83b4-1ddcf98437fa", "name": "North warehouse", "origin": "partner" },
"unit": "Stück",
"physical": "20",
"reserved": "4",
"available": "13"
}
}]
}
}| Field | Meaning |
|---|---|
inventory_id, inventory.id | Stable opaque identity for one article/warehouse inventory scope. |
article_id | Your Meykt article reference. |
external_article_ids | Article references explicitly linked through this API connection. An empty list means no such reference is linked. Meykt's sku may differ from the ERP reference. |
warehouse.id, warehouse.origin | Opaque warehouse reference and own or partner. They do not grant access to a partner organization. |
physical | Physically recorded quantity, including goods not yet eligible for allocation. |
reserved | Quantity already reserved. |
available | Currently eligible free quantity, after reservations and other holds; receiving/clarification stock and blocked or inaccessible stock are excluded. |
revision on a change | Monotonic revision in this connection's inventory projection. |
Top-level revision | Latest observed projection revision; a paginated read may still be finishing an earlier range. |
observed_at | When this page was read and its access checked, not when a particular physical movement happened. |
Quantities and revisions are decimal strings. Quantities preserve up to six decimal places. Use decimal arithmetic; never round them through a binary floating-point or 32-bit integer type. The unit is the recorded base unit; the API performs no implicit unit conversion.
#Removal and lost access
A removal is a tombstone:
{ "revision": "13", "inventory_id": "7c794b49-3342-41cb-90fd-292845e58269", "removed": true, "inventory": null }Remove this inventory entry from your local authorized projection. A tombstone can follow a withdrawn product release or ended partner relationship; it does not claim that the physical goods disappeared. Old pages also withhold payloads that are no longer authorized.
A completely empty successful delta page normally means no new authorized changes in that range; retain entries you already have. An absent article is not confirmed zero stock: it may not have a stock setup or may be outside the current sharing scope. Do not turn a read failure into zero stock or an unchanged-success response. On failure, keep the previous cursor and retry.
#Consistency and limits
This stream reconciles observed states, including current permissions. It is not a journal of every receipt, pick or movement between polls. Intermediate states can be combined into the next observed state. Apply changes idempotently; retrying a page must not create another stock movement in your ERP.
The first page of a new poll reconciles stock and pins the upper revision for its continuation pages. Those pages finish that range without reconciling stock again. Quantity changes made meanwhile appear in a subsequent poll. Current access is checked on every page, so a withdrawn grant can redact even an older change in the pinned range. Starting again at cursor=0 replays the retained projection history; it is not a constant-size current-stock snapshot.
A cursor belongs to its API connection. Keep it opaque, do not edit it and do not reuse it for another connection. The page limit is 1–250, default 100. The current read supports up to 5,000 article/warehouse scopes for the connected owner; an oversized projection fails visibly instead of silently truncating stock.
Only the owner's product stock is exposed. A partner's own goods, other customers' goods and material purchase costs are not included. Inventory responses do not promise separate quality grades, serial-number allocation, expiry selection, virtual-bundle availability or future incoming stock.
#Test keys
A test key returns environment: "test", revision/cursor "0" and an empty changes array. It neither reads live inventory nor creates pretend warehouse quantities. Use this response to check authentication and response handling; it is not evidence that your live warehouse is empty.
For current order progress and actual shipment proof, see Shipping and progress.
#Frozen initial snapshot and bounded changes
POST /v1/inventory/snapshots with {"request_id":"11111111-1111-4111-8111-111111111111"} opens a frozen view using inventory:read. Keep that UUID when retrying after a lost response. The receipt contains snapshot_id, revision, cursor, delta_cursor, item_count and expires_at. A snapshot lasts one hour; up to ten can be active per connection. Expired request identities remain recoverable as expired for seven further days; use a new UUID to start a new snapshot.
GET /v1/inventory/snapshots/{snapshotId}?cursor=0&limit=100 reads its first page. Continue with next_cursor while has_more is true. Apply each entry by inventory_id, replacing its state or removing it when removed is true. Current access is checked on every page: the snapshot never restores withdrawn access to old stock. Build into a separate empty local view, then replace the previous view only after the last page.
GET /v1/inventory/changes?cursor=<delta_cursor> continues after the snapshot revision. This feed retains changes for seven days and returns retention_floor. A cursor that is too old returns 409 snapshot_required; start a new snapshot instead of assuming an empty warehouse. Changes remain replacements, not quantities to add.
GET /v1/inventory/sync reports the current synchronization revision, retention floor, pending changes and last reconciliation error for this connection. A lagging or failed reconciliation is not a confirmed zero quantity. All four operations use inventory:read and never book physical stock.
#Exact positions by condition
GET /v1/inventory/positions requires both inventory:read and conditions:read. Start with request_id=<UUID> and retain that UUID after a lost response. Continue with the returned snapshot_id and next_cursor, omitting request_id. cursor=0 starts a page sequence and limit accepts 1–250 (default 100). Provide exactly one of request_id and snapshot_id. Snapshots last one hour and contain at most 5000 positions; a limit or permission error is never an empty warehouse.
Capability discovery publishes these as two alternatives: opening requires request_id and permits only the initial cursor 0; reading requires snapshot_id. Both reject the other identity. For AI calls, use open_api_inventory_positions and read_api_inventory_positions: each publishes its own required arguments. The general reader also accepts inventory_positions; its field descriptions explain the identity choice and the same service validates every request.
Each position includes physical, reserved, exclusive_held, available and certified_available as exact decimal strings in unit piece. Availability also respects accessible storage and exclusive physical claims. condition distinguishes immutable code/grade/disposition/version from explicit unclassified legacy goods; legacy never claims certified quality. The original aggregate inventory contract keeps its published meaning and unit label.
Physical goods exclusively held as cover for a Shopify sales quota are included in exclusive_held and already deducted from available. Do not subtract them again in your ERP. On adoption by an actual order, cover becomes that order's ordinary reservation without releasing the same goods for a second sale. Expected-delivery cover remains future stock and never increases physical or available in this API. A Shopify sales quota is a separate sales offer; it is not this physical inventory snapshot.
source_scope_ref is stable across future lots of the same owner's physical article, operator and warehouse. condition_ref binds one exact immutable condition version. Do not automatically include a newly defined condition version in an external sales offer. Operator, warehouse and lot references are opaque and owner-bound. Every stored page checks current partner releases again.
This endpoint supplies complete position snapshots, not a movement journal or a condition-delta stream. Replace a local position view only after collecting all pages successfully. Refresh it after relevant notifications and periodic reconciliation; never interpret a missing failed page as zero stock. A frozen quantity is the observation at observed_at, not a promise that no later movement occurred.
#Close a completed position snapshot
POST /v1/inventory/positions/snapshots/{snapshotId}/close requires inventory:read and conditions:read. Send an empty JSON object {}. Close only after applying every page successfully, or when intentionally discarding that entire read attempt. It changes no stock, reservation or shipment. It immediately releases the active-snapshot slot and deletes only the temporary position rows.
The snapshot ID identifies the idempotent operation. The response is:
{
"data": {
"snapshot_id": "11111111-1111-4111-8111-111111111111",
"status": "closed",
"closed_at": "2026-10-19T12:10:00.000Z",
"replay_until": "2026-10-26T13:00:00.000Z"
}
}Retry the same close after a lost response. Its original closed_at remains unchanged. Further page reads and retries of the original snapshot-opening request_id return 409 inventory_snapshot_closed; they cannot recreate the closed capture during its replay window. Open a fresh capture with a new request UUID. There is no undo. Unknown snapshots and snapshots belonging to another connection receive the same unavailable response.
The original request identity and close receipt remain recoverable until replay_until, which is the original snapshot expiry plus seven days. Cleanup does not reset that deadline. After this bounded retention window, do not reuse the old opening UUID: create a new one. A close request itself never creates a snapshot, even after its receipt has expired.
For ongoing condition synchronization, coalesce inventory.positions_changed notifications and read one new complete position snapshot, apply every page, then close it. If changes arrive while reading, perform a subsequent refresh. Retain the previous complete local view after any failed or expired page. Keep normal page and rate limits; closing a snapshot frees its slot but does not raise those limits. The /v1/inventory/changes feed contains aggregate inventory changes and is not a condition-position delta feed.
The integration-profile operation is inventory.positions.close, with canonical input { "snapshot_id": "..." }. MCP and Meyki use close_api_inventory_positions under the same API-management and data-reading permissions as opening and reading positions. The tool defaults to a preview; dry_run:false closes the snapshot.
Machine-readable version of this page: https://www.meykt.com/developers/inventory.md