> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stateset.com/llms.txt
> Use this file to discover all available pages before exploring further.

# A retailer's 850 to cash

> Follow one wholesale purchase order across the StateSet engines — the X12 850 lands at the EDI gateway, becomes a Commerce Engine order, fulfills as a durable Temporal workflow, ships back as an 856 and 810, and closes with reconciliation and applied cash.

One purchase order, four engines. A retailer, `acme-retail`, sends an X12 **850** for twelve
`SHOE-RED-10` under PO `4500123987`. The **EDI engine** translates it and emits an event; that
event becomes an order in the **Commerce Engine**; fulfillment runs as a durable **Temporal**
workflow that dispatches to the 3PL; the **856** ASN and **810** invoice go back out through EDI;
and the loop closes twice — operationally in EDI reconciliation, financially in the Commerce
Engine's accounts receivable.

Solid arrows below are API calls this guide makes. Dashed arrows are the seams — events and glue
code you own, called out honestly where they occur.

```mermaid theme={null}
sequenceDiagram
    participant R as acme-retail
    participant E as EDI engine
    participant C as Commerce Engine
    participant T as Temporal Engine
    participant P as 3PL

    R->>E: 850 purchase order (X12)
    E-->>R: 997 functional acknowledgment
    E--)C: edi.purchase_order.received (outbox event)
    C->>C: POST /api/v1/orders
    C--)T: your glue starts the workflow
    T->>P: dispatch via sync-server fulfillment tool
    P-->>T: fulfillment result
    T--)C: PATCH /api/v1/orders/{id}/ship
    E->>R: 856 ASN, then 810 invoice
    R->>E: 997s — reconciliation clears
    R--)C: payment arrives → AR application closes the invoice
```

