Download OpenAPI specification:
Vendor-agnostic, planner-to-executor REST contract between any planner (WMS / OMS / ERP) and any Warehouse Execution System.
This is the planner-to-executor REST contract that lets any planning system (WMS, OMS, or ERP) hand work off to any Warehouse Execution System and receive real-time + reconciled feedback from it.
It is the contract published by SiriusVoyager/wes-contract and consumed
by:
SiriusVoyager/wms) — 1st-party planner reference
implementationSiriusVoyager/wes) — 1st-party executor reference
implementation (MAS via JaCaMo + VDA 5050)Authoritative ADRs:
| Concern | Planner owns | Executor owns |
|---|---|---|
| Documents (PR / PO / SO / Receiver / Shipper / WorkOrder / TransferOrder) | ✓ | — |
| Inventory state (single source of truth) | ✓ | — |
| Movement log (append-only history) | ✓ | — |
| Warehouse / Zone / Bin layout (master data) | ✓ | — |
| Worker / robot / device assignment | — | ✓ |
| Task sequencing, queue management, dispatch | — | ✓ |
| WCS integration (conveyors, AS/RS, sortation) | — | ✓ |
| Capacity / load balancing on the floor | — | ✓ |
| Simulation, optimization, real-time orchestration | — | ✓ |
The contract has three distinct endpoint families with three different semantics. They are not parameterizations of one endpoint — they have different paths, different consistency guarantees, and may use different transports.
The planner publishes work-release events as documents transition to executable state. Executors consume them via one of three transports (per-partner configured):
wms-wes.dispatch.*.v1 topicsGET /wes/v1/dispatch/pending?since={cursor}Per-warehouse ordering is preserved (Kafka partition key on
warehouse_frn; webhook delivery serializes per partner).
Continuous, best-effort. The executor POSTs movements, exceptions, and work-status as physical events occur, typically per-event with sub-second to seconds latency. Eventual consistency is acceptable; corrections may arrive later via the confirmation path.
Idempotency — every realtime call carries a correlation_id. Replays
of the same (partner_id, correlation_id) return HTTP 200 with
replay: true and no duplicate state mutation.
Authoritative per-shift / per-window batches. The executor POSTs the authoritative cumulative state for a window at end-of-shift or scheduled cadence. This is the source-of-truth path for accounting, audit, and EDI 947 outbound generation.
Confirmation triggers reconciliation against the realtime stream for the same window:
RECONCILED (emits wes.window.reconciled.v1)DISCREPANCY (emits wes.window.discrepancy.v1,
supervisor queue entry, NOTIF alert; supervisor resolves by
accept-confirmation / accept-realtime / manual-adjustment)All endpoints require partner authentication. Two schemes are supported:
| Scheme | When | Detail |
|---|---|---|
| mTLS | Production | Client cert presented at TLS handshake; cert thumbprint resolves to a wes_partner row |
| API key | Dev / test only | Authorization: Bearer <key> header |
Authentication is independent of the user Keycloak JWT used by the
interactive consoles. A partner authenticated as
(warehouse=WH-Tokyo-01, vendor=ManhattanWES) may only post for that
warehouse — the PDP rejects cross-warehouse traffic from the same
credential.
correlation_id (UUID v4 or v7 recommended). It is unique per
(partner_id, correlation_id).200 OK with replay: true; no duplicate state
mutation occurs.(planner_id, correlation_id).Semver. Major version is in the URL (/wes/v1/); breaking changes
introduce /wes/v2/. Minor versions add fields (always optional);
patch versions are bug-fixes / clarifications only. Consumers pin
to a specific contract version (e.g. wes-contract:1.2.0) and
upgrade deliberately.
Both the REST realization (this document, v1.2) and the gRPC realization (v1.3, ADR-0023) track major versions in lockstep. Within a major version they may drift on minors.
Planner → Executor. Work-release events from the planner to the executor. Three transports are supported (Kafka subscription, webhook POST, polling). One transport is selected per partner in the partner registry.
Used by executors that cannot consume Kafka and cannot receive webhooks (e.g. air-gapped LANs that allow only outbound HTTPS). The executor polls this endpoint with a cursor; the planner returns dispatch events the executor has not yet acknowledged.
Cursor semantics — since is an opaque server-issued
token from the prior page's next_cursor. On the first
call, omit since to receive the oldest unacked events.
Ack semantics — after processing a page, call
POST /dispatch/ack (separately) with the
next_cursor from the response to advance the cursor and
release the events.
Per-partner ordering is preserved; per-warehouse FIFO is guaranteed.
| since | string Opaque cursor from a previous response's |
| limit | integer [ 1 .. 500 ] Default: 100 Maximum events to return in this page. |
{- "events": [
- {
- "correlation_id": "807686c4-116c-44b3-a01c-b14b50e31bcc",
- "emitted_at": "2019-08-24T14:15:22Z",
- "kind": "SHIPPER_RELEASED",
- "warehouse_id": "string",
- "priority": 100,
- "due_date": "2019-08-24T14:15:22Z",
- "document_ref": {
- "type": "PR",
- "id": "string",
- "line_no": 0
}, - "payload": {
- "shipper_id": "string",
- "carrier": "string",
- "ship_by": "2019-08-24T14:15:22Z",
- "lines": [
- {
- "line_no": 0,
- "sku_id": "SKU-WIDGET-RED-LG",
- "qty": 0,
- "uom": "EA",
- "source_location": "string",
- "dest_location": "string",
- "lot": "string",
- "serial": "string"
}
]
}
}
], - "next_cursor": "string",
- "has_more": true
}Advances the per-partner cursor. Pages whose cursor is at or below the acked value will not be returned to this partner again.
Idempotent on (partner_id, cursor).
| partner_id required | string |
| cursor required | string The |
{- "partner_id": "string",
- "cursor": "string"
}{- "acked_at": "2019-08-24T14:15:22Z"
}This endpoint is not implemented by the planner. It is
documented here as the payload shape the executor must
accept at its own configured webhook_url when the
partner registry records dispatch_transport=webhook.
The planner POSTs one DispatchEvent per webhook call.
The executor must respond with HTTP 2xx within 5 seconds
to confirm receipt; non-2xx triggers exponential-backoff
retry (0s → 5s → 30s → 2m → 10m → 1h, up to 24h, then
DLQ).
Webhook calls are at-least-once — the executor MUST
be idempotent on (planner_id, correlation_id).
| correlation_id required | string <uuid> (CorrelationId) Partner-generated unique identifier per event. UUID v4 or v7
recommended. Used as the idempotency key
|
| emitted_at required | string <date-time> (Timestamp) RFC 3339 timestamp with timezone offset (UTC |
| kind required | string (DispatchEventKind) Enum: "SHIPPER_RELEASED" "RECEIVER_EXPECTED" "WORKORDER_RELEASED" "TRANSFER_OUT_RELEASED" "TRANSFER_IN_EXPECTED" "CANCELLED" |
| warehouse_id required | string |
| priority | integer [ 0 .. 100 ] 0 = lowest, 100 = highest; absent = default (50). |
| due_date | string <date-time> (Timestamp) RFC 3339 timestamp with timezone offset (UTC |
object (DocumentRef) | |
required | ShipperReleasedPayload (object) or ReceiverExpectedPayload (object) or WorkOrderReleasedPayload (object) or TransferOutReleasedPayload (object) or TransferInExpectedPayload (object) or DispatchCancelledPayload (object) Kind-specific payload. See the per-kind schemas. |
{- "correlation_id": "807686c4-116c-44b3-a01c-b14b50e31bcc",
- "emitted_at": "2019-08-24T14:15:22Z",
- "kind": "SHIPPER_RELEASED",
- "warehouse_id": "string",
- "priority": 100,
- "due_date": "2019-08-24T14:15:22Z",
- "document_ref": {
- "type": "PR",
- "id": "string",
- "line_no": 0
}, - "payload": {
- "shipper_id": "string",
- "carrier": "string",
- "ship_by": "2019-08-24T14:15:22Z",
- "lines": [
- {
- "line_no": 0,
- "sku_id": "SKU-WIDGET-RED-LG",
- "qty": 0,
- "uom": "EA",
- "source_location": "string",
- "dest_location": "string",
- "lot": "string",
- "serial": "string"
}
]
}
}Executor → Planner. Continuous, best-effort feedback. Movements,
exceptions, and work-status are POSTed as physical events occur.
Idempotent on (partner_id, correlation_id).
Posted by the executor each time one or more inventory movements complete on the warehouse floor (pick, put, move, adjust, consume, produce).
Movements created from this endpoint are written to the
planner's movement log with source='WES',
wes_partner_id, and wes_correlation_id. Inventory state
is updated immediately via the planner's existing async
pattern.
Idempotency — replays of the same
(partner_id, correlation_id) for any movement in the batch
return HTTP 200 with replay: true; no duplicate movement is
persisted.
Batching — up to 500 movements per request. For higher throughput, open multiple parallel connections rather than increasing batch size.
| partner_id required | string (PartnerId) ^[A-Za-z0-9._:-]+/[A-Za-z0-9._:-]+$ Composite partner identifier: |
required | Array of objects (Movement) [ 1 .. 500 ] items |
{- "partner_id": "WH-Tokyo-01/ManhattanWES",
- "movements": [
- {
- "correlation_id": "0193e4e3-1c8a-7c64-9b39-9d8c43c4a1b9",
- "occurred_at": "2026-05-22T14:30:11.482Z",
- "kind": "PICK",
- "sku_id": "SKU-WIDGET-RED-LG",
- "qty": 12,
- "uom": "EA",
- "from_location": "A-12-3-2",
- "to_location": "STAGING-OUT-7",
- "document_ref": {
- "type": "SHIPPER",
- "id": "SH-2026-000183"
}, - "actor": {
- "type": "AGENT",
- "id": "agent:picker:op-042"
}
}
]
}{- "results": [
- {
- "correlation_id": "807686c4-116c-44b3-a01c-b14b50e31bcc",
- "accepted": true,
- "replay": true,
- "error": {
- "code": "string",
- "message": "string"
}
}
]
}Posted by the executor each time an exception is detected on the warehouse floor: short-pick, damaged product, location empty, wrong SKU, robot stuck, worker unavailable, etc.
Creates a supervisor queue entry on the planner side and fires a NOTIF alert keyed by severity.
Idempotency — replays of the same
(partner_id, correlation_id) return HTTP 200 with
replay: true.
| partner_id required | string (PartnerId) ^[A-Za-z0-9._:-]+/[A-Za-z0-9._:-]+$ Composite partner identifier: |
required | Array of objects (ExecutionException) [ 1 .. 200 ] items |
{- "partner_id": "WH-Tokyo-01/ManhattanWES",
- "exceptions": [
- {
- "correlation_id": "0193e4e3-2210-7c64-9b39-9d8c43c4a1b9",
- "occurred_at": "2026-05-22T14:30:11.482Z",
- "kind": "SHORT_PICK",
- "severity": "HIGH",
- "sku_id": "SKU-WIDGET-RED-LG",
- "expected_qty": 12,
- "actual_qty": 8,
- "location": "A-12-3-2",
- "document_ref": {
- "type": "SHIPPER",
- "id": "SH-2026-000183"
}, - "actor": {
- "type": "AGENT",
- "id": "agent:picker:op-042"
}, - "note": "bin showed empty after 8 units pulled"
}
]
}{- "results": [
- {
- "correlation_id": "807686c4-116c-44b3-a01c-b14b50e31bcc",
- "accepted": true,
- "replay": true,
}
]
}Optional. Posted by the executor at task lifecycle
transitions (ASSIGNED, IN_PROGRESS, PAUSED, COMPLETED,
CANCELLED, FAILED).
Persisted as WorkAssignment rows on the planner side and
surfaced in document detail views; not used for inventory
accounting (movements are).
| partner_id required | string (PartnerId) ^[A-Za-z0-9._:-]+/[A-Za-z0-9._:-]+$ Composite partner identifier: |
required | Array of objects (WorkStatus) [ 1 .. 500 ] items |
{- "partner_id": "WH-Tokyo-01/ManhattanWES",
- "statuses": [
- {
- "correlation_id": "807686c4-116c-44b3-a01c-b14b50e31bcc",
- "occurred_at": "2019-08-24T14:15:22Z",
- "kind": "ASSIGNED",
- "task_id": "string",
- "document_ref": {
- "type": "PR",
- "id": "string",
- "line_no": 0
}, - "actor": {
- "type": "AGENT",
- "id": "string",
- "display_name": "string"
}, - "progress_pct": 100
}
]
}{- "accepted": true,
- "ingested_at": "2019-08-24T14:15:22Z"
}Optional. Posted by the executor periodically (every 30s – 5min recommended) with current floor capacity figures — active pickers, idle robots, conveyor utilization, zone-level WIP counts.
Used for planner-side dashboards; not used for accounting or reconciliation. Discardable.
| partner_id required | string (PartnerId) ^[A-Za-z0-9._:-]+/[A-Za-z0-9._:-]+$ Composite partner identifier: |
| occurred_at required | string <date-time> (Timestamp) RFC 3339 timestamp with timezone offset (UTC |
required | object |
{- "partner_id": "WH-Tokyo-01/ManhattanWES",
- "occurred_at": "2019-08-24T14:15:22Z",
- "snapshot": {
- "active_pickers": 0,
- "idle_pickers": 0,
- "active_robots": 0,
- "idle_robots": 0,
- "charging_robots": 0,
- "conveyor_utilization_pct": 100,
- "zone_wip": [
- {
- "zone_id": "string",
- "open_tasks": 0
}
]
}
}{- "accepted": true,
- "ingested_at": "2019-08-24T14:15:22Z"
}Executor → Planner. Authoritative per-shift / per-window batches. Triggers reconciliation against the realtime stream for the same window.
Posted by the executor at shift / window close. Carries the
authoritative cumulative state for the window keyed by
(warehouse_id, window_start, window_end, partner_id).
Triggers planner-side reconciliation against the realtime stream sum for the same window:
RECONCILED; planner emits
wes.window.reconciled.v1.DISCREPANCY; planner
emits wes.window.discrepancy.v1, places an entry in
the supervisor queue, fires a NOTIF alert. Supervisor
resolves via accept-confirmation / accept-realtime /
manual-adjustment.| partner_id required | string (PartnerId) ^[A-Za-z0-9._:-]+/[A-Za-z0-9._:-]+$ Composite partner identifier: |
| correlation_id required | string <uuid> (CorrelationId) Partner-generated unique identifier per event. UUID v4 or v7
recommended. Used as the idempotency key
|
| warehouse_id required | string |
| shift_id | string Optional executor-side shift identifier for human reference. |
| window_start required | string <date-time> (Timestamp) RFC 3339 timestamp with timezone offset (UTC |
| window_end required | string <date-time> (Timestamp) RFC 3339 timestamp with timezone offset (UTC |
required | Array of objects (MovementTotal) >= 0 items |
object |
{- "partner_id": "WH-Tokyo-01/ManhattanWES",
- "correlation_id": "0193e4e3-3210-7c64-9b39-9d8c43c4a1b9",
- "warehouse_id": "WH-Tokyo-01",
- "window_start": "2026-05-22T06:00:00Z",
- "window_end": "2026-05-22T14:00:00Z",
- "shift_id": "TOKYO-01-DAY-2026-05-22",
- "movement_totals": [
- {
- "kind": "PICK",
- "sku_id": "SKU-WIDGET-RED-LG",
- "qty": 184,
- "uom": "EA"
}, - {
- "kind": "PUT",
- "sku_id": "SKU-WIDGET-RED-LG",
- "qty": 184,
- "uom": "EA"
}
], - "meta": {
- "note": "shift closed nominally; no holdovers"
}
}{- "accepted": true,
- "replay": true,
- "window_id": "string",
}Posted by the executor on completion of a cycle-count cycle.
Treated as definitive for the items / locations covered:
the planner's inventory state is adjusted to the counted
figures (via compensating ADJUST movements with
source='WES').
Divergence from prior planner state beyond the configurable tolerance triggers the discrepancy resolution flow.
| partner_id required | string (PartnerId) ^[A-Za-z0-9._:-]+/[A-Za-z0-9._:-]+$ Composite partner identifier: |
| correlation_id required | string <uuid> (CorrelationId) Partner-generated unique identifier per event. UUID v4 or v7
recommended. Used as the idempotency key
|
| warehouse_id required | string |
| counted_at required | string <date-time> (Timestamp) RFC 3339 timestamp with timezone offset (UTC |
| cycle_count_doc_id | string Optional planner-side CycleCount document this submission belongs to. |
required | Array of objects (CycleCountLine) non-empty |
{- "partner_id": "WH-Tokyo-01/ManhattanWES",
- "correlation_id": "807686c4-116c-44b3-a01c-b14b50e31bcc",
- "warehouse_id": "string",
- "counted_at": "2019-08-24T14:15:22Z",
- "cycle_count_doc_id": "string",
- "lines": [
- {
- "sku_id": "SKU-WIDGET-RED-LG",
- "location": "string",
- "counted_qty": 0,
- "uom": "EA",
- "lot": "string",
- "serial": "string",
- "note": "string"
}
]
}{- "accepted": true,
- "replay": true,
- "window_id": "string",
}Posted by the executor as a point-in-time snapshot of inventory it observes — partial (a subset of locations) or full (whole warehouse). Definitive for the items / locations covered.
Use cases: nightly full-warehouse reconciliation; partial sweeps after a power-loss / network-outage recovery; an AS/RS that snapshots its bay contents on demand.
| partner_id required | string (PartnerId) ^[A-Za-z0-9._:-]+/[A-Za-z0-9._:-]+$ Composite partner identifier: |
| correlation_id required | string <uuid> (CorrelationId) Partner-generated unique identifier per event. UUID v4 or v7
recommended. Used as the idempotency key
|
| warehouse_id required | string |
| taken_at required | string <date-time> (Timestamp) RFC 3339 timestamp with timezone offset (UTC |
| scope required | string Enum: "FULL" "PARTIAL" FULL: the entire warehouse was scanned at |
| zone_filter | Array of strings Optional list of zone identifiers covered by a PARTIAL scope. |
required | Array of objects (InventorySnapshotLine) >= 0 items |
{- "partner_id": "WH-Tokyo-01/ManhattanWES",
- "correlation_id": "807686c4-116c-44b3-a01c-b14b50e31bcc",
- "warehouse_id": "string",
- "taken_at": "2019-08-24T14:15:22Z",
- "scope": "FULL",
- "zone_filter": [
- "string"
], - "lines": [
- {
- "sku_id": "SKU-WIDGET-RED-LG",
- "location": "string",
- "qty": 0,
- "uom": "EA",
- "lot": "string",
- "serial": "string"
}
]
}{- "accepted": true,
- "replay": true,
- "window_id": "string",
}Health + capability negotiation. Both sides expose these so partner onboarding and operational dashboards can probe each end.
Cheap, partner-callable health check. No auth required for liveness; readiness includes downstream dependency state and so may require auth depending on tenant config.
{- "status": "UP",
- "contract_version": "1.2.0",
- "components": [
- {
- "name": "string",
- "status": "UP",
- "detail": "string"
}
]
}Returns the capability matrix this endpoint supports — which realtime / confirmation / dispatch event types are accepted, which auth schemes are honored, which transports are available, and the contract semver this side implements.
Both planner and executor implementations of the contract should expose this; it enables partner adapters to detect feature drift at startup.
{- "contract_version": "1.2.0",
- "implementation": {
- "name": "FG.AI WMS wes-service",
- "version": "1.2.3",
- "vendor": "FlexGalaxy.ai"
}, - "transports": {
- "dispatch": [
- "kafka"
], - "ingest": [
- "rest"
]
}, - "event_types": {
- "dispatch": [
- "SHIPPER_RELEASED"
], - "realtime": [
- "movements"
], - "confirmation": [
- "shift-summary"
]
}, - "auth_schemes": [
- "mTLS"
], - "limits": {
- "max_realtime_batch_size": 500,
- "max_exception_batch_size": 200,
- "rate_limit_per_minute": 6000,
- "confirmation_sla_minutes": 90
}
}