Files.comExaVault

EDI in Python: Parsing X12 and Moving Documents Over AS2 or SFTP

Automation

Most Python EDI tutorials explain how to parse an 850 and barely mention how it arrived or how the 855 gets back. In production, transport is the harder problem. This covers both.

"EDI in Python" usually describes two very different jobs. The first is parsing: an X12 850 or an EDIFACT ORDERS file arrives, and you need the order lines, addresses, quantities, and prices out of it. The second is moving: that file has to be retrieved from a partner's SFTP server or delivered over AS2 with encryption, retries, and a receipt.

Most Python tutorials explain the first job and barely mention the second. In production, transport is often the harder problem. This guide covers both: how to parse EDI in Python, where the open-source libraries fit, and how to avoid building and maintaining the transport layer yourself.

What an X12 document looks like

X12 is plain text organized into segments. Each segment ends with a terminator, usually ~, and contains elements separated by a delimiter, usually *. A purchase order looks like this:

ISA*00*          *00*          *ZZ*BRIGHTWAY      *ZZ*HARBORLINE     *260616*1030*U*00401*000000123*0*P*>~
GS*PO*BRIGHTWAY*HARBORLINE*20260616*1030*123*X*004010~
ST*850*0001~
BEG*00*SA*PO123456**20260616~
N1*ST*Brightway Retail Store 214*92*0214~
PO1*1*24*EA*3.15**UP*012345678905~
CTT*1~
SE*6*0001~
GE*1*123~
IEA*1*000000123~

The envelope is predictable. ISA opens the interchange and identifies the sender, receiver, and delimiters. GS opens a functional group and ST opens a transaction set. BEG carries the purchase order header, N1 identifies a party or location, and PO1 is a line item. SE, GE, and IEA close the transaction set, group, and interchange in reverse order.

The ST segment names the document type: 850 for a purchase order, 810 for an invoice, 856 for an advance ship notice, 855 for a purchase order acknowledgment. The syntax is simple. The business rules are not.

Read the delimiters before parsing

It is tempting to assume every X12 file uses * and ~. Many do, and a parser reads the delimiters from the ISA segment instead. The ISA segment has a fixed length of 106 characters including its terminator: character 4 is the element separator, character 105 the component-element separator, and character 106 the segment terminator. In zero-based Python indexes those are 3, 104, and 105.

A minimal X12 parser in Python

If you process one document type from one trading partner, a small parser may be all you need.

def parse_x12(text):
    text = text.lstrip("\ufeff")

    if not text.startswith("ISA"):
        raise ValueError("X12 document must begin with an ISA segment")

    if len(text) < 106:
        raise ValueError("Document is too short to contain a valid ISA segment")

    element_separator = text[3]
    segment_terminator = text[105]

    segments = [
        segment.strip()
        for segment in text.split(segment_terminator)
        if segment.strip()
    ]

    return [segment.split(element_separator) for segment in segments]

Extract the PO1 order lines:

from pathlib import Path

text = Path("po.edi").read_text(encoding="ascii")

for segment in parse_x12(text):
    if segment[0] == "PO1":
        line_number, quantity, unit, price = segment[1], segment[2], segment[3], segment[4]
        print(line_number, quantity, unit, price)

For the example above that prints 1 24 EA 3.15. This is enough for a controlled integration: one transaction type, a partner with a stable implementation guide, a small set of fields, and validation and acknowledgments handled elsewhere. The important distinction is that this code tokenizes X12. It does not interpret or validate it.

Where hand-written parsers break down

Real EDI documents get more complicated than "find the PO1 segments" quickly.

Loops repeat. An N1 segment may identify a ship-to location, a bill-to party, a buyer, a seller, or a carrier, and its meaning depends on a qualifier and on the loop it sits in. Documents nest: an 856 ship notice describes shipments, orders, packages, and items in hierarchical levels, and pulling segments out without rebuilding that hierarchy produces wrong results. Partners differ: the X12 standard calls an element optional while a retailer's implementation guide requires it, and another partner rejects the same element when it appears. "Valid X12" and "accepted by this partner" are not the same thing.

Production workflows also check transaction counts, control numbers, segment counts, and totals, and they generate a 997 or 999 functional acknowledgment. An AS2 MDN is not an EDI acknowledgment: the MDN confirms the AS2 transmission was received and processed at the protocol level, while a 997, 999, or 855 says something about the document or the business transaction.

Python libraries for EDI

Three open-source projects take you past a hand-written parser.

pyx12 is the established X12 parser and validator. It validates documents against X12 maps, produces detailed errors, and converts X12 to XML. It fits when standards-based validation matters more than a lightweight extraction script; it is mature, GPL-licensed, and not fast-moving.

Bots is a complete open-source EDI translator written in Python, with mapping scripts, partner configuration, routing, communication channels, and scheduling. It is an EDI system you operate rather than a library you import.

pydifact parses and builds EDIFACT messages, for document types such as ORDERS, INVOIC, and DESADV rather than X12 transactions.

None of them removes the need to understand the document type and the trading partner's implementation guide. The library parses a segment; it cannot decide what your partner intended.

The part tutorials skip: moving the document

Parsing is one step. The application also has to retrieve the 850 from a partner, return a 997 or 999, send an 855 acknowledgment, deliver an 856 ship notice, archive the originals, retry failed transmissions, and preserve evidence of delivery. That means one or more of three transports.