<Note>
  Each engine has its own host and key. EDI: `https://edi.stateset.com`, `Authorization: Bearer`.
  Temporal: `https://api.workstream.stateset.com`, `x-api-key`. Sync (behind the workflow's dispatch):
  `https://api.sync.stateset.com`, `x-stateset-api-key`. The **Commerce Engine is embedded and has no
  public host today** — `https://api.stateset.com/api/v1` is the placeholder base URL the
  [Commerce API reference](/api-reference/commerce/overview) uses; substitute the host where your
  engine runs. The calls are shown against the placeholder so they match the reference.
</Note>

<Steps>
  <Step title="The 850 arrives at the EDI engine">
    The retailer's interchange (adapted from the [first 850 guide](/guides/edi-first-850) — same
    partner setup, new PO) is posted to the inbound door, which auto-detects the syntax and routes
    by ISA sender. Save it as `po-4500123987.edi`:

    ```text theme={null}
    ISA*00*          *00*          *ZZ*ACMERETAIL     *ZZ*STATESET       *260615*0900*U*00401*000000201*0*T*>~
    GS*PO*ACMERETAIL*STATESET*20260615*0900*201*X*004010~
    ST*850*0001~
    BEG*00*SA*4500123987**20260615~
    PO1*1*12*EA*24.5*PE*SK*SHOE-RED-10~
    CTT*1~
    SE*5*0001~
    GE*1*201~
    IEA*1*000000201~
    ```

    ```bash theme={null}
    curl --request POST "https://edi.stateset.com/v1/edi/inbound" \
      --header "Authorization: Bearer $STATESET_EDI_KEY" \
      --header "Content-Type: application/edi-x12" \
      --data-binary @po-4500123987.edi
    ```

    The response stores the parsed PO in the document ledger under reference `4500123987`, returns
    the **997** as `ack_997` (delivering it to the retailer over HTTP is your job), and reports
    `events_queued: 1`: an `edi.purchase_order.received` event is now sitting in the durable
    outbox. A resend of the same interchange is a `409 Conflict` — the gateway dedups on (partner,
    interchange control number), so the retailer's retries cannot create a second PO.
  </Step>

  <Step title="Cross the first seam: event out, order in">
    This seam is **not** an API call. The EDI engine does not call the Commerce Engine; it queues
    `edi.purchase_order.received` for the StateSet sequencer, and a worker flushes the outbox every
    `EDI_FLUSH_SECONDS` (or you flush it now):

    ```bash theme={null}
    curl --request POST "https://edi.stateset.com/v1/outbox/flush" \
      --header "Authorization: Bearer $STATESET_EDI_KEY"
    ```

    Your consumer of that event creates the commerce order. Key the `Idempotency-Key` to the PO
    number, so a replayed event collapses into the same order:

    ```bash theme={null}
    curl --request POST "https://api.stateset.com/api/v1/orders" \
      --header "Authorization: Bearer $STATESET_COMMERCE_KEY" \
      --header "Idempotency-Key: edi-850-4500123987" \
      --header "Content-Type: application/json" \
      --data '{
        "customer_id": "3f8a1c2e-6d94-4b7a-9e50-1c2d3e4f5a6b",
        "currency": "USD",
        "notes": "EDI 850 from acme-retail, PO 4500123987",
        "items": [
          { "product_id": "7b2e4d61-8c3f-4a95-b1e0-9f8a7c6d5e4f", "sku": "SHOE-RED-10",
            "name": "Red Shoe, size 10", "quantity": 12, "unit_price": "24.50" }
        ]
      }'
    ```

    `customer_id` is the Commerce Engine record for `acme-retail` — the mapping from ISA sender to
    customer UUID is yours to keep. The default `stock_policy` reserves what is available and
    backorders the rest; pass `reject_if_insufficient` if a wholesale PO you cannot fill should
    fail loudly instead. With the order accepted, answer the retailer with an **855**
    (`POST /v1/edi/outbound/855/acme-retail`) exactly as the
    [first 850 guide](/guides/edi-first-850) does — line-level `IA`/`IB` statuses belong there.
  </Step>

  <Step title="Fulfill it as a durable Temporal workflow">
    Two engines could plausibly own this hop. The Sync Server's own endpoints are per-tenant sync
    triggers and status writes — `POST /v1/tenants/{tenant_id}/sync/dcl` triggers a DCL order
    synchronization, and `POST /v1/tenants/{tenant_id}/orders/{order_id}/fulfillment` records a
    fulfillment status — useful, but not a durable orchestration. The Temporal Engine's
    `order-fulfillment` workflow **is**: it reserves inventory, passes an NSR gate, and dispatches
    to your 3PL *through a sync-server tool* (`fulfillment_tool`), with 8 retry attempts per
    activity, deterministic idempotency keys, and replay-safe state. So fulfillment runs on
    Temporal, and the Sync Server sits behind the workflow's dispatch activity rather than being
    called directly.

    ```bash theme={null}
    curl --request POST "https://api.workstream.stateset.com/v1/workflows/order-fulfillment/start" \
      --header "x-api-key: $STATESET_TEMPORAL_KEY" \
      --header "content-type: application/json" \
      --data '{
        "brand_id": "6f1c2a3e-9b7d-4e21-a5c8-0d2f4b6e8a10",
        "order_id": "4500123987",
        "order_total_cents": 29400,
        "currency": "USD",
        "line_items": [ { "sku": "SHOE-RED-10", "quantity": 12 } ],
        "reservation_params": { "order_id": "4500123987", "warehouse": "reno-01" },
        "fulfillment_tool": "create_order",
        "fulfillment_params": { "order_id": "4500123987", "ship_method": "ground" }
      }'
    ```

    Using the PO number as `order_id` makes the workflow id deterministic per PO —
    `order-fulfillment-{brand_uuid}-4500123987` — so a duplicate start returns the existing run
    instead of shipping twice. At $294.00 this order is under the default $500 autonomy cap and
    will not park at the review gate; poll status until it lands:

    ```bash theme={null}
    WF=order-fulfillment-6f1c2a3e9b7d4e21a5c80d2f4b6e8a10-4500123987
    curl "https://api.workstream.stateset.com/v1/workflows/order-fulfillment/$WF/status" \
      --header "x-api-key: $STATESET_TEMPORAL_KEY"
    # → "fulfilled:dispatch"
    ```

    A larger PO stops at `running:review_gate` and waits up to 7 days for
    `POST …/$WF/review` — the [durable workflow guide](/guides/temporal-first-durable-workflow)
    walks that gate, the SSE event stream, and what cancellation compensates.
  </Step>

  <Step title="Mark it shipped, send the 856">
    The workflow's terminal result carries the fulfillment payload from the 3PL — carton, SSCC and
    tracking data. Turning that result into the next two calls is your glue code (the second seam:
    the Temporal Engine does not call the Commerce or EDI engines for you). First record the
    shipment on the commerce order — omitting `lines` ships every remaining unit:

    ```bash theme={null}
    curl --request PATCH "https://api.stateset.com/api/v1/orders/$ORDER_ID/ship" \
      --header "Authorization: Bearer $STATESET_COMMERCE_KEY" \
      --header "Content-Type: application/json" \
      --data '{ "tracking_number": "794611223344" }'
    ```

    Then send the ASN through EDI, cartons and SSCCs matching the physical labels:

    ```bash theme={null}
    curl --request POST "https://edi.stateset.com/v1/edi/outbound/856/acme-retail" \
      --header "Authorization: Bearer $STATESET_EDI_KEY" \
      --header "Content-Type: application/json" \
      --data '{
        "shipment_id": "SHIP-4500123987-1",
        "ship_date": "2026-06-17",
        "carrier_scac": "FDEG", "tracking_number": "794611223344",
        "orders": [{
          "po_number": "4500123987",
          "cartons": [
            { "sscc": "000123456000000032", "items": [{ "sku": "SHOE-RED-10", "sku_qualifier": "SK", "quantity": "12", "uom": "EA" }] }
          ]
        }]
      }'
    ```

    The body that comes back is the wire-ready X12 856 (`Content-Type: application/edi-x12`),
    recorded as **pending** in acknowledgment reconciliation until the retailer's 997 names it.
    Send it when the truck leaves — the ASN is the document retailers charge back on most.
  </Step>

  <Step title="Invoice it twice: the 810 for the retailer, the AR invoice for your books">
    The 810 is what the retailer pays against. Twelve × 24.50 with no allowances is 294.00 — the
    gateway does not compute the total, and a total that does not foot is the classic short-pay
    trigger:

    ```bash theme={null}
    curl --request POST "https://edi.stateset.com/v1/edi/outbound/810/acme-retail" \
      --header "Authorization: Bearer $STATESET_EDI_KEY" \
      --header "Content-Type: application/json" \
      --data '{
        "invoice_number": "INV-2026-00812",
        "invoice_date": "2026-06-17",
        "po_number": "4500123987",
        "lines": [
          { "line_number": "1", "quantity": "12", "uom": "EA", "unit_price": "24.50",
            "ids": [{ "qualifier": "SK", "value": "SHOE-RED-10" }] }
        ],
        "total_amount": "294.00"
      }'
    ```

    The X12 document bills the retailer; it does not touch your ledger. Mirror it in the Commerce
    Engine so AR has something to age — create the invoice against the order, then **send** it,
    because `overdue` is computed from the send:

    ```bash theme={null}
    curl --request POST "https://api.stateset.com/api/v1/invoices" \
      --header "Authorization: Bearer $STATESET_COMMERCE_KEY" \
      --header "Content-Type: application/json" \
      --data '{ "customer_id": "3f8a1c2e-6d94-4b7a-9e50-1c2d3e4f5a6b", "order_id": "'$ORDER_ID'",
                "invoice_type": "standard", "payment_terms": "net30",
                "notes": "EDI 810 INV-2026-00812, PO 4500123987" }'

    curl --request POST "https://api.stateset.com/api/v1/invoices/$INVOICE_ID/send" \
      --header "Authorization: Bearer $STATESET_COMMERCE_KEY"
    ```

    Keep the 810's `invoice_number` in the commerce invoice's `notes` (as above): when the
    remittance references the X12 number, you want one search to find the AR record.
  </Step>

  <Step title="Close both loops: 997 reconciliation, then cash">
    Operationally, the 856 and 810 stay **pending** until the retailer's 997s arrive through the
    same inbound door as their 850. Watch the outstanding set shrink, and read the whole journey
    from the PO's point of view:

    ```bash theme={null}
    curl "https://edi.stateset.com/v1/reconciliation?outstanding=true" \
      --header "Authorization: Bearer $STATESET_EDI_KEY"

    curl "https://edi.stateset.com/v1/lifecycle/4500123987" \
      --header "Authorization: Bearer $STATESET_EDI_KEY"
    ```

    Financially, the loop closes when the retailer's payment is **applied**. Record the payment
    (`POST /api/v1/payments`, then `/{id}/complete` — the
    [order-to-cash guide](/guides/commerce/order-to-cash) covers it) and connect it to the invoice;
    until you do, the invoice stays open and AR aging shows a delinquent retailer who already paid:

    ```bash theme={null}
    curl --request POST "https://api.stateset.com/api/v1/ar/payment-applications" \
      --header "Authorization: Bearer $STATESET_COMMERCE_KEY" \
      --header "Content-Type: application/json" \
      --data '{ "payment_id": "'$PAYMENT_ID'",
                "applications": [{ "invoice_id": "'$INVOICE_ID'", "amount": "294.00" }] }'
    ```

    A short-pay (the retailer deducts a chargeback) is applied at the amount actually received; the
    balance ages at its true age, and `GET /v1/evidence/4500123987` on the EDI side packages the
    document chain when the deduction needs disputing.
  </Step>
