Side-partner interface mapping patterns

This page covers the mapping patterns used in Path C integrations: how partner-side payload shapes map to FG.AI’s domain model on the inbound leg, how FG.AI events map to partner outbound shapes on the outbound leg, and the conventions both sides follow for time, units, identity, and error responses.

The patterns here are partner-agnostic. The concrete field-level mapping for any specific Path C engagement lives in that partner’s integration spec + the per-partner adapter repo. This page is the reference engineering teams (yours and FG.AI’s) use to design those mappings consistently.

For runtime architecture, see side-partner-architecture. For onboarding, see side-partner-onboarding. For operations, see side-partner-operations.

Inbound mapping — adapter-boundary partners

For adapter-boundary Path C partners, the FG.AI-hosted side-partner adapter is the inbound boundary. The partner maps to the adapter OpenAPI, and the adapter maps to FG.AI canonical WMS/WES internals. This keeps partner-specific payload decisions out of core WMS services.

The reusable adapter API is documented in side-partner-adapter-api. It is the correct pattern for partners that look like RTA: they push master data and work orders to FG.AI, but expect FG.AI to call their own postback APIs for operational outcomes.

Which REST family Path C uses

FG.AI WMS publishes core ingest surfaces and adapter surfaces (see openapi-reference):

Surface

Path family

Used by

OpenAPI

wms-ingest/v1/*

/wms-ingest/v1/master/*, /wms-ingest/v1/documents/*, /wms-ingest/v1/inventory/*, /wms-ingest/v1/jobs/*, /wms-ingest/v1/quarantine/*

Path A (wes-contract direct), Path B (FlexSync), 3PL EDI inbound, system-integrator bulk-ingest bridges

openapi/v1/wms-ingest.yaml — published

Adapter-boundary Path C

/wms-rta/v1/skus, /locations, /work-orders, /work-orders/{woCode}/cancel

RTA and generic side partners with the same topology

openapi/wms-rta-adapter/v1/wms-rta-adapter.yaml — published

wms-partners/v1/*

/wms-partners/v1/authenticate, /wms-partners/v1/inventory-delta, /wms-partners/v1/.well-known/jwks.json

Path C partner-scoped reads only

openapi/wms-partners/v1/wms-partners.yaml — published

The adapter-boundary API is deliberately separate from core WMS ingest. It contains the operations side partners commonly need: SKU master, location master, work-order push, work-order cancellation, pre-pick label binding, and request audit lookup.

The per-endpoint item shape inside items[] mirrors the canonical wms-ingest/v1 schemas verbatim: source_id + source_version + per-entity fields per the canonical Sku, Location, and WorkOrder definitions in openapi/v1/wms-ingest.yaml. The Path-C-specific OpenAPI, when it ships, may simplify or alias fields — pin to it when published; until then, the canonical wms-ingest/v1 item shape is the safe target. The examples in §1 / §2 / §3 below use the canonical shape.

Authentication for inbound writes

Two auth schemes apply depending on whether you are integrating against the UAT bridge (wms-partners-bridge-service in fg-ai-dev-services, today) or post-retirement real services (WMS Phase 1 master-data-service + Phase 2 production-service, future). The URLs and request envelope are identical across both; only the auth scheme changes at the retirement cutover.

Stage

FG.AI hostname

Auth scheme

Credential

Why

UAT (today) — against the bridge

dev-api.flexgalaxy.com

Partner-bearer-JWT (same as wms-partners/v1/)

RS256 JWT from POST /wms-partners/v1/authenticate

The bridge is a throwaway stub deployed only to dev; it reuses the JWT pattern so partners exercise one auth flow during UAT. Transitional convenience, not the canonical contract.

Production (post-retirement) — against real services in prod-intl / prod-cn

api.flexgalaxy.ai (intl) / api.flexgalaxy.com (cn)

mTLS at EKS ingress

Client certificate presented by your edge

Canonical FG.AI partner-auth per authentication.md; same scheme Path A, Path B, and EDI inbound use.

Production (post-retirement) — dev tier

dev-api.flexgalaxy.com (post-bridge)

API key bearer

Authorization: Bearer <api-key>

Canonical per authentication.md § API key (dev / test only).

Cutover protocol: when the bridge retires, the auth scheme on dev-api.flexgalaxy.com/pim/v1/* + /wms/v1/work-orders changes from JWT to API-key bearer. Coordinated cutover with ≥30 days advance notice to all Path C partners. URLs, request envelope, and per-item shape do not change.

The wms-partners/v1/ partner-bearer-JWT remains bounded to reads + /authenticate only (per ADR-0045 §4) for the canonical contract. The bridge stretches this convention as a UAT-only convenience; the canon is restored at retirement.

Idempotency is enforced via the (partner_id, correlation_id) tuple per the canonical idempotency convention:

  • partner_id and correlation_id are request body fields on every POST to pim/v1/ and wms/v1/ (see envelope examples below). The wms-ingest contract also accepts X-Correlation-Id as a header equivalent — body field takes precedence when both are present.

  • correlation_id is a caller-supplied UUID v4 or v7. Reusing the same value across two different logical requests is a caller-side bug; use a fresh UUID per logical action.

  • The server-assigned control_number returned in the response is a stable reference for your records and FG.AI’s audit log — it is NOT the idempotency key. Replays of the same (partner_id, correlation_id) return the original control_number + replayed: true.

Inbound reads to wms-partners/v1/ use the partner-bearer-JWT pattern described in side-partner-shim. The two auth patterns are deliberately separate and bounded to their respective namespaces.

Inbound request envelope (common to §1, §2, §3)

Every POST to pim/v1/skus, pim/v1/locations, or wms/v1/work-orders carries the same top-level shape — partner_id + correlation_id + items[] — with per-endpoint item shape:

POST /pim/v1/skus
Content-Type: application/json
Authorization: Bearer <jwt>              # UAT bridge today; post-retirement: Bearer <api-key> in dev, mTLS client cert in prod (no header)
{
  "partner_id": "<partner-code>-TENANT-<tenant-code>",
  "correlation_id": "01926e2c-8f50-7a00-b001-2345abcdef67",
  "items": [ /* per-endpoint item shape — see §1/§2/§3 */ ]
}

