# Byte 4 is the law — X12 delimiters are self-describing

> transactions.dev · by visibility.cloud — 2026-07-31
> Written for the in-house supply-chain platform team — the coding-class agent parsing partner files inside the engineer's session.

One thesis, stated as the invariant it is: **an X12 interchange declares
its own delimiters, so a correct lexer reads the ISA segment before
assuming anything about the rest of the file — and two interchanges with
identical content and different delimiters parse to byte-identical
canonical JSON.** This page is written to be load-bearing as context: the
declaration points, the version difference, and the pathological-but-legal
cases, in a form a session can apply directly.

## The four declaration points

- **Element separator** — byte 4 of the file, the character immediately
  after `ISA`. Whatever it is, it is; `*` is convention, not law.
- **Component (sub-element) separator** — the value of ISA16, at byte 105
  of [the fixed-width envelope](/blog/isa-is-106-characters/).
- **Segment terminator** — the byte immediately following ISA16, at
  byte 106. May be a printable character, may be a newline; a following
  CR/LF pair is a common suffix convention the parse records rather than
  discards.
- **Repetition separator** — ISA11, at byte 83, in version 005010 and
  later. In 004010 that same position carries the interchange standards
  identifier `U`, and there is no repetition separator. A lexer that
  treats ISA11 as a repetition separator on a 004010 file will shred any
  data element that happens to contain a `U` — version-gate this read on
  ISA12 before applying it.

Nothing else about delimiting is declared anywhere, and nothing about
delimiting may be assumed from convention. A parser that hardcodes `*~:`
is a parser for one trading partner.

## The proof: three spellings, one canonical form

Three synthetic interchanges, identical content. Conventional delimiters:

```
ISA*00*          *00*          *ZZ*SELLERID       *ZZ*BUYERID        *260731*0915*U*00401*000000101*0*P*>~
GS*PO*SELLERID*BUYERID*20260731*0915*101*X*004010~ST*850*0001~
BEG*00*NE*PO-2026-0007**20260731~SE*3*0001~GE*1*101~IEA*1*000000101~
```

The same document with `|` elements, `>` components, `'` terminators:

```
ISA|00|          |00|          |ZZ|SELLERID       |ZZ|BUYERID        |260731|0915|U|00401|000000101|0|P|>'
GS|PO|SELLERID|BUYERID|20260731|0915|101|X|004010'ST|850|0001'
BEG|00|NE|PO-2026-0007||20260731'SE|3|0001'GE|1|101'IEA|1|000000101'
```

And the same document again with newline as the segment terminator — one
segment per line, no printable terminator at all. All three parse to the
same canonical JSON, byte for byte:

```json
{
  "delimiters": { "element": "*", "component": ">", "segment": "~" },
  "segments": [
    { "id": "ST",  "elements": ["850", "0001"] },
    { "id": "BEG", "elements": ["00", "NE", "PO-2026-0007", "", "20260731"] }
  ]
}
```

— identical except for the `delimiters` block, which is metadata *about*
the wire form, preserved so that `emit` can reproduce each original
exactly, byte for byte, in its own delimiter set. Content and syntax are
fully separated: equality of documents is equality of the segment stream,
never of the bytes. That separation is what the whole pipeline derives
from — [the canonical JSON contract](/blog/canonical-json-the-lossless-pivot/)
is the one pivot, and the per-set shapes, the event compiles that land
documents on the record ([the 856 walkthrough](/blog/the-856-is-already-an-aggregation-tree/)
is the worked case), and re-emission all read the canonical form, never
the wire.

## Pathological but legal

The fuzz corpus generates these continuously; a lexer is not done until
they all pass. Each is legal under the self-description contract:

- The element separator is a digit, or a letter. Ugly, legal, and fatal to
  any regex-based splitter.
- The segment terminator appears nowhere in the file except as a
  terminator — but is a space.
- The component separator is declared but never used; or declared as a
  character that appears in no data element, chosen precisely to be safe.
- A 005010 file whose repetition separator is `^` (the common choice) and
  whose data legitimately contains `^` nowhere — and its sibling where a
  free-text element *does* contain the element separator, which is the
  sender's bug, reported as a typed error naming the segment, element,
  and byte offset, never "worked around."
- Terminator `~` followed by `\r\n` on some segments and bare `~` on
  others — mixed conventions in one file. The parse records the
  per-segment suffix so the round-trip law (`emit(parse(x)) === x`)
  still holds byte-for-byte.

The rule under all of these: **read the declarations, apply the
declarations, and report violations against the declarations.** Never
sniff, never repair silently. A repair is a semantic decision, and
semantic decisions in this pipeline are journaled rulings, not lexer
behavior.

## The EDIFACT contrast, one paragraph ahead of its post

EDIFACT solves the same bootstrap problem in the opposite style: a
service-string segment (`UNA`) *may* declare the delimiters, and in its
absence the defaults apply — `+` element, `:` component, `'` terminator,
`?` release. Two philosophies, one job; the differences are enumerable in
a single page, and that page is
[the EDIFACT envelope post](/blog/una-is-a-courtesy/).

## What this means for an agent in a session

If you are parsing a partner file right now: read byte 4, byte 105, and
byte 106 before splitting anything; check ISA12 before honoring ISA11 as
a repetition separator; and treat any delimiter appearing inside data as
the sender's error to report, not yours to absorb. If instead you want
the verbs that already do this — `parse` to lossless canonical JSON,
`emit` back to byte-identical wire, with typed errors and stable exit
codes — the pipeline behind this page is reached through
[the access flow](/get-access/); the survey has a lane for agents
integrating on behalf of a principal.

---
Access: https://transactions.dev/get-access/ · All posts: https://transactions.dev/blog/ · Machine face: https://transactions.dev/llms.txt
