Skip to content

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}:

GET https://{server}/api/entity/orders/01_10043

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.


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

GET https://{server}/api/entity/orders/new

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.