partner_id format follows the canon {source-system-code}-TENANT-{tenant-code} convention per authentication § partner_id format. FG.AI assigns the literal value at onboarding — treat the example here as a structural placeholder, not a value to copy.

Successful response (200 OK) — UAT-bridge shape (wms-partners-bridge-service):

{
  "control_number": "ctl_a1b2c3d4e5f60718293a4b5c6d7e8f90",
  "correlation_id": "01926e2c-8f50-7a00-b001-2345abcdef67",
  "received_at": "2026-05-29T08:02:14Z",
  "replayed": false
}
  • control_number is server-assigned (format ctl_<32-hex>, derived from a UUID v4). Stable for the row’s lifetime; safe to log for audit/correlation.

  • correlation_id is echoed verbatim from the request body so you can verify the dedup key was received correctly.

  • replayed: true on subsequent POSTs with the same (partner_id, endpoint, correlation_id) triple, returning the originally-minted control_number + original received_at.

Successful response (200 OK) — canonical post-retirement shape (canonical BulkUpsertResponse per openapi/v1/wms-ingest.yaml):

{
  "results": [ /* per-item ItemResult: ACCEPTED | REPLAY | QUARANTINED | REJECTED — see errors.md */ ],
  "summary": { "accepted": 95, "replay": 0, "quarantined": 5, "rejected": 0 }
}

The two response shapes are different. During UAT, the bridge service returns the simpler {control_number, correlation_id, received_at, replayed} shape because it stages payloads without per-item processing. After bridge retirement (when WMS Phase 1 ships master-data-service and Phase 2 ships production-service — per dmall-bridge-plan.md §”Retirement”), the same URLs return the canonical BulkUpsertResponse shape with per-item results[] + summary.