SFTP: the partner gives you an account on its server or you give the partner one on yours, and a process polls a folder, downloads new files, and uploads responses. Writing a basic poller is straightforward. Operating one reliably means credential management, host-key verification, duplicate detection, retries, archiving, monitoring, and alerting.

AS2: an HTTPS-based protocol for secure business-document exchange, with signing, encryption, and Message Disposition Notifications. Large retailers frequently require it. Implementing it properly means public and private certificates, rotation and expiration, signing and verification, encryption and decryption, synchronous or asynchronous MDNs, retries and duplicates, and audit records. That is why most Python teams do not implement AS2 themselves and bolt on a separate AS2 product instead.

A VAN: a value-added network between you and every partner, which reduces the connections you manage and charges by document volume or kilocharacter.

Let Files.com handle the transport

Files.com keeps EDI transport out of the Python. A Remote Server Mount exposes a partner's SFTP server as a folder on your Files.com site; Files.com stores the credentials, can generate the key pair, pins the partner's host key, and presents the remote files through the same folder interface your application already uses. Partners who mandate AS2 connect to Files.com's built-in AS2: turn it on and each trading partner gets an inbox, an outbox, and a sent folder, outbound messages retry until delivery is confirmed, and every transmission either comes back with an MDN or shows up as a failure to act on. Partners on SFTP or FTP land in the same folder tree.

The Python application no longer needs to know whether a partner uses AS2, SFTP, or FTP. It lists folders, downloads inbound files, and uploads outbound files over HTTPS with the Files.com Python SDK:

pip3 install Files.com
import os
from pathlib import Path

import files_sdk

files_sdk.set_api_key(os.environ["FILES_API_KEY"])

inbound_dir = Path("./inbound")
inbound_dir.mkdir(exist_ok=True)

for item in files_sdk.folder.list_for("/partners/brightway/as2/inbox"):
    local_path = inbound_dir / item.display_name
    files_sdk.file.download(item.path, str(local_path))
    orders = parse_x12(local_path.read_text(encoding="ascii"))
    # Apply your business logic here.

files_sdk.file.upload_file(
    "./outbound/855_PO123456.edi",
    "/partners/brightway/as2/outbox/855_PO123456.edi",
)

Dropping the 855 into the partner's outbox starts the AS2 transmission. Files.com performs the signing, encryption, and delivery and records the MDN or the failure. Your application stays responsible for the business outcome: the right response, no duplicate processing, and what to do when a document fails.

You may not need to parse the EDI in Python either. TransformScript parses X12 and EDIFACT natively on the platform, so an incoming 850 becomes the JSON or CSV your ERP expects before Python touches it, file extraction pulls EDI fields into searchable metadata, and content validation flags a document whose contents are wrong rather than merely missing. In that design Python owns only the business logic that is specific to your company.

Production considerations

Whichever tools you choose, archive the exact bytes you received. Process idempotently, so a retry or a duplicate upload never creates a second order. Track the ISA, GS, and ST control numbers to detect duplicates and match acknowledgments. Separate transport success from business acceptance; an MDN does not mean the purchase order was valid. Validate against the partner's guide, not only the standard. Keep full EDI payloads out of application logs. Rotate AS2 certificates before they expire. And quarantine failures rather than re-processing a document that already failed validation.

Choosing the right approach

A hand-written parser is reasonable when the scope is small and stable. Use a library or a translator when you need standards-based validation, multiple document types, complex loops, or many partner-specific mappings. Treat transport as a separate decision: SFTP is manageable for a few partners, AS2 is rarely worth implementing from scratch, and with Files.com carrying the documents your Python works against one HTTPS folder interface while the platform manages partner protocols. Let Python own your business logic, not certificate rotation, SFTP polling, and AS2 receipts. The Python SFTP post covers the SFTP side in depth.

Frequently asked questions

How do I parse EDI in Python?

For X12, read the ISA segment to learn the delimiters, split the document into segments, and split each segment into elements; then pick segments by tags such as BEG, N1, and PO1. Use pyx12 when you need map-based X12 validation and pydifact for EDIFACT.

Is there a Python library for EDI?

Yes. pyx12 for X12 parsing, validation, and XML conversion; Bots for a complete Python-based EDI translation system; pydifact for EDIFACT. You still need the relevant implementation guide and the partner's rules.

How do I send EDI over AS2 from Python?

Use a dedicated AS2 product or a managed platform rather than implementing the protocol yourself. With Files.com, the Python script uploads the EDI document to the partner's outbox over HTTPS with the Python SDK, and Files.com performs the AS2 exchange and tracks the MDN.

How do I receive EDI from a partner's SFTP server in Python?

Connect directly with an SSH library and manage credentials, host keys, polling, retries, and duplicates yourself, or mount the partner's server on Files.com and read it as a folder through the Python SDK while Files.com holds the connection details.

Can Files.com translate EDI documents?

Yes. TransformScript parses and transforms X12 and EDIFACT, file extraction pulls fields into metadata, and content validation checks inside the parsed document before it reaches downstream systems.

FTP, SFTP, FTPS — in a Modern UI

Files.com is the cloud File Orchestration Platform. Bring your FTP clients; pick up a real web file manager, share links, automations, and SOC 2 / HIPAA-BAA compliance.