Skip to content
SyncMyShop

Odoo and EDI

Odoo EDI integration

What Odoo calls EDI is electronic invoicing: UBL, CII and Peppol, with Odoo as its own Peppol access point. It has no X12 or EDIFACT translator, no 856 and no SSCC generator. We build the X12 side to your Odoo database: 850s into sales orders, and 855, 856 and 810 out, mapped per trading partner, hosted and run for you.

What Odoo does natively

Odoo uses the word EDI for electronic invoicing. Its documentation page "Electronic invoicing (EDI)" covers the formats per country and the Peppol network, where Odoo acts as access point and SMP itself; Peppol registration is free and available in Community. The modules in Odoo 19 follow that line: account_edi, account_edi_ubl_cii (E-FFF, UBL BIS 3, EHF3, NLCIUS, Factur-X, XRechnung), account_peppol, around forty country modules, and two for orders: sale_edi_ubl imports purchase orders in UBL BIS 3, purchase_edi_ubl_bis3 exports and imports them on the purchase side.

A search of the Odoo 19 documentation index on 3 October 2026 returns no page for X12, EDIFACT or SSCC. The list of countries eligible for Peppol registration in Odoo has 42 entries, all European; the United States is not on it. For a supplier to Walmart, Target or Home Depot the native part is therefore the records an integration writes to and reads from:

Native part Odoo model or field What an EDI integration uses it for
Sales orders sale.order, client_order_ref The 850, and the PO number every later document returns
Delivery contacts res.partner of type delivery, ref One contact per retailer DC or store
Barcodes per product and per unit product.product.barcode, product.uom.barcode Resolving UPC or GTIN-14 in PO1
Deliveries and packages stock.picking, stock.package Source of the 856
Invoices account.move from the order Source of the 810
GS1 barcode rules barcodes_gs1_nomenclature Reads an SSCC scan with AI (00); does not create one

This page is the Odoo side of our EDI integration. How Odoo exposes data in general is on the Odoo system page.

What sits between Odoo and your trading partner

Document Direction Odoo record Source of the content
850 In sale.order The partner's file
855 Out sale.order Quantity per line after reservation
856 Out stock.picking with its packages Lines, packages, carrier, carrier_tracking_ref
810 Out account.move Invoiced lines, tax, freight
846 Out product.product free_qty per warehouse
997 Both None Kept in the integration per document

The mapping of the 850 uses PO 4500018236 from our document pages: 24 units of item 10482 and 16 of item 10499 for Harbor Retail DC 07.

An X12 850 mapped to an Odoo 19 sales order Purchase order 4500018236 from Harbor Retail mapped to sale.order fields in Odoo 19. The PO number goes to client_order_ref, the buyer and the ship-to DC resolve to a customer and a delivery contact, the delivery date to commitment_date, and each PO1 line resolves through its UPC to a product with quantity, unit and price. The unit field is product_uom_id since Odoo 19. X12 850, PO 4500018236 Odoo 19, sale.order ISA06 / N1*BY 123456789 / 0001 BEG03 4500018236 N1*ST N104 0007 Harbor Retail DC 07 DTM*002 20261006 PO107 (UP) 614141104826 PO102, PO103 24, EA PO104 12.50 partner_id 4711 from a partner table client_order_ref 4500018236 partner_shipping_id 4718 delivery contact, ref 0007 commitment_date 2026-10-06 order_line.product_id 881 found by barcode product_uom_qty 24 product_uom_id Units product_uom up to Odoo 18 price_unit 12.50 lookup lookup date to UTC barcode EA to Units
Line 1 of the shared example order in Odoo 19. Three of the seven values are lookups, and the unit field changed name in Odoo 19.

The order is written over the JSON-2 API in two calls, create and then action_confirm, after the lookups for customer, delivery contact and products:

POST /json/2/sale.order/create HTTP/1.1
Authorization: bearer 7c1d...a940
Content-Type: application/json; charset=utf-8

{"vals_list": [{"partner_id": 4711, "partner_shipping_id": 4718,
  "client_order_ref": "4500018236", "commitment_date": "2026-10-06 04:00:00",
  "order_line": [[0, 0, {"product_id": 881, "product_uom_qty": 24, "price_unit": 12.50}],
                 [0, 0, {"product_id": 894, "product_uom_qty": 16, "price_unit": 8.75}]]}]}

commitment_date is a datetime that Odoo stores in UTC. Midnight on 6 October in Columbus, Ohio, during daylight saving time is 04:00 UTC; sent as 00:00:00 it lands on the evening of the 5th in the warehouse's view.

The technical reality of this pair

The Custom plan. Odoo states that data access through the external API is only available on Custom plans. Without it there is no route for an outside integration.

Keys that expire. Since Odoo 19 an API key for a normal internal user expires after at most 90 days. The integration renews its own key with res.users.apikeys/generate before the expiry date; a key that lapses stops every 850 at once.

