Sales Orders¶
The Orders entity provides CRUD access to P21 sales order records. In P21, a sales order is composed of two related tables: the order header (oe_hdr) containing customer and shipping information, and one or more order lines (oe_line) containing the individual item and quantity details.
Endpoints¶
| Method | URL | Description |
|---|---|---|
GET | /entity/orders/ | Get all orders (ArrayOfOrder) |
POST | /entity/orders/ | Create a new order |
GET | /entity/orders/{companyId}_{orderNo} | Get a single order with all lines |
PUT | /entity/orders/{companyId}_{orderNo} | Update an order |
GET | /entity/orders/new | Get a blank order with defaults populated |
GET | /entity/orders/ping | Health check |
Compound Key¶
The URL key segment is {companyId}_{orderNo}:
The order number is numeric in most P21 installations.
Field Reference¶
Order Header Fields (oe_hdr)¶
| Field | Type | Description |
|---|---|---|
company_id | string | Company identifier |
order_no | int | Order number (system-assigned on create) |
customer_id | string | Customer placing the order |
order_date | date | Date order was placed |
ship_to_id | string | Ship-to address identifier |
ship_via | string | Shipping method/carrier code |
carrier_id | string | Carrier identifier |
freight_amount | decimal | Freight charge amount |
order_total | decimal | Total order value |
status | string | Order status code |
ordered_by | string | Contact name who placed the order |
po_no | string | Customer's purchase order number |
notes | string | Order-level notes |
Order Line Fields (oe_line)¶
| Field | Type | Description |
|---|---|---|
line_no | int | Line sequence number (1-based) |
item_id | string | Item/product identifier |
unit_quantity | decimal | Ordered quantity |
unit_price | decimal | Unit selling price |
extended_price | decimal | unit_quantity * unit_price |
required_date | date | Customer-requested ship date |
warehouse_id | string | Fulfilling warehouse |
allocated_qty | decimal | Quantity allocated to this line |
disposition | string | Line status/disposition code |
unit_of_measure | string | UOM for the quantity |
notes | string | Line-level notes |
Nested Structure¶
An Order object contains an OrderLines collection. The relationship mirrors the oe_hdr / oe_line table structure in the P21 database.
Order (oe_hdr)
└── OrderLines
├── OrderLine (oe_line, line_no=1)
├── OrderLine (oe_line, line_no=2)
└── OrderLine (oe_line, line_no=N)
When you GET an order, all lines are returned in the single response. When you POST or PUT, the full order including all lines is included in the payload.
Order Status Values¶
Header Status¶
| Status | Description |
|---|---|
O | Open — order is active and pending fulfillment |
C | Closed — order is fully shipped and invoiced |
H | On Hold — order is blocked from processing |
X | Cancelled — order has been cancelled |
Line Disposition Codes¶
| Disposition | Description |
|---|---|
O | Open — line is not yet fulfilled |
B | Backordered — insufficient stock; awaiting replenishment |
S | Shipped — line has been picked and shipped |
I | Invoiced — line has been invoiced |
C | Cancelled — line has been cancelled |
H | Hold — line is on hold |
Disposition vs. status
The header status and line disposition are separate fields with overlapping but distinct code sets. A single order can have lines in multiple disposition states simultaneously — for example, some lines S (shipped) while others remain B (backordered).
XML Examples¶
GET — Order with Lines¶
<Order xmlns="http://www.epicor.com/entity">
<company_id>01</company_id>
<order_no>10043</order_no>
<customer_id>C100001</customer_id>
<order_date>2025-11-01</order_date>
<ship_to_id>WAREHOUSE</ship_to_id>
<ship_via>UPS_GROUND</ship_via>
<carrier_id>UPS</carrier_id>
<freight_amount>15.00</freight_amount>
<order_total>1265.00</order_total>
<status>O</status>
<ordered_by>John Buyer</ordered_by>
<po_no>PO-2025-0441</po_no>
<OrderLines>
<OrderLine>
<line_no>1</line_no>
<item_id>WIDGET-100</item_id>
<unit_quantity>50.00</unit_quantity>
<unit_price>12.50</unit_price>
<extended_price>625.00</extended_price>
<required_date>2025-11-08</required_date>
<warehouse_id>MAIN</warehouse_id>
<allocated_qty>50.00</allocated_qty>
<disposition>O</disposition>
<unit_of_measure>EA</unit_of_measure>
</OrderLine>
<OrderLine>
<line_no>2</line_no>
<item_id>WIDGET-200</item_id>
<unit_quantity>33.00</unit_quantity>
<unit_price>18.75</unit_price>
<extended_price>618.75</extended_price>
<required_date>2025-11-08</required_date>
<warehouse_id>MAIN</warehouse_id>
<allocated_qty>20.00</allocated_qty>
<disposition>B</disposition>
<unit_of_measure>EA</unit_of_measure>
</OrderLine>
</OrderLines>
</Order>
POST — Create Order (Request Body)¶
<Order xmlns="http://www.epicor.com/entity">
<company_id>01</company_id>
<customer_id>C100001</customer_id>
<order_date>2025-11-15</order_date>
<ship_to_id>WAREHOUSE</ship_to_id>
<ship_via>UPS_GROUND</ship_via>
<ordered_by>Jane Buyer</ordered_by>
<po_no>PO-2025-0500</po_no>
<OrderLines>
<OrderLine>
<line_no>1</line_no>
<item_id>WIDGET-100</item_id>
<unit_quantity>10.00</unit_quantity>
<unit_price>12.50</unit_price>
<required_date>2025-11-22</required_date>
<warehouse_id>MAIN</warehouse_id>
<unit_of_measure>EA</unit_of_measure>
</OrderLine>
</OrderLines>
</Order>
System-assigned order number
Do not set order_no in a POST payload. P21 assigns the order number automatically. The response will include the assigned order_no.
Creating Orders — Recommended Pattern¶
Follow these steps when creating orders programmatically. Skipping the template fetch is the most common source of order creation failures.
Step 1: GET the order template
This returns a blank order document with all header defaults populated. Inspect it to understand which fields are required in your P21 configuration.
Step 2: Populate header fields
Set customer_id, order_date, ship_to_id, ship_via, ordered_by, and po_no at minimum. Leave order_no blank.
Step 3: Build the OrderLines collection
For each line item, add an OrderLine element. Set line_no sequentially starting from 1. At minimum provide item_id, unit_quantity, unit_price, required_date, and warehouse_id.
Step 4: POST the complete document
POST https://{server}/api/entity/orders/
Content-Type: application/xml
Authorization: Bearer {token}
The response body contains the created order with the system-assigned order_no.
C# Examples¶
Get an Order¶
public async Task<Order> GetOrderAsync(string companyId, int orderNo)
{
var url = $"{_baseUrl}/entity/orders/{companyId}_{orderNo}";
var response = await _httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
var xml = await response.Content.ReadAsStringAsync();
var serializer = new XmlSerializer(typeof(Order));
using var reader = new StringReader(xml);
return (Order)serializer.Deserialize(reader);
}
Create an Order¶
public async Task<Order> CreateOrderAsync(OrderCreateRequest request)
{
// Step 1: Fetch template with defaults
var templateResponse = await _httpClient.GetAsync($"{_baseUrl}/entity/orders/new");
templateResponse.EnsureSuccessStatusCode();
var templateXml = await templateResponse.Content.ReadAsStringAsync();
var serializer = new XmlSerializer(typeof(Order));
using var templateReader = new StringReader(templateXml);
var template = (Order)serializer.Deserialize(templateReader);
// Step 2: Populate header
template.customer_id = request.CustomerId;
template.order_date = request.OrderDate;
template.ship_to_id = request.ShipToId;
template.ship_via = request.ShipVia;
template.ordered_by = request.OrderedBy;
template.po_no = request.PoNo;
// Step 3: Add lines
template.OrderLines = request.Lines.Select((line, index) => new OrderLine
{
line_no = index + 1,
item_id = line.ItemId,
unit_quantity = line.Quantity,
unit_price = line.UnitPrice,
required_date = line.RequiredDate,
warehouse_id = line.WarehouseId,
unit_of_measure = line.Uom
}).ToList();
// Step 4: Serialize and POST
using var writer = new StringWriter();
serializer.Serialize(writer, template);
var xml = writer.ToString();
var content = new StringContent(xml, Encoding.UTF8, "application/xml");
var response = await _httpClient.PostAsync($"{_baseUrl}/entity/orders/", content);
response.EnsureSuccessStatusCode();
var responseXml = await response.Content.ReadAsStringAsync();
using var reader = new StringReader(responseXml);
return (Order)serializer.Deserialize(reader);
}
Notes and Common Pitfalls¶
Line numbers must be sequential
line_no values must be unique within an order and should be assigned sequentially starting from 1. Gaps in line numbering may cause unexpected behavior in P21 reporting and picking workflows.
extended_price is calculated
You do not need to calculate extended_price yourself. P21 computes it from unit_quantity * unit_price. If you supply it, it must match the calculation or you may receive a validation error.
Pricing rules and contracts
The unit_price in the order line is the actual billed price. If your P21 installation uses price contracts or customer-specific pricing, P21 may override the price you submit. Always retrieve the created order after POSTing to confirm the final prices applied.
Order updates and line state
When PUTting an order to update header fields, include all existing lines in the payload exactly as they were returned by GET. Omitting lines from a PUT payload may remove them from the order. Always fetch the current order before updating.