Bring your ERP · Epicor · read-only

Margin check for Prophet 21.

Run the Margin X-Ray on your Epicor Prophet 21 data. You pull a few tables read-only, drop them on the X-Ray, and it finds the supplier increases that never reached the price, the expired contracts still billing, the claims never filed, all inside your browser tab. Nothing is uploaded, and no login, password or key ever leaves your building.

On-premises SQL Server, Epicor-hosted, or Epicor cloud (SaaS on Azure).

Where Prophet 21 keeps it

A distributor’s file is many supplier lines, each with its own rule. These are the places Prophet 21 stores what the X-Ray reads.

Where Epicor Prophet 21 stores each piece of pricing data the X-Ray reads.
What the X-Ray readsWhere Prophet 21 keeps it
Supplier and its listinventory_supplier (supplier_id, supplier_part_no, list_price, cost); supplier for the name
Purchase discount groupinv_loc.purchase_discount_group
Price line / product groupinv_mast.product_group_id
Customer price classprice library → book → page, assigned per customer (price_library_x_cust_x_cmpy)
Quantity breaksprice_page.break1–14 with calculation_value1–15 (row_status_flag 704 = active, 700 = deleted)
Contracts / job pricingjob_price_hdr + job_price_line; vendor side job_price_vendor
Invoices and overridesinvoice_hdr + invoice_line (cogs_amount); the override flag lives on oe_line.manual_price_overide

The read-only pull

Read-only OData (the cleanest least-privilege path)

  1. Your P21 admin creates one API user with “Allow OData API Service” switched on, and nothing else.
  2. In Role Maintenance > Dataservice Permission, allow ONLY the tables below. Read-only by design: OData here cannot write.
  3. Run each request below, paging with $top / $skip (an unpaged request can return 800,000 rows in one response).
  4. Save each result as CSV with the table’s name (inv_mast.csv, inv_loc.csv …) and drop them, or one .zip of them, on the X-Ray.
OData (GET only)
# replace <your-p21> with your server; the API user’s login stays with your admin, never in a file
GET https://<your-p21>/odataservice/odata/table/inv_mast?$select=inv_mast_uid,item_id,item_desc,base_unit,product_group_id,class_id1,price1,upc_or_ean_id,delete_flag&$top=5000&$skip=0
GET https://<your-p21>/odataservice/odata/table/inv_loc?$select=inv_mast_uid,location_id,moving_average_cost,qty_on_hand,discontinued,purchase_discount_group&$top=5000&$skip=0
GET https://<your-p21>/odataservice/odata/table/inventory_supplier?$select=inv_mast_uid,supplier_id,supplier_part_no,list_price,cost,future_cost,effective_date,primary_supplier&$top=5000&$skip=0
GET https://<your-p21>/odataservice/odata/table/supplier?$select=supplier_id,supplier_name
GET https://<your-p21>/odataservice/odata/table/price_page?$select=price_page_uid,price_page_type_cd,inv_mast_uid,break1,break2,break3,break4,break5,calculation_value1,calculation_value2,calculation_value3,calculation_value4,calculation_value5,effective_date,expiration_date,row_status_flag&$top=5000&$skip=0
GET https://<your-p21>/odataservice/odata/table/job_price_hdr?$select=contract_no,customer_id,ship_to_id,start_date,end_date,approved,cancelled
GET https://<your-p21>/odataservice/odata/table/job_price_line?$select=contract_no,inv_mast_uid,product_group_id,price,multiplier&$top=5000&$skip=0
GET https://<your-p21>/odataservice/odata/table/job_price_vendor?$select=contract_no,inv_mast_uid,supplier_id,rebate_cost
GET https://<your-p21>/odataservice/odata/table/customer?$select=customer_id,customer_name,salesrep_id
GET https://<your-p21>/odataservice/odata/table/invoice_hdr?$select=invoice_no,invoice_date,customer_id,ship_to_id,salesrep_id,branch_id&$filter=invoice_date ge 2024-10-01&$top=5000&$skip=0
GET https://<your-p21>/odataservice/odata/table/invoice_line?$select=invoice_no,line_no,item_id,qty_shipped,unit_of_measure,unit_price,extended_price,cogs_amount,order_no,oe_line_no&$top=5000&$skip=0
GET https://<your-p21>/odataservice/odata/table/oe_line?$select=order_no,line_no,manual_price_overide&$top=5000&$skip=0

A few columns in the sample pack come from a second report in a real pull, and are marked illustrative: inv_mast.mfg_name, inv_mast.default_sales_unit_size, inv_mast.price1_last_changed, inventory_supplier.prior_cost, inventory_supplier.cost_last_changed, inventory_supplier.purchase_pack, job_price_line.committed_qty, customer.price_class, customer.freight_terms, customer.free_freight_min, invoice_hdr.freight_cost, oe_line.source_quote_no, oe_line.quote_expiration_date, spa_claims.csv (a rebate-claim report).

The traps the X-Ray handles

  • Soft deletes everywhere: price_page.row_status_flag 700 is deleted (in one real sample 297 of 300 pages were). The X-Ray leaves them out and tells you how many.
  • Price 1–10 are price SLOTS (often customer columns), not quantity breaks; price1 is list by convention only.
  • Three costs: cogs_amount, commission cost and other cost. Say which one you manage margin to.
  • Pricing unit is not the sales unit (pricing_unit, unit_size): normalized before any margin math.
  • OData has no server-side join: the X-Ray joins the tables in your browser on inv_mast_uid.

The X-Ray recognizes these Prophet 21 tables from their column names alone and joins them in your browser, with no column choices. Soft-deleted rows are left out and counted, never scanned.

Next: your own file

Start with the free X-Ray on your own export. When you want every leak sized against your invoices and supplier files, the Margin Diagnostic does it on your full data (from $5,000), with your Supplier Line Rulebook and a Line Review Kit for each supplier review included.

Other ERPs: SX.e / CSD · Eclipse · NetSuite · Business Central

Field names follow vendor documentation and community data dictionaries, compiled in our ERP research (October 2026). Your version may name a field differently; the X-Ray’s column matcher accepts either.