Two transactions per order. Every JSON-2 call runs in its own database transaction, and calls cannot be chained. When create succeeds and action_confirm fails, a quotation with the PO number stays behind. Odoo does mark a draft order whose customer and client_order_ref match another order in duplicated_order_ids, but it does not block it, and the check covers only drafts. The integration looks up client_order_ref for that customer before every retry and confirms the existing quotation instead of creating a second one.

A morning file. One 850 takes about four calls: customer and delivery contact, products, create, action_confirm. A retailer that sends 400 purchase orders at 06:00 causes 1,600 calls. Odoo documents no request limit per minute, so the pace depends on the hosting and the installed apps. The integration measures it on your database, queues the orders and returns the 997 when the file has been read, not when the last order is confirmed.

What Odoo and X12 store differently

X12 needs In Odoo 19 What the integration does
An SSCC per carton, 18 digits with a check digit Package names from a sequence, PACK0000001 by default; valid_sscc only checks a name Generates the SSCC and writes it as the package name
Pallet, carton and item levels in the 856 parent_package_id nests packages since Odoo 19 Builds the HL hierarchy from the package tree
The retailer's item number in PO1 Barcode on the variant or per unit; no customer product codes A table of the retailer's numbers per variant
Cases of 12 Units relative to a reference unit (relative_factor); packagings removed in Odoo 19 A case unit with its own GTIN-14 in product.uom
The invoice for what shipped invoice_policy defaults to ordered quantities for goods Sets delivered quantities for EDI items, checks the 810 against the 856

The SSCC. A package type can have its own sequence, but a sequence counts up with a prefix and padding; it does not compute the GS1 check digit. An SSCC built from the GS1 company prefix needs that digit, and Amazon, for example, rejects a number reused within 365 days. The SSCC label page explains the structure.

The invoice policy. With the default policy an invoice bills the ordered quantities. For PO 4500018236 that is 24 × 12.50 plus 16 × 8.75, 440.00, against 405.00 for the 24 and 12 units that shipped. With the 2% discount and 18.50 freight the 810 would say 449.70 instead of 415.40, and a retailer that matches the invoice against the ship notice rejects it or deducts the difference.

Units and barcodes. A retailer that orders 2 CA means 24 units. In Odoo 19 a case is a unit with relative_factor 12 against Units, and its GTIN-14 sits in product.uom, where Odoo enforces that one barcode belongs to one product and unit. The units of measure note covers the general case.

When it goes wrong

What happens What the integration does
The API key nears its expiry date Renews it and revokes the old key
action_confirm fails after create Retries on the existing quotation, found by client_order_ref
A UPC matches no product, or a DC code no delivery contact Holds the order with the line or address; the rest of the file is written
An 856 carries a package name that is not a valid SSCC Holds the 856 before it is sent
No 997 from the partner within its window An alert with the control number that is waiting

We build the integration to your Odoo database and your trading partners, host it and run it, for a fixed build price and a monthly fee. A new partner starts as a dry run that writes nothing to Odoo and sends nothing, and you approve the first documents. When a partner publishes a new guide or Odoo renames a field in an upgrade, we change the integration. The same work on other ERPs is under NetSuite EDI integration and Acumatica EDI integration. Tell us your Odoo version and partners.

Frequently asked questions

Does Odoo have built-in EDI?

Not for retail X12 or EDIFACT. Odoo's EDI documentation covers electronic invoicing: UBL BIS 3, Factur-X, XRechnung, NLCIUS and the country formats, sent through Peppol with Odoo as access point. The module sale_edi_ubl imports purchase orders in UBL BIS 3. A search of the Odoo 19 documentation returns nothing for X12, EDIFACT or SSCC.

Which Odoo plan does an EDI integration need?

The Custom plan. Odoo's documentation says access through the external API is only available on Custom plans, not on One App Free or Standard. An EDI integration reads and writes sales orders, deliveries and invoices from outside, which is external API use.

Can Odoo receive EDI orders through Peppol?

Odoo's Peppol support is for invoices and credit notes out and vendor bills in, in BIS Billing 3.0, XRechnung and NLCIUS. Orders in UBL can be imported by uploading the file in the sales order list. A US retailer sends X12 over AS2, SFTP or a VAN, none of which Odoo reads.

Does it work on Odoo Online?

Yes. The integration uses only the external API and the standard models, so no custom module is needed. On Odoo.sh or a self-hosted server a small module can be added, for example a field for the retailer's department number on the sales order.

What happens to the integration when we upgrade Odoo?

Field and model names change between major versions. Odoo 19 renamed the order line fields tax_id to tax_ids and product_uom to product_uom_id, replaced stock.quant.package with stock.package and removed product packagings. Before an upgrade the integration runs as a dry run against a copy of the database.

Tell us what you need

Tell us which systems you use and what needs to work better. We reply personally within one working day. We help you define the work, then agree the price and timing before you commit.

  • You know the price and the timeline before you commit to anything
  • We work out the connection that takes the work off your hands
  • We handle the build, hosting, monitoring and maintenance
  • Your team has less information to retype and fewer discrepancies to chase

Suppliers, products, orders per day, a rough estimate is fine.

Or email directly: carsten@syncmyshop.com