API v2 integration guide
1. Server, token and first request
The base URL is your NWMS installation without /api/v2. Local documentation describes local code; availability on another server depends on its deployed release. Use your account Bearer token. User permissions and data scope apply to API requests.
export NWMS_BASE_URL='https://YOUR-NWMS-HOST'
export NWMS_TOKEN='YOUR-TOKEN'
export NWMS_REQUEST_KEY='UNIQUE-KEY-FOR-ONE-OPERATION'
curl --globoff "${NWMS_BASE_URL}/api/v2/products?per_page=1" \
--header "Authorization: Bearer ${NWMS_TOKEN}" \
--header 'Accept: application/json'2. Request and response shapes
| JSON | Send Content-Type: application/json. Public write fields are nested in data. Lines, product arrays and commands have their own schemas; use the operation-specific contract. |
|---|---|
| GET item | {"data": {...}, "meta": {"api_version": "v2"}} |
| GET collection | {"data": [...], "links": {...}, "meta": {...}} |
| PUT / PATCH | Do not assume PUT replaces the entire resource. For notification templates, both methods update supplied fields only. Omitted fields and explicit null differ; check nullable in the schema. |
| IDs | Document, line, warehouse, lot and unique SSCC identifiers refer to different entities. Do not substitute marketplace external IDs for WMS IDs. |
3. Filters, sorting and export
Each operation lists its supported filters. The common form is filter[field]; sort=-updated_at,id orders by descending time then ascending ID. include selects supported relations. Notification lists default to 50 rows and cap at 250; other operations may differ. URL-encode query values, especially Unicode, timezone plus signs and brackets.
curl --get "${NWMS_BASE_URL}/api/v2/notification-templates" \
--header "Authorization: Bearer ${NWMS_TOKEN}" \
--header 'Accept: application/json' \
--data-urlencode 'filter[status_id]=1' \
--data-urlencode 'filter[name]=Приёмка' \
--data-urlencode 'include=bindings' \
--data-urlencode 'sort=-updated_at,id' \
--data-urlencode 'per_page=50' \
--data-urlencode 'page=1'Keep filters and sort unchanged while following links.next until null. A changing database is not a snapshot: store IDs and updated_at and deduplicate locally. Use after_id only where documented. A YYYY-MM-DD bound for created_at/updated_at includes that full day in the application timezone; agree on server timezone for synchronization.
4. Idempotency and timeout recovery
For ordinary v2 writes using this middleware, Idempotency-Key is optional but recommended, at most 191 characters. Successful responses are retained for one day per user, HTTP method and path. Repeat the exact URL and body bytes: a different serialization of equivalent JSON can conflict. Replays include Idempotency-Replayed: true. Error responses are not stored as successful outcomes.
| IDEMPOTENCY_KEY_CONFLICT / 409 | The key was used with another URL/body. Use a new key for a new logical operation; restore the original request for a retry. |
|---|---|
| IDEMPOTENCY_KEY_IN_PROGRESS / 409 | The earlier operation is still processing. Wait for its result rather than creating a duplicate under another key. |
| Cross-dock | Idempotency-Key is mandatory, including type_id=3 acceptance creation. Commands also require a UUID command_id and current version where applicable. See cross-docking. |
5. Handling errors
| 401 | Check the token and Authorization header. |
|---|---|
| 403 | Check module and action permissions; changing an ID does not grant access. |
| 404 | The object is missing or outside scope. Check domain, parent and nested object IDs. |
| 422 | Correct fields listed in errors. Repeating an unchanged invalid body is generally unhelpful. |
| 409 | Inspect the conflict code. Refetch a stale version; wait for an in-progress command. |
| 429 | Reduce request rate and honor Retry-After when present. Use bounded retries with increasing delays. |
| 5xx / timeout | A write outcome may be unknown. Reconcile the resource and retry the original request with the same key. Log time, method, path, status and response without credentials. |
Do not assume one universal error field: cross-dock uses meta.error_code, whereas ordinary idempotency conflicts use error. Check each operation response schema.
Documents, lots and physical stock
| stocks.physical / stocks.aggregate | Arrays of warehouse totals: warehouse_id, warehouse_name, stockTotal, stockReserved, stockAvailable. stocks.available_by_warehouse is an object keyed by warehouse ID; these are not identical shapes. |
|---|---|
| GET /api/v2/products/{id}/stock-lots | Physically placed quantities by lot and dates. An unassigned reservation cannot be subtracted from an arbitrary lot. Check quantity units and warehouse in the response. |
| accepted_lots | Actual receipt lots. Expected, accepted and placed quantities are distinct measures; a difference does not itself indicate an API error. |
| requested_lot / fulfillment_lots | Order requested_lot expresses a requirement; fulfillment_lots records actual picking and picked_quantity the picked amount. Multiple actual lots cannot be reduced to one date. |
| movement_type / operation | Picking can emit a reservation-release and physical-issue pair. Distinguish operation instead of counting two physical issues. Transfers correlate through movement_group_id; transfer_kind specifies the kind. |
| GET /api/v2/closed-boxes | Actual sealed boxes with a unique SSCC: balance=1 and balance_unit=box. A packaging-type barcode from products is not a unique SSCC. Opened, shipped and expected-only boxes are excluded. |
| acceptance_date | For standard/return creation (type_id=1/2), the current handler assigns server now() even when acceptance_date is supplied. Updating an existing acceptance accepts the date. Type 3 uses a separate cross-dock service. created_at/updated_at are technical timestamps, not the document date. |
| Lifecycle | Acceptance cancellation is a separate cancel command with state checks, not deletion. Prefer /lines; deprecated /offers remain for compatibility. See operation-specific constraints. |