FG.AI WMS ↔ WES Integration Contract (1.2.0)

Download OpenAPI specification:

FG.AI Platform — Contract Maintainers: platform@flexgalaxy.ai URL: https://github.com/SiriusVoyager/wes-contract License: Apache-2.0

Vendor-agnostic, planner-to-executor REST contract between any planner (WMS / OMS / ERP) and any Warehouse Execution System.

Overview

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:

  • FG.AI WMS (SiriusVoyager/wms) — 1st-party planner reference implementation
  • FG.AI WES (SiriusVoyager/wes) — 1st-party executor reference implementation (MAS via JaCaMo + VDA 5050)
  • 3rd-party WES products (Manhattan, Honeywell, Dematic, Softeon, KION, Locus, Geek+, AutoStore, etc.) via per-vendor adapters
  • 3rd-party WMS / OMS / ERP products that want to drive an FG.AI WES

Authoritative ADRs:

  • WMS ADR-0022 — REST realization (this document, v1.2)
  • WMS ADR-0023 — gRPC realization (v1.3, additive; same semantics)
  • WES ADR-0005 — standalone-repo decision
  • WES ADR-0006 — generalization from WMS↔WES to planner↔executor

Responsibility split

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

Endpoint families

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.

1. Dispatch (planner → executor)

The planner publishes work-release events as documents transition to executable state. Executors consume them via one of three transports (per-partner configured):

  • Kafka subscription — executor consumes from the planner's wms-wes.dispatch.*.v1 topics
  • Webhook POST — planner POSTs to the executor's configured URL with at-least-once exponential-backoff retries
  • Polling — executor calls GET /wes/v1/dispatch/pending?since={cursor}

Per-warehouse ordering is preserved (Kafka partition key on warehouse_frn; webhook delivery serializes per partner).

2. Realtime (executor → planner)

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.

3. Confirmation (executor → planner)

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:

  • Sums matchRECONCILED (emits wes.window.reconciled.v1)
  • Sums divergeDISCREPANCY (emits wes.window.discrepancy.v1, supervisor queue entry, NOTIF alert; supervisor resolves by accept-confirmation / accept-realtime / manual-adjustment)

Authentication

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.

Idempotency and ordering

  • Every realtime + confirmation payload carries a partner-generated correlation_id (UUID v4 or v7 recommended). It is unique per (partner_id, correlation_id).
  • Replays return 200 OK with replay: true; no duplicate state mutation occurs.
  • Dispatch ordering is preserved per warehouse via Kafka partition key. Webhook delivery serializes per partner. Poll callers are expected to honor the returned cursor.
  • Webhook retries follow exponential backoff (typical: 0s → 5s → 30s → 2m → 10m → 1h) with at-least-once semantics. Executors must be idempotent on (planner_id, correlation_id).

Versioning

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.

dispatch

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.

Pull pending dispatch events (poll mode)

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 semanticssince 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.

Authorizations:
mTLSApiKey
query Parameters
since
string

Opaque cursor from a previous response's next_cursor. Omit on the very first call.

limit
integer [ 1 .. 500 ]
Default: 100

Maximum events to return in this page.

Responses

Response samples

Content type
application/json
{
  • "events": [
    ],
  • "next_cursor": "string",
  • "has_more": true
}

Acknowledge a dispatch page (poll mode)

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).

Authorizations:
mTLSApiKey
Request Body schema: application/json
required
partner_id
required
string
cursor
required
string

The next_cursor value returned by a prior /dispatch/pending page.

Responses

Request samples

Content type
application/json
{
  • "partner_id": "string",
  • "cursor": "string"
}

Response samples

Content type
application/json
{
  • "acked_at": "2019-08-24T14:15:22Z"
}

[Reference] Webhook payload shape for `dispatch_transport=webhook`

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).

Authorizations:
WebhookHmac
Request Body schema: application/json
required
correlation_id
required
string <uuid> (CorrelationId)

Partner-generated unique identifier per event. UUID v4 or v7 recommended. Used as the idempotency key (partner_id, correlation_id).

