Stock Movements

Every operation below is generated from the OpenAPI v2 specification and includes parameters, request fields, response fields, errors, and examples.

Stock Movements

GET /api/v2/stock-movements

List stock movements

operationIdlistStockMovements
TagStock Movements
SecuritybearerAuth

Returns stock movements visible to the authenticated account. Use documented query parameters for filtering, sorting, and pagination.
For incremental export, request sort=id&after_id=<last_seen_id>. The after_id boundary is exclusive. Use the greatest returned id as the next high-water mark. The endpoint does not provide snapshot isolation; rows inserted concurrently with greater IDs may appear in later calls.
Both sides of one relocation use movement_type=stock_transfer and share the same non-null movement_group_id; use that opaque value to correlate the negative and positive rows.
Since the picking-lot update, actual picking appends two atomic order rows: operation=reservation_release has positive quantity_delta for the former reservation, and operation=physical_issue has negative quantity_delta with the actual batch, production_date and expiration_date. Each pair shares movement_group_id and sums to zero, preserving cumulative availability. Do not sum only negative order rows as consumption: that would count both the reservation and the physical issue. Use source=order_picking.issue to select new physical issues. Earlier rows are not rewritten; historical current picked lots can be read from orders.
transfer_kind distinguishes within_warehouse (document types 3/4) and between_warehouses (6/7); movement_group_id is opaque. stock_after remains null. The journal does not cover every manual loss/correction path.
Unknown read parameters are ignored. per_page is silently capped; use meta.per_page. Date-only boundaries follow the installation's stored calendar dates; ISO timestamps are serialized in UTC. The default general API limit is 600/minute; a user-specific override may apply. Its bucket is shared by authenticated user, not by shop/domain; Retry-After is returned on throttling.
Since the picking-lot update, actual picking appends two atomic order rows: operation=reservation_release has positive quantity_delta for the former reservation, and operation=physical_issue has negative quantity_delta with the actual batch, production_date and expiration_date. Each pair shares movement_group_id and sums to zero, preserving cumulative availability. Do not sum only negative order rows as consumption: that would count both the reservation and the physical issue. Use source=order_picking.issue to select new physical issues. Earlier rows are not rewritten; historical current picked lots can be read from orders.
transfer_kind distinguishes within_warehouse (document types 3/4) and between_warehouses (6/7); movement_group_id is opaque. stock_after remains null. The journal does not cover every manual loss/correction path.
Unknown read parameters are ignored. per_page is silently capped; use meta.per_page. Date-only boundaries follow the installation's stored calendar dates; ISO timestamps are serialized in UTC. The default general API limit is 600/minute; a user-specific override may apply. Its bucket is shared by authenticated user, not by shop/domain; Retry-After is returned on throttling.
Since the picking-lot update, actual picking appends two atomic order rows: operation=reservation_release has positive quantity_delta for the former reservation, and operation=physical_issue has negative quantity_delta with the actual batch, production_date and expiration_date. Each pair shares movement_group_id and sums to zero, preserving cumulative availability. Do not sum only negative order rows as consumption: that would count both the reservation and the physical issue. Use source=order_picking.issue to select new physical issues. Earlier rows are not rewritten; historical current picked lots can be read from orders.
transfer_kind distinguishes within_warehouse (document types 3/4) and between_warehouses (6/7); movement_group_id is opaque. stock_after remains null. The journal does not cover every manual loss/correction path.
Unknown read parameters are ignored. per_page is silently capped; use meta.per_page. Date-only boundaries follow the installation's stored calendar dates; ISO timestamps are serialized in UTC. The default general API limit is 600/minute; a user-specific override may apply. Its bucket is shared by authenticated user, not by shop/domain; Retry-After is returned on throttling.
Since the picking-lot update, actual picking appends two atomic order rows: operation=reservation_release has positive quantity_delta for the former reservation, and operation=physical_issue has negative quantity_delta with the actual batch, production_date and expiration_date. Each pair shares movement_group_id and sums to zero, preserving cumulative availability. Do not sum only negative order rows as consumption: that would count both the reservation and the physical issue. Use source=order_picking.issue to select new physical issues. Earlier rows are not rewritten; historical current picked lots can be read from orders.
transfer_kind distinguishes within_warehouse (document types 3/4) and between_warehouses (6/7); movement_group_id is opaque. stock_after remains null. The journal does not cover every manual loss/correction path.
Unknown read parameters are ignored. per_page is silently capped; use meta.per_page. Date-only boundaries follow the installation's stored calendar dates; ISO timestamps are serialized in UTC. The default general API limit is 600/minute; a user-specific override may apply. Its bucket is shared by authenticated user, not by shop/domain; Retry-After is returned on throttling.
Since the picking-lot update, actual picking appends two atomic order rows: operation=reservation_release has positive quantity_delta for the former reservation, and operation=physical_issue has negative quantity_delta with the actual batch, production_date and expiration_date. Each pair shares movement_group_id and sums to zero, preserving cumulative availability. Do not sum only negative order rows as consumption: that would count both the reservation and the physical issue. Use source=order_picking.issue to select new physical issues. Earlier rows are not rewritten; historical current picked lots can be read from orders.
transfer_kind distinguishes within_warehouse (document types 3/4) and between_warehouses (6/7); movement_group_id is opaque. stock_after remains null. The journal does not cover every manual loss/correction path.
Unknown read parameters are ignored. per_page is silently capped; use meta.per_page. Date-only boundaries follow the installation's stored calendar dates; ISO timestamps are serialized in UTC. The default general API limit is 600/minute; a user-specific override may apply. Its bucket is shared by authenticated user, not by shop/domain; Retry-After is returned on throttling.
Since the picking-lot update, actual picking appends two atomic order rows: operation=reservation_release has positive quantity_delta for the former reservation, and operation=physical_issue has negative quantity_delta with the actual batch, production_date and expiration_date. Each pair shares movement_group_id and sums to zero, preserving cumulative availability. Do not sum only negative order rows as consumption: that would count both the reservation and the physical issue. Use source=order_picking.issue to select new physical issues. Earlier rows are not rewritten; historical current picked lots can be read from orders.
transfer_kind distinguishes within_warehouse (document types 3/4) and between_warehouses (6/7); movement_group_id is opaque. stock_after remains null. The journal does not cover every manual loss/correction path.
Unknown read parameters are ignored. per_page is silently capped; use meta.per_page. Date-only boundaries follow the installation's stored calendar dates; ISO timestamps are serialized in UTC. The default general API limit is 600/minute; a user-specific override may apply. Its bucket is shared by authenticated user, not by shop/domain; Retry-After is returned on throttling.