Hard cutover at retirement T — no overlap window, no machine-readable discriminator. Per WMS ADR-0045 §”Resolutions” §5a, retirement is a coordinated event with a datetime-pinned cutover and ≥ 30 days advance notice to every active Path C partner. FG.AI does NOT publish a X-FGAI-Response-Schema header, a versioned Content-Type, a schema_version field, or any other signal that lets one client handle both shapes. You are expected to maintain one response-parsing code path per stage: deploy the bridge-shape parser during UAT; deploy the canonical-shape parser at or before retirement T. If your client posts to the URL after T with bridge-shape parsing still wired in, the response will fail to parse and you observe the cutover as a client-side incident. The 30-day notice + datetime pin is the partner-coordination mechanism; missing the cutover deadline is treated as a partner-side operational issue, not a FG.AI commitment failure.

See errors for the per-item ACCEPTED / REPLAY / QUARANTINED / REJECTED status semantics inside results[] and the request-level HTTP status table.

§1 — SKU master push

Map your SKU shape to FG.AI’s PIM Product/SKU REST surface:

POST api.flexgalaxy.com/pim/v1/skus

Common field mapping pattern (your exact mapping is partner-specific):

Your side

FG.AI WMS side

Notes

productCode (or your equivalent)

sku.code

Partner-stable string; idempotency key

product display name

sku.name

UTF-8; localized names supported

unit-of-measure

sku.base_uom

Immutable post-first-movement; map your UoM enum to FG.AI’s

dimensions / weight / hazmat

sku.attributes (JSONB)

Partner-defined attribute shape

your category / family code

sku.category

Optional; FG.AI does not consume for planner logic

Example per-item shape inside items[] (mirrors the canonical Sku schema in openapi/v1/wms-ingest.yaml):

{
  "source_id": "SKU-DAIRY-42",
  "source_version": 1,
  "lifecycle": "ACTIVE",
  "name": "Whole Milk 1L Carton",
  "description": null,
  "base_uom": "EA",
  "lot_tracked": false,
  "serial_tracked": false,
  "hazmat_class": null,
  "temperature_class": "AMBIENT"
}

source_id + source_version together form the item-level idempotency key per the canonical idempotency convention. source_id is your partner-stable string for the SKU; source_version is a monotonically increasing integer you bump when the SKU’s attributes change. Same (source_id, source_version) re-sent returns REPLAY and does not mutate.

FG.AI requirements: MD-01 (CRUD Products), MD-02 (CRUD SKUs linked to Products), Phase 1.

§2 — Location master push

Map your warehouse/zone/bin hierarchy to FG.AI’s master-data location surface:

POST api.flexgalaxy.com/pim/v1/locations

Common field mapping:

Your side

FG.AI WMS side

warehouseCode

warehouse.code

zoneCode

zone.code (scoped to warehouse)

binCode (or your equivalent)

bin.code (scoped to zone)

zone capacity / type / temperature / hazmat

zone.attributes (JSONB)

Example per-item shape inside items[] (mirrors the canonical Location schema in openapi/v1/wms-ingest.yaml). Locations are a three-level hierarchy keyed off kindWAREHOUSEZONE (parent = warehouse) → BIN (parent = zone) — submitted as separate items in the same batch, with parent_source_id wiring the hierarchy:

[
  {
    "source_id": "WH-TOKYO-01",
    "source_version": 1,
    "lifecycle": "ACTIVE",
    "kind": "WAREHOUSE",
    "name": "Tokyo Hub 01",
    "parent_source_id": null,
    "attributes": { "timezone": "Asia/Tokyo" }
  },
  {
    "source_id": "Z-AMBIENT",
    "source_version": 1,
    "lifecycle": "ACTIVE",
    "kind": "ZONE",
    "name": "Ambient",
    "parent_source_id": "WH-TOKYO-01",
    "attributes": { "temperature_class": "AMBIENT" }
  },
  {
    "source_id": "A-12-3-1",
    "source_version": 1,
    "lifecycle": "ACTIVE",
    "kind": "BIN",
    "name": "Bin A-12-3-1",
    "parent_source_id": "Z-AMBIENT",
    "attributes": { "capacity_units": 240, "pickable": true }
  }
]