emitted_at
required
string <date-time> (Timestamp)

RFC 3339 timestamp with timezone offset (UTC Z recommended).

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 Z recommended).

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.

Responses

Request samples

Content type
application/json
{
  • "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": {
    },
  • "payload": {
    }
}

realtime

Executor → Planner. Continuous, best-effort feedback. Movements, exceptions, and work-status are POSTed as physical events occur. Idempotent on (partner_id, correlation_id).

Report executed movements (real-time)

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.

Authorizations:
mTLSApiKey
Request Body schema: application/json
required
partner_id
required
string (PartnerId) ^[A-Za-z0-9._:-]+/[A-Za-z0-9._:-]+$

Composite partner identifier: {warehouse_code}/{vendor_code}. The planner resolves this against the wes_partner registry and cross-checks the authenticated cert thumbprint.

required
Array of objects (Movement) [ 1 .. 500 ] items

Responses

Request samples

Content type
application/json
Example
{
  • "partner_id": "WH-Tokyo-01/ManhattanWES",
  • "movements": [
    ]
}

Response samples

Content type
application/json
{
  • "results": [
    ]
}

Report execution-floor exceptions (real-time)

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.

Authorizations:
mTLSApiKey
Request Body schema: application/json
required
partner_id
required
string (PartnerId) ^[A-Za-z0-9._:-]+/[A-Za-z0-9._:-]+$

Composite partner identifier: {warehouse_code}/{vendor_code}. The planner resolves this against the wes_partner registry and cross-checks the authenticated cert thumbprint.

required
Array of objects (ExecutionException) [ 1 .. 200 ] items

Responses

Request samples

Content type
application/json
{
  • "partner_id": "WH-Tokyo-01/ManhattanWES",
  • "exceptions": [
    ]
}

Response samples

Content type
application/json
{
  • "results": [
    ]
}

Report task-level work-status (real-time)

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).

Authorizations:
mTLSApiKey
Request Body schema: application/json
required
partner_id
required
string (PartnerId) ^[A-Za-z0-9._:-]+/[A-Za-z0-9._:-]+$

Composite partner identifier: {warehouse_code}/{vendor_code}. The planner resolves this against the wes_partner registry and cross-checks the authenticated cert thumbprint.

required
Array of objects (WorkStatus) [ 1 .. 500 ] items

Responses

Request samples

Content type
application/json
{
  • "partner_id": "WH-Tokyo-01/ManhattanWES",
  • "statuses": [
    ]
}

Response samples

Content type
application/json
{
  • "accepted": true,
  • "ingested_at": "2019-08-24T14:15:22Z"
}

Report floor / fleet capacity metrics (real-time, optional)

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.

Authorizations:
mTLSApiKey
Request Body schema: application/json
required
partner_id
required
string (PartnerId) ^[A-Za-z0-9._:-]+/[A-Za-z0-9._:-]+$

Composite partner identifier: {warehouse_code}/{vendor_code}. The planner resolves this against the wes_partner registry and cross-checks the authenticated cert thumbprint.

occurred_at
required
string <date-time> (Timestamp)

RFC 3339 timestamp with timezone offset (UTC Z recommended).

required
object

Responses

Request samples

Content type
application/json
{
  • "partner_id": "WH-Tokyo-01/ManhattanWES",
  • "occurred_at": "2019-08-24T14:15:22Z",
  • "snapshot": {
    }
}

Response samples

Content type
application/json
{
  • "accepted": true,
  • "ingested_at": "2019-08-24T14:15:22Z"
}

confirmation

Executor → Planner. Authoritative per-shift / per-window batches. Triggers reconciliation against the realtime stream for the same window.

Submit authoritative per-shift movement summary

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:

  • Sums match (within tolerance) → window flips to RECONCILED; planner emits wes.window.reconciled.v1.
  • Sums diverge → window flips to 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.
Authorizations:
mTLSApiKey
Request Body schema: application/json
required
partner_id
required
string (PartnerId) ^[A-Za-z0-9._:-]+/[A-Za-z0-9._:-]+$

