Skip to main content
One retail order, four documents. The retailer sends an 850 purchase order; you answer with a 997 (the envelope was readable) and an 855 (you accept the order), ship against it with an 856 advance ship notice, and bill it with an 810 invoice. Each outbound document is then tracked until the retailer’s own 997 names it. This guide runs that cycle against https://edi.stateset.com with one illustrative partner, acme-retail, and one PO, 4500012345. It assumes you know what the gateway is — if not, read the overview and the quickstart. The focus here is the order of calls, what each response tells you, and what to do when one does not say what you expected.

Before you start

  • A tenant API key, sent as Authorization: Bearer <key> on every request (the admin token picks a tenant with x-tenant-id, default default).
  • Your ISA qualifier and ID (ZZ / STATESET below), the retailer’s, and their X12 version.
JSON endpoints wrap results as { "success": true, "data": ... }. The outbound document endpoints do not return JSON: the body is the wire-ready X12 interchange, Content-Type: application/edi-x12.
1

Register the trading partner

A partner is one TradingPartner object, keyed by the id in the URL. Only the four ISA fields are required.
partner_isa_qualifier + partner_isa_id is how inbound documents are routed: the gateway reads the ISA sender of whatever arrives and looks up the matching partner. ack_sla_hours is the clock the reconciliation monitor runs for this partner’s 997s, overriding the global EDI_ACK_SLA_HOURS.Before the retailer sends anything real, POST /v1/partners/acme-retail/certification/run-standard runs the prebuilt retail-baseline pack against this envelope and reports passed per case.
An empty validation block means this partner’s implementation guide accepts everything — right for a first cycle; tighten it once the retailer’s spec is in hand (Configuration). Transport is configuration too: moving this partner to AS2, SFTP or a VAN changes nothing below except who carries the bytes (Transports).
2

Receive the 850 and return the 997

The interchange below is adapted from examples/po.edi in the server repository (PO number and date changed). Save it as po-4500012345.edi:
BEG*00*SA is an original, stand-alone order; PO1 is line 1, twelve each at 24.50, by seller SKU (SK). To dry-run it first, post the same body to POST /v1/edi/validate/acme-retail: the ValidationReport says valid: true or names the structural_error / per-document reason, with no side effects. Then submit it for real — POST /v1/edi/inbound auto-detects the syntax and routes by ISA sender (POST /v1/edi/inbound/{partner_id} is the route a partner-scoped key may call).
Response (abridged)
Three things happened. The PO was parsed into a PurchaseOrder850 and stored in the document ledger under the reference 4500012345 — the PO number is the handle every later document and trace uses. An edi.purchase_order.received event was queued in the durable outbox for the StateSet sequencer (events_queued: 1). And a 997 was generated, acknowledging group 101 (AK1*PO*101) and accepting transaction 0001.Over the HTTP API, returning that 997 to the retailer is your job — it is the ack_997 string. On the file and SFTP transports the worker writes the .997.edi beside the inbound file for you.
Send the same file twice and the second call is 409 Conflict: the gateway dedups on (partner, interchange control number), so retailer resends and your own retries never create a second order. Seen-index retention is covered in Configuration.
3

Acknowledge the order with an 855

The 997 said “we could read it”. The 855 says “we will fill it”, line by line. po_number, ack_date and at least one line are required. ack_type is BAK02 (AC accept, AD accept with detail, RJ reject); each line’s status is ACK01 (IA accepted, IB backordered, IQ quantity changed, IR rejected).
The response body is the enveloped X12 855 — ST*855*0001, the first this partner has been sent. Behind it the gateway applied the partner’s overlay (if any), minted ISA/GS/ST control numbers from the per-partner sequence, wrote a sent ledger record, queued the outbound event, recorded the 855 as pending in acknowledgment reconciliation, and handed it to AS2, AS1 or the outbound directory if one is configured. Over plain HTTP, delivering the body is up to you.Partial acceptance is still one 855: mark the short line IQ, or IB with status_quantity for the backordered portion. A declined order is ack_type: "RJ".
4