FG.AI requirement: MD-07, Phase 1.

§3 — Work order push

Map your work-order shape to FG.AI’s document-service WO surface:

POST api.flexgalaxy.com/wms/v1/work-orders

Common field mapping:

Your side

FG.AI WMS side

Notes

woCode (or your equivalent)

work_order.code

Partner-stable; idempotency key

whCode

work_order.warehouse_code

Must reference a previously-pushed warehouse

taskList[].taskNo

work_order.tasks[].code

Per-task partner-stable identifier

taskList[].productCode

work_order.tasks[].sku_code

References your SKU master

taskList[].planQty

work_order.tasks[].planned_qty

Decimal end-to-end

taskList[].expirationDate

work_order.tasks[].expiration_at

See time conventions below

Example per-item shape inside items[] (mirrors the canonical WorkOrder / DocumentHeader / DocumentLine schemas in openapi/v1/wms-ingest.yaml):

{
  "source_id": "WO-2026-04823",
  "source_version": 1,
  "lifecycle": "ACTIVE",
  "warehouse_source_id": "WH-TOKYO-01",
  "state": "OPEN",
  "sub_type": "PRODUCTION",
  "bom_source_id": null,
  "issued_at": "2026-05-29T09:00:00+09:00",
  "expected_at": "2026-05-29T17:00:00+09:00",
  "lines": [
    {
      "line_no": 1,
      "sku_source_id": "SKU-DAIRY-42",
      "qty": "240",
      "uom": "EA",
      "to_location_source_id": "A-12-3-1"
    }
  ],
  "attributes": {}
}

The warehouse_source_id, sku_source_id, and to_location_source_id fields are source_id references into the SKU / location masters you already pushed via pim/v1/skus and pim/v1/locations. The push order matters: master data first, then work orders that reference it.

Lot/serial tracking is OUT OF SCOPE for the initial Path C inbound subset. The canonical DocumentLine schema includes lot_source_id and serial_source_ids[] fields that reference lot/serial masters pushed via the broader wms-ingest/v1/master/lots + wms-ingest/v1/master/serials surfaces, which Path C does not currently expose. If your workflows require lot or serial tracking, raise this during onboarding so we can scope the Path C lot/serial extension separately (it is not currently in the MAX_SIDE_PARTNERS=2 budget envelope). For UAT against the bridge, omit lot_source_id and serial_source_ids[] from your work-order lines.

FG.AI requirements: DOC + WO categories, Phase 1.

§4 — Inventory delta query

Inventory delta uses the only Path C-specific endpoint FG.AI ships (PART-02). You poll; FG.AI returns paginated change-log:

GET api.flexgalaxy.com/wms-partners/v1/inventory-delta?since={timestamp}&limit={N}&cursor={opaque}
Authorization: Bearer {partner-token from wms-partners/v1/authenticate}

See side-partner-shim § The one new endpoint for the full parameter table (since is required; limit defaults to 100 with max 1000; cursor is the opaque next-page token; retention default is 7 days, configurable per-partner).

Response:

{
  "since": "2026-05-29T08:00:00Z",
  "until": "2026-05-29T08:05:00Z",
  "next_cursor": "eyJ0aW1lc3RhbXAiOi4uLn0=",
  "deltas": [
    {
      "sku_code": "...",
      "location_code": "...",
      "qty_delta": "+8" | "-3",
      "movement_type": "RECEIVE" | "PUTAWAY" | "PICK" | "PACK" | "SHIP" | "TRANSFER_OUT" | "TRANSFER_IN_PENDING" | "TRANSFER_IN_COMPLETE" | "TRANSFER_REVERT" | "INTERNAL_MOVE" | "ADJUST" | "CYCLE_COUNT" | "CONSUME" | "PRODUCE" | "RESERVE" | "RELEASE" | "STATUS_CHANGE" | "QUARANTINE_CLEAR",
      "occurred_at": "2026-05-29T08:02:14Z",
      "source_movement_id": "frn:wms:intl:movement/MV-2026-04823"
    }
  ]
}