HTTP request example (cURL)

Set your server and token. Replace sample IDs with accessible IDs. For requests with a body, save the applicable JSON example below as request.json and replace sample values before sending. Idempotency-Key identifies one logical operation.

curl --globoff --request GET "${NWMS_BASE_URL}/api/v2/stock-movements" \
  --header "Accept: application/json" \
  --header "Authorization: Bearer ${NWMS_TOKEN}"

Parameters

Name In Required Type Style Explode Constraints Description Example
page query no integer:int64 form true min: 1 Page number. The same filter may also be sent as filter[page] where supported by the Laravel query adapter. 1
per_page query no integer:int64 form true min: 1
max: 500
Number of records per page. Default is 100; maximum is 500. The same filter may also be sent as filter[per_page] where supported by the Laravel query adapter. 100
sort query no string form false pattern: ^-?[A-Za-z0-9_]+(,-?[A-Za-z0-9_]+)*$ Comma-separated sort fields. Prefix with - for descending order. Allowed fields: id, occurred_at, created_at. Default: id. The same filter may also be sent as filter[sort] where supported by the Laravel query adapter. "id"
after_id query no integer:int64 form true min: 0 Use sort=id and after_id for incremental export. The after_id boundary is exclusive (id > after_id). The same filter may also be sent as filter[after_id] where supported by the Laravel query adapter. 1000
id query no integer:int64 form true min: 1 Filter by id using exact matching. The same filter may also be sent as filter[id] where supported by the Laravel query adapter. 1001
warehouse_id query no integer:int64 form true min: 1 Filter by warehouse_id using exact matching. The same filter may also be sent as filter[warehouse_id] where supported by the Laravel query adapter. 42
product_id query no integer:int64 form true min: 1 Filter by product_id using exact matching. The same filter may also be sent as filter[product_id] where supported by the Laravel query adapter. 1201
base_product_id query no integer:int64 form true min: 1 Filter by base_product_id using exact matching. The same filter may also be sent as filter[base_product_id] where supported by the Laravel query adapter. 1200
document_type_id query no integer:int64 form true min: 1 Filter by document_type_id using exact matching. The same filter may also be sent as filter[document_type_id] where supported by the Laravel query adapter. 1
document_id query no integer:int64 form true min: 1 Filter by document_id using exact matching. The same filter may also be sent as filter[document_id] where supported by the Laravel query adapter. 5101
movement_type query no string form true Filter by movement_type using exact matching. The same filter may also be sent as filter[movement_type] where supported by the Laravel query adapter. "stock_transfer"
movement_group_id query no string form true Filter by movement_group_id using exact matching. The same filter may also be sent as filter[movement_group_id] where supported by the Laravel query adapter. "relocation:1722099600000000"
source query no string form true Filter by source using exact matching. The same filter may also be sent as filter[source] where supported by the Laravel query adapter. "api"
barcode query no string form true Filter by barcode using partial text matching. The same filter may also be sent as filter[barcode] where supported by the Laravel query adapter. "4601234567890"
batch query no string form true Filter by batch using partial text matching. The same filter may also be sent as filter[batch] where supported by the Laravel query adapter. "LOT-2026"
occurred_at_from query no string:date form true Lower inclusive date boundary for occurred_at. The same filter may also be sent as filter[occurred_at_from] where supported by the Laravel query adapter. "2026-07-14"
occurred_at_to query no string:date form true Upper inclusive date boundary for occurred_at. The same filter may also be sent as filter[occurred_at_to] where supported by the Laravel query adapter. "2026-07-14"
created_at_from query no string:date form true Lower inclusive date boundary for created_at. The same filter may also be sent as filter[created_at_from] where supported by the Laravel query adapter. "2026-07-14"
created_at_to query no string:date form true Upper inclusive date boundary for created_at. The same filter may also be sent as filter[created_at_to] where supported by the Laravel query adapter. "2026-07-14"
production_date_from query no string:date form true Lower inclusive date boundary for production_date. The same filter may also be sent as filter[production_date_from] where supported by the Laravel query adapter. "2026-07-14"
production_date_to query no string:date form true Upper inclusive date boundary for production_date. The same filter may also be sent as filter[production_date_to] where supported by the Laravel query adapter. "2026-07-14"
expiration_date_from query no string:date form true Lower inclusive date boundary for expiration_date. The same filter may also be sent as filter[expiration_date_from] where supported by the Laravel query adapter. "2026-07-14"
expiration_date_to query no string:date form true Upper inclusive date boundary for expiration_date. The same filter may also be sent as filter[expiration_date_to] where supported by the Laravel query adapter. "2026-07-14"