Composite partner identifier: {warehouse_code}/{vendor_code}. The planner resolves this against the wes_partner registry and cross-checks the authenticated cert thumbprint.

correlation_id
required
string <uuid> (CorrelationId)

Partner-generated unique identifier per event. UUID v4 or v7 recommended. Used as the idempotency key (partner_id, correlation_id).

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 Z recommended).

window_end
required
string <date-time> (Timestamp)

RFC 3339 timestamp with timezone offset (UTC Z recommended).

required
Array of objects (MovementTotal) >= 0 items
object

Responses

Request samples

Content type
application/json
{
  • "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": [
    ],
  • "meta": {
    }
}

Response samples

Content type
application/json
{
  • "accepted": true,
  • "replay": true,
  • "window_id": "string",
  • "reconciliation_url": "http://example.com"
}

Submit authoritative cycle-count results

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.

Authorizations:
mTLSApiKey
Request Body schema: application/json
required
partner_id
required
string (PartnerId) ^[A-Za-z0-9._:-]+/[A-Za-z0-9._:-]+$

Composite partner identifier: {warehouse_code}/{vendor_code}. The planner resolves this against the wes_partner registry and cross-checks the authenticated cert thumbprint.

correlation_id
required
string <uuid> (CorrelationId)

Partner-generated unique identifier per event. UUID v4 or v7 recommended. Used as the idempotency key (partner_id, correlation_id).

warehouse_id
required
string
counted_at
required
string <date-time> (Timestamp)

RFC 3339 timestamp with timezone offset (UTC Z recommended).

cycle_count_doc_id
string

Optional planner-side CycleCount document this submission belongs to.

required
Array of objects (CycleCountLine) non-empty

Responses

Request samples

Content type
application/json
{
  • "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": [
    ]
}

Response samples

Content type
application/json
{
  • "accepted": true,
  • "replay": true,
  • "window_id": "string",
  • "reconciliation_url": "http://example.com"
}

Submit point-in-time inventory snapshot (partial or full)

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.

Authorizations:
mTLSApiKey
Request Body schema: application/json
required
partner_id
required
string (PartnerId) ^[A-Za-z0-9._:-]+/[A-Za-z0-9._:-]+$

Composite partner identifier: {warehouse_code}/{vendor_code}. The planner resolves this against the wes_partner registry and cross-checks the authenticated cert thumbprint.

correlation_id
required
string <uuid> (CorrelationId)

Partner-generated unique identifier per event. UUID v4 or v7 recommended. Used as the idempotency key (partner_id, correlation_id).

warehouse_id
required
string
taken_at
required
string <date-time> (Timestamp)

RFC 3339 timestamp with timezone offset (UTC Z recommended).

scope
required
string
Enum: "FULL" "PARTIAL"

FULL: the entire warehouse was scanned at taken_at; missing (SKU, location) pairs are taken as zero. PARTIAL: only the (SKU, location) pairs explicitly listed are authoritative; everything else is untouched.

zone_filter
Array of strings

Optional list of zone identifiers covered by a PARTIAL scope.

required
Array of objects (InventorySnapshotLine) >= 0 items

Responses

Request samples

Content type
application/json
{
  • "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": [
    ],
  • "lines": [
    ]
}

Response samples

Content type
application/json
{
  • "accepted": true,
  • "replay": true,
  • "window_id": "string",
  • "reconciliation_url": "http://example.com"
}

introspection

Health + capability negotiation. Both sides expose these so partner onboarding and operational dashboards can probe each end.

Liveness / readiness probe

Cheap, partner-callable health check. No auth required for liveness; readiness includes downstream dependency state and so may require auth depending on tenant config.

Responses

Response samples

Content type
application/json
{
  • "status": "UP",
  • "contract_version": "1.2.0",
  • "components": [
    ]
}

Capability negotiation

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.

Authorizations:
mTLSApiKey

Responses

Response samples

Content type
application/json
{
  • "contract_version": "1.2.0",
  • "implementation": {
    },
  • "transports": {
    },
  • "event_types": {
    },
  • "auth_schemes": [
    ],
  • "limits": {
    }
}