If your design expected a push-on-change webhook, Path C replaces that with this pull-on-poll endpoint. Your addQty / deductQty decomposition is recoverable from qty_delta sign — positive = add, negative = deduct.

Authentication: POST /wms-partners/v1/authenticate with (partner_app_id, partner_app_secret) returns an RS256 JWT signed via AWS KMS, with configurable TTL (default 7200 seconds). The token is verifiable locally via the partition-scoped GET /wms-partners/v1/.well-known/jwks.json endpoint (RFC 7517 JWKS, Cache-Control 300s). See side-partner-shim § Partner-bearer-JWT authentication for the full claim shape, JWKS verification pattern, and partition-scoping rules. WMS REQs PART-02 + PART-03.

Outbound mapping — FG.AI adapter translates to your shape

The outbound side is the wes-{partner}-adapter’s job. The adapter aggregates FG.AI Kafka events and produces your outbound payloads. This section covers the patterns the adapter follows so you know what to expect.

Authentication for outbound

The adapter authenticates to your /authenticate endpoint at startup and refreshes the token proactively before expiry. Refresh-on-expiry is automatic; observed 401s trigger an immediate refresh + retry. Your TTL is whatever you declare in the /authenticate response; the adapter applies a 5-minute refresh lead by default (so a 120-minute TTL refreshes at the 115-minute mark).

The token is cached per-pod in memory and persisted to the per-(partner, tenant) Postgres token_cache table so JVM restart does not force re-auth.

No HMAC body signature. Path C outbound POSTs authenticate by presenting your bearer token in the Authorization header over TLS. The adapter does NOT add an X-FGAI-Signature HMAC header to outbound bodies — that convention belongs to FG.AI’s wms-ingest webhook pattern (see authentication § HMAC-SHA256) and is deliberately not adopted for Path C, where the bearer token issued by your /authenticate endpoint is the authoritative caller credential. If your security policy requires signed webhook bodies, raise it during onboarding so we can either (a) document that Path C does not provide them OR (b) negotiate a per-partner HMAC layer as a scoped extension.

Per-document completion postback pattern

A common outbound shape: per-document completion (per work order, per shipper, per receiver) with a list of task completions. The adapter waits for both the FG.AI-side closure signal and the executor-side per-task confirmation before posting — incomplete events sit in the per-(partner, tenant) aggregation table until completed or aged out per shift-window timeout.

Aggregation rule: emit one outbound POST per document when BOTH sides arrive. The outbound payload contains:

  • Tenant identifier (your tenant-side siteName from the binding row)

  • Warehouse identifier

  • Document identifier + open/close timestamps

  • Per-task entries with: task code, SKU code, planned qty, actual picked qty (> 0), operator identifier, container code

Inventory transitions postback pattern

A common outbound shape: per-shift-window inventory delta with per-(SKU, location) aggregated add/deduct quantities. The adapter accumulates wms.{tenant}.movement.v1 events grouped by (sku_code, location_code) over the configured shift window, then posts at window close.

The aggregation:

  • addQty = sum of positive qty_delta values for the (sku, location) pair

  • deductQty = sum of absolute values of negative qty_delta values for the same pair

Pure-add rows (addQty>0, deductQty=0) and pure-deduct rows are emitted as-is — the optional zero field is included.

Edge case discipline

These are the behavioral rules the adapter follows on outbound; design your inbound consumer (your side) with these in mind:

Short-pick handling

If actual picked qty < planned qty, the adapter posts the line with actual qty. Your WMS treats the delta as a discrepancy on your side; FG.AI’s reconciliation flow (RECON-01..07) handles the discrepancy on FG.AI’s side via the wes-contract confirmation path. Both sides converge by handling their own discrepancy independently — no cross-system reconciliation in v1.