Ship it with an 856

shipment_id (BSN02), ship_date and orders[].po_number are required. Use orders[].cartons — each carton with its SSCC-18 and contents — for the S→O→P→I hierarchy retailers expect; orders[].items is for shipments with no carton breakdown.
Each carton becomes an HL*…*P loop with MAN*GM carrying its SSCC, so the barcode on the physical label and the ASN agree — mint those labels from the same payload with POST /v1/labels/856/acme-retail/cartons. The ASN is the document retailers charge back on most: send it when the truck leaves, and make sure the SSCCs here are the ones actually printed.
5

Invoice it with an 810

invoice_number, invoice_date, po_number, lines and total_amount are required. Freight, handling and discounts go in allowances as explicit SAC segments, not folded into unit prices.
Money fields are strings and stay exact; total_amount lands in TDS with two implied decimals. Twelve × 24.50 plus 15.00 freight is 309.00 — the gateway does not compute the total, and a total that does not foot is the classic short-pay trigger.
6

Close the loop: the retailer's 997, reconciliation, and the trace

Every outbound document is pending until the retailer’s 997 names it. GET /v1/reconciliation?outstanding=true now shows three OutboundAck rows for acme-retail — sets 855, 856, 810, each with its ST control_number and status: "pending" — and outstanding: 3 on the envelope.The retailer’s 997 arrives through the same inbound door as their 850. This one accepts the 855 (adapted from the reconciliation test in tests/http_api.rs):
Post it to POST /v1/edi/inbound exactly as you posted the 850. FA groups are never themselves acknowledged (ack_997 is null) and queue no event; the gateway matches AK2*855*0001 against the pending 855 by set + ST control number and maps AK5 to a status — Aaccepted, Eaccepted_with_errors, Ppartially_accepted, Rrejected. outstanding drops to 2.Finally, trace the chain from the PO’s point of view, and push the queued events to the StateSet sequencer now instead of waiting for the EDI_FLUSH_SECONDS worker:
The trace lists every event carrying that reference, oldest first. GET /v1/documents is the same ledger paged newest-first across partners — each id opens the raw wire, parsed payload, generated acknowledgment and any validation_issues — and GET /v1/evidence/4500012345 packages the chain as a compliance bundle when a chargeback needs answering.

Where errors surface

Reprocess and replay are idempotent: event IDs derive from (partner, ISA13, GS06, ST02), so re-deriving events from the same wire collapses in the outbox and at the sequencer.

Troubleshooting

The partner’s validation.accepted_inbound does not include 850. That list is an allow-list: once non-empty, every set not in it is rejected in the 997 with AK5*R. Add 850 (and 860 if the retailer sends changes) with another PUT /v1/partners/acme-retail, then POST /v1/documents/{id}/reprocess the stored document — it re-validates against the current guide and issues a fresh 997.
Two problems look the same here. On AS2, check GET /v1/deliveries first — a failed or long-pending delivery means the retailer never received the document, so no 997 is coming; redeliver it. If it is delivered (or the file demonstrably landed), the retailer is simply late, and ?overdue=true will say so once ack_sla_hours has elapsed.
Matching is by (partner, transaction set, ST control number) — the AK2 pair. Open the inbound 997 with GET /v1/documents/{id} and compare its AK2*855*0001 with the pending OutboundAck’s control_number. Padded or truncated control numbers, or a 997 sent under a different ISA sender than the partner record, will not match.

What you built

Every call above is also an MCP tool with the same handler behind it; the server’s edi_demo_order_to_invoice_flow tool runs this cycle in one shot — see the EDI MCP server.

Next steps

Move the partner to AS2 or SFTP

Add an as2 or SFTP configuration to the same partner record; the pipeline above does not change.

Label the cartons

One GS1-128 / SSCC-18 ZPL label per carton, minted from the same 856 payload.

Operate it

Business reconciliation, evidence bundles, deductions, monitoring and alerting.

Every transaction set

860 changes, 865 change acks, 940/945 warehouse flows, 820 remittance, and the EDIFACT pair.