PO_HEADERS_INTERFACE, PO_INTERFACE_ERRORS: Oracle PO Import
Aug 30, 2026
Aug 30, 2026
Convert a purchase order to Excel, CSV, or JSON
Submit your purchase orders
Drop documents here, or click to file
Up to 50 files per batch
Uploading...
Oracle Purchasing does not let you insert a purchase order straight into PO_HEADERS_ALL. Every programmatic load goes through four staging tables, PO_HEADERS_INTERFACE, PO_LINES_INTERFACE, PO_LINE_LOCATIONS_INTERFACE and PO_DISTRIBUTIONS_INTERFACE, and is then processed by the Purchasing Document Open Interface. Rows you insert start with PROCESS_CODE set to PENDING. After the import runs, each row ends up ACCEPTED, REJECTED or NOTIFIED, and any validation failure is written one row per bad column into PO_INTERFACE_ERRORS. This page is the field reference for that process, including the two behaviors that cost people the most time: NOTIFIED rows that never imported and were never errors, and a single header fault that silently discards every line beneath it.
Last updated August 2026. The column and status detail here describes E-Business Suite R12 Purchasing. The Oracle Fusion Cloud equivalent, which uses the same interface table names behind a file based load, is covered near the end.
The staging structure mirrors the document structure. A purchase order is a header, with lines, with shipment schedules under each line, with accounting distributions under each schedule. Each of those levels gets its own interface table, and each child points at its parent through an interface identifier rather than a real purchase order id, because at load time no purchase order exists yet.
| Interface table | Holds | Key | Points at its parent through |
|---|---|---|---|
| PO_HEADERS_INTERFACE | Supplier, currency, buyer, operating unit, document type, approval status | INTERFACE_HEADER_ID | Nothing. This is the top level. |
| PO_LINES_INTERFACE | Item, quantity, unit of measure, unit price, line type, category | INTERFACE_LINE_ID | INTERFACE_HEADER_ID |
| PO_LINE_LOCATIONS_INTERFACE | Ship to location, promised and need by dates, quantity per schedule | INTERFACE_LINE_LOCATION_ID | INTERFACE_LINE_ID |
| PO_DISTRIBUTIONS_INTERFACE | Charge account, deliver to location, requester, quantity ordered per distribution | INTERFACE_DISTRIBUTION_ID | INTERFACE_LINE_LOCATION_ID |
For a straightforward standard purchase order you can often populate only the header and lines table and let Purchasing derive a single default shipment and distribution from your setup. That shortcut works right up until you need two ship to locations on one line or a split across two cost centers, at which point the lower two tables become mandatory and the interface identifier chain has to be right all the way down.
One more table matters and is not part of the chain: PO_INTERFACE_ERRORS, which is where every validation failure lands. It is not a log file, it is a queryable table, and it is the single most useful object in this whole area.
The table has well over a hundred columns and almost all of them are optional. The short list below is what a standard purchase order load actually requires. Everything else either defaults from your supplier and organization setup or is only relevant to blanket agreements and catalog quotations.
| Column | What it is | Notes |
|---|---|---|
| INTERFACE_HEADER_ID | Your unique identifier for this header within the batch | Take it from PO_HEADERS_INTERFACE_S. Every line you want under this header repeats this value. |
| BATCH_ID | Groups the rows that belong to one import run | You choose the value. It is what you pass to the concurrent program so it picks up your rows and not somebody else's. |
| PROCESS_CODE | Row status | Insert as PENDING. The import program only picks up PENDING rows and rewrites this column when it finishes. |
| ACTION | Create, update or add to an existing document | See the section on ACTION values below. This is the column most often set wrongly. |
| DOCUMENT_TYPE_CODE | The kind of document to create | STANDARD for a standard purchase order. BLANKET and QUOTATION are the other supported paths. |
| ORG_ID | Operating unit | Must match the operating unit the concurrent request runs in, or nothing is found. |
| VENDOR_ID and VENDOR_SITE_ID | Supplier and supplier site | Resolve these before you insert. Passing a supplier name and hoping Purchasing matches it is the second most common cause of rejection. |
| CURRENCY_CODE | Document currency | A rate is required as well if it differs from the ledger currency. |
| AGENT_ID | Buyer | Must be a valid, active buyer. A terminated employee who is still in your source extract will fail here. |
| APPROVAL_STATUS | What state the created document lands in | APPROVED, INCOMPLETE or INITIATE APPROVAL. Set carefully. APPROVED skips your approval hierarchy entirely. |
| DOCUMENT_NUM | The purchase order number | Leave it null and Purchasing assigns one, provided your numbering is set to automatic. Supply it only when numbering is manual. |
| INTERFACE_SOURCE_CODE | A label for where the data came from | Not validated, but it is the only breadcrumb you will have when auditing a bad load six months later. Populate it. |
The APPROVAL_STATUS choice deserves a second look before a production load. Setting APPROVED means the imported orders are live commitments the moment the concurrent program completes, with no approval hierarchy involved. That is correct for a legacy data migration, where the orders were already approved in the old system, and wrong for almost every ongoing integration.
| Column | What it is | Notes |
|---|---|---|
| INTERFACE_LINE_ID | Unique identifier for this line | From PO_LINES_INTERFACE_S. |
| INTERFACE_HEADER_ID | The header this line belongs to | Must exactly match a row in PO_HEADERS_INTERFACE in the same batch. |
| LINE_NUM | Line number on the document | Sequential within the header. Gaps are tolerated but confuse everyone later. |
| LINE_TYPE or LINE_TYPE_ID | Goods, services, or a custom line type | Drives whether quantity and price or amount is expected. A services line validated as goods fails on the unit of measure. |
| ITEM or ITEM_ID | The inventory item | Optional for a description only line, mandatory otherwise, and validated against the item master in the correct inventory organization. |
| ITEM_DESCRIPTION | Free text description | Required when there is no item. This is the route for one off purchases. |
| UNIT_OF_MEASURE | UOM | The full unit of measure name, not the three character code, and it must be a valid conversion for the item. |
| QUANTITY | Quantity ordered | Must reconcile with the quantities you put on the shipment and distribution rows, if you supply them. |
| UNIT_PRICE | Price per unit | Validated against price tolerance where a source document or catalog price exists. See NOTIFIED below. |
| CATEGORY_ID | Purchasing category | Derived from the item when there is one, mandatory when there is not. |
The item validation is the step that fails most often on a first load, because the part number in the source system and the part number in the Oracle item master are rarely identical. Teams whose part numbers really live in a separate inventory and reorder system rather than in the ERP hit this on essentially every line until a cross reference is built, so it is worth resolving ITEM_ID up front in your staging query instead of letting the interface discover it.
PROCESS_CODE is the single column that tells you what happened to a row, and it has four values that people routinely collapse into two. You insert PENDING. The import program rewrites it. A row that is neither PENDING nor ACCEPTED did not import, but only one of the remaining values is an error.
| PROCESS_CODE | Set by | Means | What to do |
|---|---|---|---|
| PENDING | You, on insert | Waiting to be picked up. The import program selects only these rows. | Nothing. If rows are still PENDING after the program completes, your BATCH_ID or ORG_ID parameter did not match. |
| IN PROCESS | The import program | Currently being worked. Transient. | Nothing, unless it is still there after the request ends, which means the program died mid run. |
| ACCEPTED | The import program | The header and all of its successfully validated children were created in Purchasing. | Query PO_HEADERS_ALL for the resulting document. This is success. |
| REJECTED | The import program | Validation failed. Details are in PO_INTERFACE_ERRORS. | Read the errors, fix the source data, reset to PENDING and rerun. |
| NOTIFIED | The import program | Applies to blanket agreements and standard purchase orders when a supplier price catalog update exceeds your price tolerance. The line is flagged for a buyer to look at. | Review the price change and accept or reject it. Do not treat it as a defect in your load. |
NOTIFIED is the value that wastes days. It is not in PO_INTERFACE_ERRORS, so an error driven reconciliation reports nothing wrong. It is not ACCEPTED, so the document is not in Purchasing. A monitoring query that counts rejections and finds zero will declare a clean run while the rows sit in the interface table indefinitely. If you build one check on this page, make it a count grouped by PROCESS_CODE rather than a count of errors.
This is the same misreading shape that catches people in every other integration Oracle and SAP ship. A status that means "handed off" is read as a status that means "done". The EDI 997 acknowledgment code AK901 equal to A is exactly this, and so is SAP IDoc status 03. In each case the fix is the same: check for the state you actually want rather than the absence of the state you fear.
This behavior surprises people because it is not how they would have written it. If validation fails on a header row, none of its lines are processed at all. Purchasing sets PROCESS_CODE to REJECTED on the header and stops. The child rows are not individually validated, so PO_INTERFACE_ERRORS contains the header fault and nothing else.
The practical consequence is that a load can look like it has one small problem and actually have twenty. You fix the header, rerun, and now the line level validation happens for the first time and produces a fresh crop of errors on rows that were never examined. Budget for at least two rounds on any first load, and do not read the error count from round one as a measure of how much work is left.
The inverse is also worth knowing. A line level failure does not necessarily reject the whole document in every release and configuration, which means a partially loaded order is possible. Always reconcile line counts between your source extract and the created documents rather than assuming a header that reached ACCEPTED brought all of its lines with it.
Oracle's own technical reference describes PO_INTERFACE_ERRORS as containing error message information generated by the open interface programs in Purchasing, with one row for each column that has an error on an interface transaction row. That granularity is the useful part and also the part that confuses first time readers: a single bad line can produce five rows here, one per faulty column, so error counts and bad record counts are different numbers.
| Column | What it tells you |
|---|---|
| INTERFACE_TYPE | Which open interface program generated the message. Purchasing has several, so this distinguishes PDOI errors from receiving or requisition import errors. |
| INTERFACE_TRANSACTION_ID | The interface row the error belongs to. This is what you join back on. |
| TABLE_NAME | Which interface table the bad row is in, so you know whether to look at the header, line, shipment or distribution. |
| COLUMN_NAME | The specific column that failed validation. |
| ERROR_MESSAGE | The message text. Frequently a message code that needs looking up rather than a sentence. |
| BATCH_ID | The batch the failing row belonged to. |
| PROCESSING_DATE | When the message was generated, which is how you separate today's run from last week's. |
Because it is a table rather than a log, the whole reconciliation is one query. This is the query worth saving, since it answers "what happened to my batch" in a single pass:
-- 1. What state did every row in the batch end up in?
SELECT process_code, COUNT(*)
FROM po_headers_interface
WHERE batch_id = :batch_id
GROUP BY process_code;
-- 2. Every validation failure, with the column that caused it
SELECT e.table_name,
e.column_name,
e.error_message,
h.interface_header_id,
h.document_num
FROM po_interface_errors e,
po_headers_interface h
WHERE e.batch_id = :batch_id
AND e.interface_transaction_id = h.interface_header_id
ORDER BY h.interface_header_id, e.table_name;
Run the first query before the second. If it returns rows sitting at NOTIFIED or still at PENDING, the second query will come back empty and you will spend an hour looking for errors that do not exist. Oracle also ships a seeded Purchasing Interface Errors Report that formats the same content, which is the right tool to hand a functional colleague who does not have SQL access.
ACTION tells the interface whether you are creating a new document, replacing one, or adding to an existing one. It is the column where published guidance genuinely conflicts: community references and older implementation notes variously list ORIGINAL, UPDATE, ADD, NEW and REPLACE, and not all of them are valid in every release or for every document type.
The honest advice is to not take the list from any article, including this one. Check the valid values for your own release in Oracle's Electronic Technical Reference Manual for PO_HEADERS_INTERFACE, or by reading the value set the interface validates against in your instance. For a plain new standard purchase order load, ORIGINAL is the value that works across the widest range of R12 instances, and it is what most migration scripts use. If your load fails immediately on every row with an ACTION related message, that is the first place to look rather than the last.
Once the interface tables hold PENDING rows, the seeded concurrent program does the work. It is called Import Standard Purchase Orders and its short name is POXPOPDOI. Submit it from the Purchasing responsibility in the same operating unit whose ORG_ID you populated.
The parameters that matter are the document type, the approval status you want the created documents to have, the batch id, and the operating unit. Batch id is the one people leave blank. Leaving it null makes the program pick up every PENDING row in the table, including rows another integration inserted five minutes ago, which is how a test load ends up creating live purchase orders nobody expected. Always pass your own batch id.
To reprocess rejected rows after fixing the data, set PROCESS_CODE back to PENDING on the affected rows and resubmit with the same batch id. Deleting and reinserting works too, and is cleaner when the original interface identifiers have already been reused elsewhere.
Fusion keeps the interface table names but you no longer insert into them with SQL. The load is file based: you fill an FBDI spreadsheet template, generate the CSV zip, upload it through the file import and export page into UCM, and then run a scheduled process. The interface chain is unchanged, with PO_HEADERS_INTERFACE referenced by PO_LINES_INTERFACE, which is referenced by PO_LINE_LOCATIONS_INTERFACE, which is referenced by PO_DISTRIBUTIONS_INTERFACE.
| Import Orders parameter | What it does |
|---|---|
| Procurement BU | The business unit whose documents are imported. The Fusion equivalent of getting ORG_ID right. |
| Default Buyer | Used for any row where the buyer was not supplied in the file. |
| Approval Action | Do Not Approve leaves documents incomplete, Submit for Approval routes them, Bypass Approval opens them directly. The direct analogue of APPROVAL_STATUS in EBS. |
| Default Requisitioning BU | Used where the requisitioning business unit is absent from the data. |
| Import Source | Matched against the interface rows to select which set to process. |
| Batch ID | Used together with Import Source to pick the right records. The same discipline applies: always set it. |
| Create or Update Item | Whether the process may create items that do not exist. Leave it off unless you mean it. |
| Communicate Orders | Whether imported documents are sent to the supplier. Leave it off on a migration, or your suppliers receive several thousand historical orders. |
Two Fusion specific constraints are worth writing down. File based data import does not support creating or updating purchasing documents with more than 10,000 distributions, and exceeding that causes performance problems and failures rather than a clean rejection, so split large migrations by business unit or date range. And only one instance of the Import Orders process runs at a time, which the application enforces, so scheduling it every fifteen minutes does not make it faster.
Errors in Fusion come back through the process output rather than a table you can query. View the PDF output of the completed request for the list of documents created and the error detail, and the log for anything the output does not explain. That is a genuine step backwards from PO_INTERFACE_ERRORS if you are used to EBS, and it is why Fusion teams tend to validate harder before upload rather than after.
PO_HEADERS_ALL is the real table. It holds live purchase orders and is what you query for reporting. PO_HEADERS_INTERFACE is a staging table that holds candidate rows on their way in, and it is emptied or purged on a schedule. A row existing in PO_HEADERS_INTERFACE proves only that somebody inserted it, not that a purchase order exists. The full read side reference, including the join path down to lines, shipments and distributions, is in the Oracle purchase order tables guide.
Almost always because it found no rows to process. The three usual causes, in order of frequency: the BATCH_ID parameter does not match what you inserted, the concurrent request ran in a different operating unit than the ORG_ID on your rows, or PROCESS_CODE was left null instead of PENDING. A completed request with zero documents created is a selection problem, not a validation problem, and PO_INTERFACE_ERRORS will be empty because nothing was ever validated.
Everything above assumes you already have clean rows to insert. In practice that is the harder half of the project, because the purchase orders being loaded usually start life as documents rather than as database records: a customer emails an order as a PDF, a supplier sends a scanned confirmation, a legacy system exports something that is not quite a file format.
PurchaseOrders handles that first mile. It reads purchase orders that arrive as PDFs, scans or email attachments and returns the header plus every line item as clean columns, in Excel, CSV or JSON, with no template to build per supplier. Those columns map directly onto the interface tables above: order number and supplier to PO_HEADERS_INTERFACE, and one row per item with quantity, unit of measure and unit price to PO_LINES_INTERFACE. If your Oracle load is currently fed by somebody retyping documents into a spreadsheet, the purchase order to Oracle page covers the mapping, and bulk purchase order upload covers doing a migration's worth in one pass.
Stop retyping purchase orders
Upload a PDF, scan, or photo of any PO and get clean Excel, CSV, or JSON line items in seconds.
Try it free25 pages free. No credit card required.