Zero-pick handling

If a task has zero picks (e.g., bin-empty exception), the adapter filters out the task from the outbound payload entirely and emits an audit event. You will not receive zero-pick lines.

Duplicate prevention

The adapter dedups on (partner_id, source_event_id) in the per-(partner, tenant) outbound_dedup table. Multiple consumes of the same Kafka offset (after retry, JVM restart, etc.) produce one POST. Your inbound consumer should still be idempotent as defense-in-depth, but the adapter is the primary dedup layer.

Replay after DLT recovery

If outbound POSTs fail persistently (your endpoint down, credential rotation issue, sustained 5xx), they are routed to the per-(partner, tenant) DLT table. After recovery, an operator-triggered replay drives them out in order. You may see a burst of catch-up POSTs after an extended outage.

Time and unit conventions

  • Timestamps in FG.AI source events are ISO-8601 with offset (2026-05-29T08:02:14.000+09:00). If your outbound shape expects milliseconds since UNIX epoch (Java Instant.toEpochMilli()), the adapter converts. Both directions are handled by the translator code per partner.

  • Quantities are BigDecimal end-to-end. The adapter does NOT convert to double or long. Fractional pick quantities are valid for fractional UoMs (kg, liters, etc.).

  • Strings are UTF-8 JSON. Display fields may contain CJK or other non-ASCII characters. The adapter does NOT translate display fields on the outbound — you see what FG.AI stored.

Identity mapping

The adapter sits behind FG.AI’s identity-mapping layer, so the outbound payloads use partner-stable identifiers (your SKU codes, your warehouse codes, your location codes) — not FG.AI’s internal FRNs.

Identity mapping happens at FG.AI’s inbound write layer per the Phase 8 EDI primitives:

  • partner_sku_mapping (EDI-03): your SKU code → FG.AI internal sku.id FRN. The adapter sees wms.movement.v1 events with the already-mapped SKU code stored in sku.code, and emits that on outbound.

  • partner_location_mapping (EDI-03): your location code → FG.AI internal bin.id FRN. Same pattern.

  • Partner-side warehouse-code (e.g., your epCode field) is mapped via partner-registry config keyed by the FG.AI warehouse FRN. The adapter looks up your epCode from registry config and emits it on outbound.

You provide these mappings at onboarding time (see side-partner-onboarding for the bulk-import flow).

Error code envelope conventions

The adapter expects your outbound response to follow a typical error code + message envelope. Configure the adapter’s response handler at onboarding time with your specific code semantics. The adapter’s default response handling:

Class

Semantic

Adapter response

Success (2xx + your “success” code)

accepted

mark posted; emit audit event

Partial success (2xx + your “partial” code)

accepted with caveat

mark posted; log caveat; emit audit event

Param error (your “param invalid” code)

adapter sent bad shape

mark DLT; alert (likely adapter bug or schema drift)

Auth failed (401 or your “auth failed” code)

token expired or revoked

invalidate token cache; retry once with fresh token; second failure → alert + DLT

Server error (5xx or your “system error” code)

your side temporarily down

retry with backoff; eventually DLT + alert

Rate limit (429 or your equivalent)

your side throttling

back off + reduce request rate; escalate if sustained

Operational specifics around these responses live in side-partner-operations.

Operational invariants the adapter holds

The adapter is built with these invariants enforced by code + ArchUnit gates:

  • Never invents data. Missing required outbound fields cause the row to be skipped + an audit event emitted.

  • Never batches across tenants. Per-(partner, tenant) deployment; one tenant’s events never appear in another tenant’s outbound POST.

  • Never stores raw partner secrets in logs. Credentials are read from SecretsManager at startup and held in memory only.

  • Dedups on (partner_id, source_event_id). Replay-safe across retry + JVM restart.

  • Adapts to your shape, not the other way around. FG.AI does not invent endpoint families to match your spec; the adapter translates to your existing spec.