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

JSONSend 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 / PATCHDo 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.
IDsDocument, 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 / 409The 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 / 409The earlier operation is still processing. Wait for its result rather than creating a duplicate under another key.
Cross-dockIdempotency-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

401Check the token and Authorization header.
403Check module and action permissions; changing an ID does not grant access.
404The object is missing or outside scope. Check domain, parent and nested object IDs.
422Correct fields listed in errors. Repeating an unchanged invalid body is generally unhelpful.
409Inspect the conflict code. Refetch a stale version; wait for an in-progress command.
429Reduce request rate and honor Retry-After when present. Use bounded retries with increasing delays.
5xx / timeoutA 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.aggregateArrays 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-lotsPhysically 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_lotsActual receipt lots. Expected, accepted and placed quantities are distinct measures; a difference does not itself indicate an API error.
requested_lot / fulfillment_lotsOrder 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 / operationPicking 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-boxesActual 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_dateFor 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.
LifecycleAcceptance cancellation is a separate cancel command with state checks, not deletion. Prefer /lines; deprecated /offers remain for compatibility. See operation-specific constraints.