Understanding EDI 856 ASN Hierarchical Levels (HL Loops)

The 856 advance ship notice is the transaction set that confuses people longest, and the reason is always the same: the HL loop. Everything else in X12 is a flat sequence of segments. The 856 is a tree encoded in a flat sequence, and you have to reconstruct it as you read.

The hierarchy

BSN05 declares the structure. The most common value is 0001, meaning Shipment → Order → Packaging → Item:

Shipment (S)
└── Order (O)
    └── Tare / pallet (T)
        └── Pack / carton (P)
            └── Item (I)

Not every 856 uses every level. A pick-and-pack ASN might go S → O → P → I with no pallet level. A bulk shipment might be S → O → I. The partner's implementation guide decides.

A pick-and-pack 856

ST*856*0001~
BSN*00*SHIP-5521*20260801*1030*0001~
HL*1**S~
TD1*CTN25*4~
TD5**2*UPSN*M~
REF*BM*1Z999AA10123456784~
DTM*011*20260801~
N1*ST*ACME DISTRIBUTION CENTER*92*DC-04~
HL*2*1*O~
PRF*4500123456~
HL*3*2*P~
MAN*GM*00012345678000000123~
HL*4*3*I~
LIN**UP*012345678905~
SN1**24*EA~
HL*5*2*P~
MAN*GM*00012345678000000130~
HL*6*5*I~
LIN**UP*012345678912~
SN1**12*EA~
CTT*6~
SE*20*0001~

Reading HL

HL*4*3*I~
  • HL01 = 4 — this level's own ID. Increments across the whole transaction set, never resets.
  • HL02 = 3the parent's HL01. This is the pointer that builds the tree.
  • HL03 = I — the level code: Shipment, Order, Tare, Pack, Item.
  • HL04 — optional child indicator, 1 if this level has children.

So HL*4*3*I reads as: level 4 is an item, and it lives inside level 3. Level 3 was HL*3*2*P, a carton, which lives inside level 2, the order, which lives inside level 1, the shipment.

Trace the second carton the same way: HL*5*2*P — carton 5 hangs off level 2, the order. Not off carton 3. Getting this wrong is what produces ASNs where every carton nests inside the previous one, forming a chain a hundred levels deep instead of a hundred siblings.

Segments belong to the level above them

This is the part that trips up people reading raw text. There is no closing marker for an HL loop. Every segment after an HL belongs to it until the next HL appears.

So MAN*GM*000123... after HL*3*2*P is the carton label for carton 3. The SN1*​*24*EA after HL*4*3*I is the quantity for the item in that carton. Read a segment against the wrong parent and the entire shipment is misinterpreted.

MAN and the carton label

MAN*GM*00012345678000000123~

MAN01 = GM means the value is an SSCC-18 — the number encoded in the UCC-128 barcode physically stuck to that carton. This is the join between the electronic document and the physical box: the receiving dock scans the label, and the ASN tells them what should be inside.

If the MAN value doesn't match the printed label, the carton scans as unknown and the shipment gets handled manually — usually with a chargeback attached.

CTT counts HL segments, not line items

CTT*6~

In the 850 and 810, CTT01 counts line items. In the 856 it counts HL segments — six of them here, across all levels. Reusing 850 mapping logic for the 856 gets this wrong nearly every time, and the resulting file is rejected with a perfectly accurate but unhelpful error.

The checks worth automating

  • Every HL02 references an HL01 that already appeared.
  • HL01 values are unique and ascending.
  • Level codes follow the structure declared in BSN05.
  • Every pack level has a MAN if the partner requires labels.
  • CTT01 equals the HL count.
  • Item quantities roll up to the totals in TD1.

A structured tree view does most of this by rendering the hierarchy directly — a mis-parented carton becomes obvious the moment the tree is drawn, instead of after the partner rejects the file.

Related: decoding the 997 when an ASN is rejected.

Try EDI Studio free