Stock Movements
Every operation below is generated from the OpenAPI v2 specification and includes parameters, request fields, response fields, errors, and examples.
Stock Movements
List stock movements
| operationId | listStockMovements |
|---|---|
| Tag | Stock Movements |
| Security | bearerAuth |
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"
}