</Steps>

## What you built

| Piece                                               | Engine         | Where to go deeper                                                                                                                                                                                                                    |
| --------------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Inbound 850 → 997 + `edi.purchase_order.received`   | EDI            | [Inbound](/api-reference/edi/inbound/v1-edi-inbound-create)                                                                                                                                                                           |
| Commerce order, idempotent on the PO number         | Commerce       | [Create order](/api-reference/commerce/orders/orders-create)                                                                                                                                                                          |
| One durable fulfillment run, 3PL dispatch behind it | Temporal       | [Start workflow](/api-reference/temporal/workflows/v1-workflows-order-fulfillment-start-create)                                                                                                                                       |
| Shipped order + 856 ASN with matching SSCCs         | Commerce + EDI | [Ship](/api-reference/commerce/orders/orders-by-ship-update) · [856](/api-reference/edi/outbound/v1-edi-outbound-856-by-create)                                                                                                       |
| 810 to the retailer, AR invoice on your books       | EDI + Commerce | [810](/api-reference/edi/outbound/v1-edi-outbound-810-by-create) · [Create invoice](/api-reference/commerce/invoices/invoices-create)                                                                                                 |
| Reconciliation, lifecycle trace, applied cash       | EDI + Commerce | [Reconciliation](/api-reference/edi/operations/v1-reconciliation-list) · [Lifecycle](/api-reference/edi/operations/v1-lifecycle-by-get) · [Apply payment](/api-reference/commerce/accounts_receivable/ar-payment-applications-create) |

The seams between engines — 850 event to order create, workflow result to ship/856 — are yours:
events and glue code, deliberately idempotent at every hop, never a hidden API call.

## Next steps

<CardGroup cols={2}>
  <Card title="The full EDI cycle" icon="right-left" href="/guides/edi-first-850">
    Partner onboarding, the 855, validation, TA1s, and every place an EDI error surfaces.
  </Card>

  <Card title="Durability, tested" icon="arrows-rotate" href="/guides/temporal-first-durable-workflow">
    The review gate, the SSE event stream, retries, cancellation and compensation on the same workflow.
  </Card>

  <Card title="The sync server at work" icon="shuffle" href="/guides/sync-shopify-to-netsuite">
    The engine behind `fulfillment_tool` — syncing orders, inventory and fulfillments between systems.
  </Card>

  <Card title="Commerce API reference" icon="book" href="/api-reference/commerce/overview">
    All 432 operations of the embedded engine, orders through the general ledger.
  </Card>
</CardGroup>
