Adjust stock#

This guide explains how stock is modelled in Afosto, how to read the current stock of a SKU, and which mutations change it — putting items back on stock, writing off items that turned out to be missing, reserving stock for a customer, and announcing goods that are on their way. It closes with the audit trail you use to verify that a change landed.

How stock is structured#

Stock in Afosto is not a single number you write to. It is derived from three layers:

  • Inventory item (InventoryItem) — one physical registration: a SKU, in a warehouse, at a position, with a quantity, a cost and a status (AVAILABLE, COMMITTED, RESERVED, UNAVAILABLE). Items with identical attributes are merged into the same line.
  • Inventory (Inventory) — the summary of those items grouped per SKU, warehouse and position. This is what you read when you want "how many do I have", split out over quantity_available, quantity_committed, quantity_reserved, quantity_unavailable and quantity_expected.
  • Stock mutation (StockMutation) — the audit record of every change that affected stock, with the source that caused it and the new_quantity that resulted from it.
⚠
Warning:

There is no setStock or updateStockLevel mutation. Stock is always the consequence of an operation: receiving a supply, reserving items, allocating them to an order, restocking a return. Pick the operation that matches what actually happened — the quantity follows. Stock that has no operation behind it (an opening balance, or the outcome of a physical count) is written straight to the inventory service over REST — see step 4.

Every change is labelled with a StockMutationSource:

SourceMeaning
REGISTERNew stock registered in a warehouse
ALLOCATIONStock committed to or released from an order
RESTOCKItems put back on stock after a cancellation or return
CORRECTIONA correction — a count difference, or an item that was missing while picking
EXTERNALA change pushed in by a connected external system
UNKNOWNSource could not be determined

Overview#

  1. Read the current stock with the inventory query
  2. Inspect the individual inventory items with inventoryItems or inventoryAtLocation
  3. Change the stock with the operation that matches what happened:
  4. Register or correct stock directly over REST when no operation sits behind the change
  5. Set the thresholds that drive reordering with createItemPreferences / updateItemPreferences
  6. Verify the result with the stockMutations query

All of these require a valid API key — see Authentication.


Step 1 — Read the current stock#

The inventory query returns the stock summary, grouped per SKU, warehouse and position. Filter it by sku, warehouse_id or business_id.

Arguments:

NameTypeRequiredDescription
sku
String
OptionalLimit the result to a single SKU.
warehouse_id
String
OptionalLimit the result to one warehouse.
business_id
String
OptionalLimit the result to the stock owned by one business.
filters
InventoryFilterInput▾
OptionalRicher filtering — `sku`, `warehouse_id`, `business_id`, `location_id`, `ids`, and `quantity` as an inequality filter.
first
Int
OptionalPage size.
after
String
OptionalCursor to continue from — see [Pagination](/docs/graphql/pagination).

Each node is an Inventory:

NameTypeRequiredDescription
id
String!
OptionalIdentifier of the grouped inventory line.
ids
[String!]!
OptionalThe IDs of the individual inventory items that make up this line.
sku
String!
OptionalThe SKU of the grouped items.
quantity
Int64!
OptionalTotal quantity on hand for this line.
quantity_available
Int64!
OptionalQuantity that can still be sold — not committed, reserved or unavailable.
quantity_committed
Int64!
OptionalQuantity allocated to an order and waiting to be picked.
quantity_reserved
Int64!
OptionalQuantity held by a reservation or a claim.
quantity_unavailable
Int64!
OptionalQuantity that is on hand but cannot be sold — damaged, quarantined, missing.
quantity_expected
Int64!
OptionalQuantity announced as incoming but not yet received.
warehouse
Warehouse
OptionalThe warehouse the line is held in.
position
Position
OptionalThe position within the warehouse.
cost
InventoryCost!▾
OptionalCost of the line.
query GetInventory($sku: String, $warehouseId: String, $first: Int!, $after: String!) {
  inventory(sku: $sku, warehouse_id: $warehouseId, first: $first, after: $after) {
    nodes {
      id
      ids
      sku
      quantity
      quantity_available
      quantity_committed
      quantity_reserved
      quantity_unavailable
      quantity_expected
      warehouse {
        id
        label
      }
      position {
        id
        type
      }
      cost {
        amount
        amount_average
        currency
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
{
  "sku": "SHIRT-BLUE-M",
  "warehouseId": "8b1f0e2c-3d4a-4b5c-9e6f-7a8b9c0d1e2f",
  "first": 25,
  "after": ""
}
ℹ
Tip:

quantity is everything on hand; quantity_available is what you can still sell. A drop in quantity_available without a drop in quantity means the goods are still there but have been committed or reserved.


Step 2 — Inspect the individual inventory items#

The summary tells you how much there is. To see which registrations it is made of — batch numbers, serial numbers, expiry dates, per-item status — use inventoryItems.

Arguments:

NameTypeRequiredDescription
filters
InventoryItemFilterInput!▾
RequiredAt least one of the filters below.
first
Int
OptionalPage size.
after
String
OptionalCursor to continue from.
query GetInventoryItems($filters: InventoryItemFilterInput!, $first: Int) {
  inventoryItems(filters: $filters, first: $first) {
    nodes {
      id
      sku
      quantity
      status
      reason
      batch_number
      serial_number
      expires_at
      created_at
      updated_at
      warehouse {
        id
        label
      }
      position {
        id
        type
      }
      cost {
        amount
        currency
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
{
  "filters": {
    "sku": ["SHIRT-BLUE-M"]
  },
  "first": 50
}

status is an InventoryItemState: AVAILABLE, COMMITTED, RESERVED or UNAVAILABLE. When an item is UNAVAILABLE, reason explains why.

To look at a single warehouse position instead — what a picker sees when they walk up to a shelf — use inventoryAtLocation:

query GetInventoryAtLocation($filter: InventoryAtLocationFilter!) {
  inventoryAtLocation(filter: $filter) {
    items {
      position
      quantity_on_hand
      quantity_available
      quantity_committed
      quantity_reserved
      batch_number
      serial_number
      expires_at
      product {
        sku
        label
      }
    }
  }
}
{
  "filter": {
    "location_id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c",
    "sku": "SHIRT-BLUE-M"
  }
}

Step 3 — Change the stock#

Which mutation you call depends on what happened to the goods.

What happenedMutationResulting source
A cancelled or returned item goes back on the shelfrestockOrderItemsRESTOCK
An item could not be found while pickingmarkItemsAsMissingCORRECTION
An item is temporarily not sellablemarkItemsAsUnavailableCORRECTION
The item turned up again at a known locationmarkItemsAsAvailableCORRECTION
Stock is held for a customer without an order yetcreateReservationALLOCATION
Goods are on their way from a suppliercreateAnnouncement → createSupplyREGISTER

A — Put order items back on stock#

restockOrderItems takes items off a cancelled or returned order and puts their physical stock back into the inventory, at the location and position you choose. Use it as the final step of a cancellation or a return.

Input: RestockOrderItemsInput!

NameTypeRequiredDescription
order_id
String!
RequiredID of the order the items belong to.
items
[RestockItemInput!]!▾
RequiredThe items to put back on stock.

Returns: RestockOrderItemsPayload

mutation RestockOrderItems($input: RestockOrderItemsInput!) {
  restockOrderItems(input: $input) {
    order {
      id
      number
      items {
        id
        sku
        quantity
      }
    }
  }
}
{
  "input": {
    "order_id": "72fca344-2a6f-4c3e-b4ca-029920b2522a",
    "items": [
      {
        "item_id": "5a6b7c8d-9e0f-1234-5678-9abcdef01234",
        "location_id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c",
        "position": "A-01-03"
      }
    ]
  }
}
·
Note:

Restocking only works for items that have been cancelled or returned. Restocking an item that is still on an open, unshipped order is rejected — deallocate it with deallocateOrderItems instead, which releases the commitment without moving stock.

B — Write off items that are missing#

When a picker cannot find an item, the recorded stock is higher than the physical stock. markItemsAsMissing records that difference: it takes the item off the order and writes a correction against the inventory.

Input: MarkItemsAsMissingInput!

NameTypeRequiredDescription
order_id
String!
RequiredID of the order the items belong to.
items
[MissingItemInput!]!▾
RequiredThe items that could not be found.

Returns: MarkItemsAsMissingPayload

mutation MarkItemsAsMissing($input: MarkItemsAsMissingInput!) {
  markItemsAsMissing(input: $input) {
    order {
      id
      number
      items {
        id
        sku
        quantity
      }
    }
  }
}
{
  "input": {
    "order_id": "72fca344-2a6f-4c3e-b4ca-029920b2522a",
    "items": [{ "item_id": "5a6b7c8d-9e0f-1234-5678-9abcdef01234" }]
  }
}

Two related mutations cover the rest of the picking corrections:

  • markItemsAsUnavailable (MarkItemsAsUnavailableInput!, takes order_id and items with an item_id) — the goods exist but cannot be sold right now, for instance because they are damaged. The quantity stays in quantity, but moves out of quantity_available.
  • markItemsAsAvailable (MarkItemsAvailableInput!, takes order_id and items with an item_id and a location_id) — the reverse: the item turned up at the given location and is sellable again.
mutation MarkItemsAsAvailable($input: MarkItemsAvailableInput!) {
  markItemsAsAvailable(input: $input) {
    order {
      id
      number
    }
  }
}
{
  "input": {
    "order_id": "72fca344-2a6f-4c3e-b4ca-029920b2522a",
    "items": [
      {
        "item_id": "5a6b7c8d-9e0f-1234-5678-9abcdef01234",
        "location_id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c"
      }
    ]
  }
}

C — Reserve stock for a customer#

A reservation holds quantity for a customer before there is an order — a click-and-collect hold, or goods set aside after a store visit. The reserved quantity leaves quantity_available and shows up in quantity_reserved, and the reservation expires automatically at expires_at.

Input: CreateReservationInput!

NameTypeRequiredDescription
business_id
String!
RequiredID of the business the reserved items are claimed for.
warehouse_id
String!
RequiredID of the warehouse the stock is held in.
channel_id
String!
RequiredID of the channel the reservation is created for.
customer
SetReservationContactInput!▾
RequiredThe customer the stock is held for.
items
[ReservationItemInput!]!▾
RequiredThe items to reserve.
expires_at
Int64!
RequiredUnix timestamp (milliseconds) at which the reservation releases the stock again.
notes
String
OptionalFree-form note on the reservation.
id
String
OptionalOptional ID to assign to the reservation.

Returns: CreateReservationPayload

mutation CreateReservation($input: CreateReservationInput!) {
  createReservation(input: $input) {
    reservation {
      id
      number
      expires_at
      items {
        quantity
        product {
          sku
          label
        }
      }
      claims {
        id
        quantity
        status
      }
    }
  }
}
{
  "input": {
    "business_id": "1a2b3c4d-5e6f-7890-abcd-ef1234567890",
    "warehouse_id": "8b1f0e2c-3d4a-4b5c-9e6f-7a8b9c0d1e2f",
    "channel_id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
    "customer": { "contact_id": "d4e5f6a7-b8c9-0123-def1-234567890123" },
    "items": [{ "sku": "SHIRT-BLUE-M", "quantity": 2 }],
    "expires_at": 1749081600000,
    "notes": "Held at the counter until Friday"
  }
}

Release the stock early with removeReservation:

mutation RemoveReservation($input: RemoveReservationInput!) {
  removeReservation(input: $input) {
    success
  }
}
{
  "input": {
    "id": "e5f6a7b8-c9d0-1234-ef12-345678901234"
  }
}

D — Announce incoming stock#

Goods that a supplier is sending count as quantity_expected until they are received. Announce them in three steps: create the announcement, add its items, then group one or more announcements into a supply that your warehouse receives against.

mutation CreateAnnouncement($input: CreateAnnouncementInput!) {
  createAnnouncement(input: $input) {
    announcement {
      id
      number
      expected_at
    }
  }
}
{
  "input": {
    "buyer_id": "1a2b3c4d-5e6f-7890-abcd-ef1234567890",
    "location_id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c",
    "from": { "entity_id": "f6a7b8c9-d0e1-2345-f123-456789012345", "entity_type": "SUPPLIER" },
    "expected_at": 1749081600000,
    "description": "PO-2026-0042"
  }
}

Then add the items you expect to receive:

mutation AddItemsToAnnouncement($input: AddItemsToAnnouncementInput!) {
  addItemsToAnnouncement(input: $input) {
    announcement {
      id
      number
    }
  }
}
{
  "input": {
    "announcement_id": "a7b8c9d0-e1f2-3456-1234-567890123456",
    "items": [{ "sku": "SHIRT-BLUE-M" }, { "sku": "SHIRT-BLUE-L" }]
  }
}

Finally, group the announcements into a supply. settings.processing decides what happens to the goods on arrival — REGISTER puts everything on stock, while the MATCH_* options cross-dock the goods straight onto waiting orders or purchases.

Input: CreateSupplyInput!

NameTypeRequiredDescription
announcement_ids
[String!]!
RequiredThe announcements to receive in this supply.
settings
SupplySettingsInput!▾
RequiredHow the received goods are processed.
location_id
String
OptionalLocation the supply is received at.
due_at
Int64
OptionalUnix timestamp (milliseconds) the supply is due.
id
String
OptionalOptional ID to assign to the supply.

Returns: CreateSupplyPayload

mutation CreateSupply($input: CreateSupplyInput!) {
  createSupply(input: $input) {
    supply {
      id
      number
      status
      due_at
      announcements {
        id
        number
      }
    }
  }
}
{
  "input": {
    "announcement_ids": ["a7b8c9d0-e1f2-3456-1234-567890123456"],
    "location_id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c",
    "due_at": 1749081600000,
    "settings": {
      "processing": "REGISTER"
    }
  }
}

If more arrives than was announced, book the difference on the supply with addOverage instead of creating a second announcement:

mutation AddOverage($input: AddOverageInput!) {
  addOverage(input: $input) {
    supply {
      id
      number
      overages {
        id
        sku
        position
      }
    }
  }
}
{
  "input": {
    "id": "b8c9d0e1-f2a3-4567-2345-678901234567",
    "item": {
      "id": "c9d0e1f2-a3b4-5678-3456-789012345678",
      "sku": "SHIRT-BLUE-M",
      "position": "A-01-03"
    }
  }
}

Step 4 — Register or correct stock directly#

Everything above changes stock as a side effect of an operation. When there is no operation — an opening balance for a new SKU, or the outcome of a physical count — you write to the inventory service directly. These three endpoints are REST, not GraphQL: there is no mutation that does this.

·
Note:

The query runner on this site only speaks GraphQL, so the examples below are curl. They use the same API key as the GraphQL endpoint — see Authentication.

POST   https://afosto.app/api/inventory/items
PATCH  https://afosto.app/api/inventory/items
DELETE https://afosto.app/api/inventory/items
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
⚠
Warning:

These endpoints take camelCase field names (warehouseId, batchNumber, serialNumber, expiresAt), while the GraphQL API uses snake_case. Reading stock through inventory and writing it through this endpoint means switching convention halfway.

Add stock#

POST /api/inventory/items registers new stock. Every posted item increases the quantity on a stock line — items with an identical set of attributes are merged into the same line, so posting the same SKU, warehouse, position and batch twice adds up rather than creating a second line.

Body: a data array of items.

NameTypeRequiredDescription
sku
string
RequiredThe SKU to register stock for.
warehouseId
string
RequiredID of the warehouse the stock is held in.
businessId
string
RequiredID of the business that owns the stock.
quantity
number
OptionalNumber of items to register.
state
string
Optional`AVAILABLE` or `UNAVAILABLE`. Defaults to available.
reason
string
Optional`BROKEN` or `NONE` — only meaningful when `state` is `UNAVAILABLE`.
position
string
OptionalPosition within the warehouse.
gtin
string[]
OptionalScannable barcodes for the item.
rfid
string
OptionalRFID value.
cost
object▾
OptionalCost price of the item.
batchNumber
string
OptionalBatch number.
serialNumber
string
OptionalSerial number.
weight
object▾
OptionalWeight of the item.
expiresAt
number
OptionalUnix timestamp (milliseconds) at which the item expires.
curl -X POST https://afosto.app/api/inventory/items \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "data": [
      {
        "sku": "SHIRT-BLUE-M",
        "warehouseId": "8b1f0e2c-3d4a-4b5c-9e6f-7a8b9c0d1e2f",
        "businessId": "1a2b3c4d-5e6f-7890-abcd-ef1234567890",
        "quantity": 24,
        "state": "AVAILABLE",
        "position": "A-01-03",
        "cost": { "amount": 850, "currency": "EUR" }
      }
    ]
  }'

A 201 returns the resulting stock lines:

{
  "data": [
    {
      "id": "e1f2a3b4-c5d6-7890-5678-901234567890",
      "sku": "SHIRT-BLUE-M",
      "quantity": 24,
      "state": "AVAILABLE",
      "position": "A-01-03",
      "cost": { "amount": 850, "currency": "EUR" }
    }
  ]
}

Correct stock#

PATCH /api/inventory/items updates existing stock lines, identified by id. This is how you book the outcome of a count: set quantity to what you actually counted.

Body: a data array of updates. Each entry needs an id; every other field is optional and replaces the current value.

NameTypeRequiredDescription
id
string
RequiredID of the stock line to update — from `inventoryItems` or the `ids` on an `Inventory` node.
quantity
number
OptionalThe corrected quantity.
state
string
Optional`AVAILABLE` or `UNAVAILABLE`.
reason
string
Optional`BROKEN` — only meaningful when `state` is `UNAVAILABLE`.
position
string
OptionalMove the line to another position.
gtin
string[]
OptionalScannable barcodes.
rfid
string
OptionalRFID value.
cost
object
OptionalCost price — `amount` in cents and `currency`.
batchNumber
string
OptionalBatch number.
serialNumber
string
OptionalSerial number.
weight
object
OptionalWeight — `amount` and `unit`.
expiresAt
number
OptionalUnix timestamp (milliseconds) of expiry.
curl -X PATCH https://afosto.app/api/inventory/items \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "data": [
      {
        "id": "e1f2a3b4-c5d6-7890-5678-901234567890",
        "quantity": 22
      }
    ]
  }'
ℹ
Tip:

Fetch the id first with the inventoryItems query from step 2. The ids field on an Inventory node lists the individual lines behind a grouped summary, which is what you need when a SKU sits at several positions.

Remove stock#

DELETE /api/inventory/items removes stock lines entirely. Select them with a filter on the query string — use this for a line that should not exist at all, rather than to set a quantity to zero.

NameTypeRequiredDescription
filter[id][eq]
string
OptionalRemove the single stock line with this ID.
filter[id][in]
string
OptionalRemove several lines at once — a comma-separated list of IDs.
curl -X DELETE "https://afosto.app/api/inventory/items?filter%5Bid%5D%5Beq%5D=e1f2a3b4-c5d6-7890-5678-901234567890" \
  -H "Authorization: Bearer YOUR_API_KEY"
⚠
Warning:

A write to these endpoints bypasses the order and supply flows — nothing is matched against waiting orders, and no announcement is settled. Reach for them only when there genuinely is no operation behind the change. If goods arrived from a supplier, announce them (step 3D); if a customer returned them, restock the order items (step 3A). The change still lands in the audit trail as a REGISTER or CORRECTION stock mutation either way.


Step 5 — Set the stock thresholds#

Stock preferences are the policy around a SKU, per warehouse and business: when to reorder, how much to keep, and whether the SKU may be sold when it runs out. They do not change the quantity — they change what the platform does with it.

Input: CreateItemPreferencesInput! — a list of ItemPreferenceInput.

NameTypeRequiredDescription
sku
String!
RequiredThe SKU the preference applies to.
warehouse_id
ID!
RequiredThe warehouse the preference applies to.
business_id
ID!
RequiredThe business the preference applies to.
minimum_quantity
Int64!
RequiredLowest stock level you want to hold.
optimal_quantity
Int64!
RequiredStock level a replenishment run aims for.
maximum_quantity
Int64!
RequiredHighest stock level you want to hold.
reorder_point
Int64!
RequiredLevel at which a reorder is triggered.
is_backorder_allowed
Boolean!
RequiredWhether the SKU can still be ordered once it is out of stock.
is_restricted_to_single_position
Boolean!
RequiredWhether this SKU may only be held at one position in the warehouse.
preorder_deadline_at
Int64
OptionalUnix timestamp (milliseconds) until which the SKU can be pre-ordered.
id
ID
OptionalOptional ID to assign to the preference.

Returns: CreateItemPreferencesPayload

mutation CreateItemPreferences($input: CreateItemPreferencesInput!) {
  createItemPreferences(input: $input) {
    preferences {
      id
      sku
      minimum_quantity
      optimal_quantity
      maximum_quantity
      reorder_point
      is_backorder_allowed
      warehouse {
        id
        label
      }
    }
  }
}
{
  "input": {
    "preferences": [
      {
        "sku": "SHIRT-BLUE-M",
        "warehouse_id": "8b1f0e2c-3d4a-4b5c-9e6f-7a8b9c0d1e2f",
        "business_id": "1a2b3c4d-5e6f-7890-abcd-ef1234567890",
        "minimum_quantity": 5,
        "optimal_quantity": 40,
        "maximum_quantity": 80,
        "reorder_point": 10,
        "is_backorder_allowed": false,
        "is_restricted_to_single_position": true
      }
    ]
  }
}

To change an existing preference, use updateItemPreferences. Its input is the same, except it is keyed on the preference id and takes no sku or business_id — read the current preferences first with the stockPreferences query:

query GetStockPreferences($sku: String, $warehouseId: String, $first: Int) {
  stockPreferences(sku: $sku, warehouse_id: $warehouseId, first: $first) {
    nodes {
      id
      sku
      minimum_quantity
      optimal_quantity
      maximum_quantity
      reorder_point
      is_backorder_allowed
      is_restricted_to_single_position
      preorder_deadline_at
      warehouse {
        id
        label
      }
    }
  }
}
{
  "sku": "SHIRT-BLUE-M",
  "warehouseId": "8b1f0e2c-3d4a-4b5c-9e6f-7a8b9c0d1e2f",
  "first": 25
}
mutation UpdateItemPreferences($input: UpdateItemPreferencesInput!) {
  updateItemPreferences(input: $input) {
    preferences {
      id
      sku
      reorder_point
      is_backorder_allowed
    }
  }
}
{
  "input": {
    "preferences": [
      {
        "id": "d0e1f2a3-b4c5-6789-4567-890123456789",
        "warehouse_id": "8b1f0e2c-3d4a-4b5c-9e6f-7a8b9c0d1e2f",
        "minimum_quantity": 5,
        "optimal_quantity": 60,
        "maximum_quantity": 120,
        "reorder_point": 15,
        "is_backorder_allowed": true,
        "is_restricted_to_single_position": true
      }
    ]
  }
}
·
Note:

updateItemPreferences replaces the whole preference, so send every field — omitted numbers are not left alone. Remove a preference entirely with removeItemPreferences, which takes a list of preference IDs.


Step 6 — Verify with the stock mutation log#

Every change you made above lands in stockMutations. Use it to confirm a mutation was processed, to see which quantity it resulted in, and to check whether the new quantity was broadcast to each connected sales channel.

Arguments:

NameTypeRequiredDescription
query
QueryFilter
OptionalA query string of filters and sorting, e.g. `sku=SHIRT-BLUE-M&sort=-created_at`.
first
Int
OptionalPage size.
after
String
OptionalCursor to continue from.

Each node is a StockMutation:

NameTypeRequiredDescription
id
ID!
OptionalIdentifier of the stock mutation.
source
StockMutationSource!
OptionalWhat caused the change — see the table at the top of this guide.
status
StockMutationStatus!
Optional`PENDING`, `PROCESSED` or `FAILING`.
new_quantity
Int64!
OptionalThe quantity that resulted from this change.
product
Product!
OptionalThe product whose stock changed.
location
Location!
OptionalThe location the change happened at.
order
Order
OptionalThe order behind the change, when there is one.
actor
StockMutationActor!▾
OptionalWho or what triggered the change.
references
[StockMutationReference!]!
OptionalThe entities behind the change — an `Order`, `Supply`, `DeliveryRequest`, `Reservation`, `Announcement` or `InventoryList`. Select them with inline fragments.
messages
[StockMutationMessage!]!▾
OptionalOne entry per sales channel the new quantity was broadcast to.
query GetStockMutations($first: Int!, $after: String!, $query: QueryFilter) {
  stockMutations(first: $first, after: $after, query: $query) {
    nodes {
      id
      source
      status
      new_quantity
      created_at
      product {
        sku
        label
      }
      location {
        id
        name
      }
      actor {
        id
        type
      }
      order {
        id
        number
      }
      references {
        ... on Order {
          order_id: id
          number
        }
        ... on Supply {
          supply_id: id
          number
        }
        ... on Reservation {
          reservation_id: id
          number
        }
        ... on Announcement {
          announcement_id: id
          number
        }
        ... on InventoryList {
          inventory_list_id: id
          number
        }
        ... on DeliveryRequest {
          delivery_request_id: id
          number
        }
      }
      messages {
        id
        status
        type
        new_quantity
        channel {
          id
          name
        }
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
{
  "first": 25,
  "after": "",
  "query": "sku=SHIRT-BLUE-M&sort=-created_at"
}
ℹ
Tip:

A mutation with status: PENDING has been accepted but not applied yet — re-read inventory after it flips to PROCESSED. A FAILING mutation did not land; check its messages to see which channel rejected the broadcast.


Notes#

  • Stock is held per business and warehouse. The same SKU can have completely different quantities and preferences in each combination, so always pass warehouse_id and business_id when you filter or write.
  • Int64 timestamps in these inputs are Unix timestamps in milliseconds. Divide by 1000 for a standard Unix timestamp.
  • Monetary amounts on cost are integers in cents.
  • quantity_expected never becomes sellable on its own — it only moves into quantity_available once the supply is actually received in the warehouse.
  • The inventory endpoints in step 4 are the only part of this guide that is not GraphQL, and they take camelCase field names where the rest of the API uses snake_case.
  • See Authentication for how to pass your API key, and Pagination for how the first / after cursors work.
Query Runnerhttps://afosto.app/graphql

No query loaded

Click play on any code block in the docs to load a query here.