Request body

No request body.

Responses

200 Successful response.

Successful response.

Field Required Type Flags Constraints Description Example
data yes array<StockMovement>
data[] no StockMovement Append-only stock movement journal row.
data[].id yes integer:int64 readOnly min: 1 1001
data[].created_at no string:date-time | null "2026-07-14T10:30:00Z"
data[].updated_at no string:date-time | null "2026-07-14T10:30:00Z"
data[].movement_type yes StockMovementType Stock movement type emitted by the backend. Current values are acceptance, order, stock_transfer, inventory, quant_unpacking, and movement. The set may grow when new document sources are added. "stock_transfer"
data[].movement_group_id yes string | null Opaque correlation ID shared by both sides of one relocation or stock-transfer line. "relocation:1722099600000000"
data[].source no string | null "api"
data[].domain_id no integer:int64 | null min: 1 3
data[].warehouse_id no integer:int64 | null min: 1 42
data[].product_id no integer:int64 | null min: 1 1201
data[].base_product_id no integer:int64 | null min: 1 1200
data[].quantity_delta no number:float | null -2
data[].base_quantity_delta no number:float | null -12
data[].stock_after no number:float | null 18
data[].document no StockMovementDocument
data[].document.type_id no integer:int64 | null 3
data[].document.id no integer:int64 | null min: 1 8101
data[].document.line_id no integer:int64 | null min: 1 7001
data[].place_id no integer:int64 | null min: 1 501
data[].barcode no string | null "4601234567890"
data[].batch no string | null "LOT-2026"
data[].production_date no string:date | null "2026-01-15"
data[].expiration_date no string:date | null "2027-01-15"
data[].occurred_at no string:date-time | null "2026-07-14T10:30:00Z"
data[].transfer_kind no enum | null enum: within_warehouse, between_warehouses "within_warehouse"
data[].operation no enum | null enum: reservation_release, physical_issue Set for new picking audit pairs; null for older and other operations. "physical_issue"
links yes PaginationLinks
links.first yes string:uri | null "https://betawms.ffcdek.ru/api/v2/orders?page=1"
links.last yes string:uri | null "https://betawms.ffcdek.ru/api/v2/orders?page=2"
links.prev yes string:uri | null
links.next yes string:uri | null "https://betawms.ffcdek.ru/api/v2/orders?page=2"
meta yes PaginationMeta
meta.api_version yes enum enum: v2 "v2"
meta.current_page yes integer:int32 min: 1 1
meta.from yes integer:int32 | null min: 1 1
meta.last_page yes integer:int32 min: 1 2
meta.path yes string:uri "https://betawms.ffcdek.ru/api/v2/orders"
meta.per_page yes integer:int32 min: 1 50
meta.to yes integer:int32 | null min: 1 50
meta.total yes integer:int32 min: 0 73

Response examples

success

{
  "data": [
    {
      "id": 120001,
      "movement_type": "stock_transfer",
      "movement_group_id": "relocation:1722099600000000",
      "source": "api",
      "domain_id": 3,
      "warehouse_id": 42,
      "product_id": 1201,
      "base_product_id": 1200,
      "quantity_delta": -2,
      "base_quantity_delta": -12,
      "stock_after": null,
      "document": {
        "type_id": 3
      },
      "place_id": 501,
      "barcode": "4601234567890",
      "batch": "LOT-2026",
      "production_date": "2026-01-15",
      "expiration_date": "2027-01-15",
      "occurred_at": "2026-07-14T10:30:00Z",
      "created_at": "2026-07-14T10:30:00Z",
      "updated_at": "2026-07-14T10:30:00Z"
    }
  ],
  "links": {
    "first": "https://betawms.ffcdek.ru/api/v2/stock-movements?page=1",
    "last": "https://betawms.ffcdek.ru/api/v2/stock-movements?page=2",
    "prev": null,
    "next": "https://betawms.ffcdek.ru/api/v2/stock-movements?page=2"
  },
  "meta": {
    "api_version": "v2",
    "current_page": 1,
    "from": 1,
    "last_page": 2,
    "path": "https://betawms.ffcdek.ru/api/v2/stock-movements",
    "per_page": 100,
    "to": 100,
    "total": 123
  }
}

401 Authentication required

Unauthorized

Authentication is required.

Field Required Type Flags Constraints Description Example
message yes string "The request could not be processed."
error yes string "BAD_REQUEST"

Response examples

AUTH_REQUIRED

{
  "message": "Authentication is required.",
  "error": "AUTH_REQUIRED"
}

403 Forbidden

Forbidden

The authenticated account is not allowed to perform this action.

Field Required Type Flags Constraints Description Example
message yes string "The request could not be processed."
error yes string "BAD_REQUEST"

Response examples

FORBIDDEN

{
  "message": "The authenticated account is not allowed to perform this action.",
  "error": "FORBIDDEN"
}

429 Rate limit exceeded

TooManyRequests

Rate limit exceeded.

Field Required Type Flags Constraints Description Example
message yes string "Too Many Attempts."
error yes string "RATE_LIMITED"

500 Internal server error

InternalServerError

An unexpected server error occurred.

Field Required Type Flags Constraints Description Example
message yes string "The request could not be processed."
error yes string "BAD_REQUEST"

Response examples

INTERNAL_SERVER_ERROR

{
  "message": "An unexpected server error occurred.",
  "error": "INTERNAL_SERVER_ERROR"
}