Purchase Orders

API Reference

Complete guide to the Purchase Order API in Omnium. Learn how to create, manage, and fulfill purchase orders, handle deliveries and goods reception, and integrate with your procurement workflows.

Introduction

Purchase orders in Omnium represent orders placed with suppliers to replenish inventory. They support the full procurement lifecycle — from creating orders based on reorder suggestions, through supplier confirmation, to goods reception at the warehouse.

Purchase orders can also represent internal transfers between warehouses, where one warehouse acts as the supplier for another.

Purchase order lifecycle

┌──────────────┐     ┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│     New      │────►│  Confirmed   │────►│  InProgress  │────►│  Completed   │
│              │     │              │     │              │     │              │
│ Create PO    │     │ Send to      │     │ Awaiting     │     │ All goods    │
│ Add lines    │     │ supplier     │     │ delivery     │     │ received     │
│ Validate     │     │ Set ETA      │     │              │     │              │
└──────┬───────┘     └──────┬───────┘     └──────┬───────┘     └──────────────┘
       │                    │                    │
       │  ┌──────────────┐  │                    │
       └─►│   Canceled   │◄─┘                    │
          │              │◄──────────────────────┘
          │ PO no longer │
          │ needed       │
          └──────────────┘

A purchase order can be canceled from any status that is not marked as completed or read-only.

How purchase orders connect to other entities

┌─────────────────────┐
│   Supplier          │ ◄─── SupplierId on PO
│   (or Warehouse)    │
└─────────┬───────────┘
          │ supplies

┌─────────────────────┐       ┌─────────────────────┐
│   Purchase Order    │──────►│   Delivery          │
│                     │       │                     │
│ • Line items        │       │ • Goods reception   │
│ • Requested ETA     │       │ • Tracking info     │
│ • Supplier info     │       │ • Invoice details   │
│ • Tags & properties │       │ • Received qtys     │
└─────────┬───────────┘       └─────────┬───────────┘
          │ reserves                     │ updates
          ▼                              ▼
┌─────────────────────┐       ┌─────────────────────┐
│   Sales Orders      │       │   Inventory         │
│                     │       │                     │
│ Orders waiting for  │       │ Stock levels updated │
│ incoming stock      │       │ on goods reception  │
└─────────────────────┘       └─────────────────────┘

Base URL and authentication

All purchase order endpoints are available at:

https://api.omnium.no/api/PurchaseOrders

Endpoints require one of two authorization roles:

RoleAccess
PurchaseOrderReadGroupsGET endpoints, adding comments
PurchaseOrderAdminGroupsAll write operations (POST, PUT, PATCH, DELETE)

Purchase order model

The OmniumPurchaseOrder model is used for creating, updating, and retrieving purchase orders. Below are the most important properties grouped by usage.

Key properties

PropertyTypeDescription
IdstringUnique purchase order ID. Auto-generated if not provided on creation.
StatusstringCurrent status (must match a configured purchase order status)
SupplierIdstringID of the supplier
SupplierNamestringSupplier display name
StoreIdstringReceiving warehouse/store ID
RequestedDeliveryDateDateTime?When you need the goods delivered
PurchaseOrderFormobjectContains line items and totals
IsDeliveryboolWhen true, a delivery entity is created/synced with this PO
DeliveryIdstringID of the associated delivery

Financial properties

PropertyTypeDescription
SubTotaldecimalSum of all line item totals
SubTotalExclTaxdecimalSubtotal excluding tax
TaxTotaldecimalTotal tax amount
TotaldecimalGrand total including tax
TotalExclTaxdecimalGrand total excluding tax
BillingCurrencystringCurrency code (e.g., "NOK", "EUR")

Supplier and purchaser

PropertyTypeDescription
SupplierIdstringSupplier identifier
SupplierNamestringSupplier name
SupplierEmailstringSupplier contact email
SupplierPhonestringSupplier contact phone
SupplierWarehouseCodestringSet for internal transfers — the warehouse supplying the goods
PurchaserIdstringID of the person who created the PO
PurchaserNamestringName of the person who created the PO

Metadata

PropertyTypeDescription
PurchaseOrderNumberstringHuman-readable PO number
ReferenceNumberstringExternal reference (e.g., supplier's order number)
NamestringOptional name/description for the PO
OriginstringWhere the PO was created from
MarketIdstringAssociated market
TagsList<string>Tags for filtering and workflow routing
PropertiesList<OmniumPropertyItem>Custom key-value properties
ExternalIdsList<OmniumExternalId>IDs from external systems (ERP, WMS, etc.)
ErrorsList<OmniumEntityError>Validation errors on the PO
VersionIdstringOptimistic concurrency version
CreatedDateTime?Creation timestamp
ModifiedDateTime?Last modification timestamp

Comments

PropertyTypeDescription
PurchaseOrderNotesList<OmniumComment>Internal comments (visible only to your team)
PublicPurchaseOrderNotesList<OmniumComment>Public comments (can be sent to the supplier)

Purchase order line model

Each purchase order contains a PurchaseOrderForm with a list of LineItems. A line item represents a product being ordered from the supplier.

Essential line properties

PropertyTypeDescription
LineItemIdstringUnique line ID (auto-generated if not provided)
CodestringProduct SKU
QuantitydecimalQuantity to order
PlacedPricedecimalUnit cost price including tax
PlacedPriceExclTaxdecimalUnit cost price excluding tax
CurrencystringPrice currency

Quantity tracking

These fields track the fulfillment progress of each line:

┌─────────────────────────────────────────────────────┐
│                    Quantity: 100                     │
│                                                     │
│  ┌──────────────┐  ┌──────────┐  ┌───────────────┐ │
│  │ Delivered: 60│  │Cancel: 10│  │ Remaining: 30 │ │
│  └──────────────┘  └──────────┘  └───────────────┘ │
└─────────────────────────────────────────────────────┘
PropertyTypeDescription
QuantitydecimalTotal quantity ordered
DeliveredQuantitydecimalQuantity received via goods reception
CanceledQuantitydecimalQuantity that has been canceled
CountedQuantitydecimalQuantity verified during counting

Product information

PropertyTypeDescription
DisplayNamestringProduct display name
GtinstringGlobal Trade Item Number (barcode)
SizestringProduct size
ColorstringProduct color
BrandstringProduct brand
ImageUrlstringProduct image URL
SupplierSkuIdstringSupplier's own SKU reference

Pricing

PropertyTypeDescription
PlacedPricedecimalUnit price (before discounts)
PlacedPriceExclTaxdecimalUnit price excluding tax
ExtendedPricedecimalTotal line price (quantity x price, with discounts)
ExtendedPriceExclTaxdecimalExtended price excluding tax
DiscountedPricedecimalPrice after line-level discounts
LineItemDiscountAmountdecimalDiscount amount
TaxRatedecimalTax rate percentage
TaxTotaldecimalTax total for the line

Delivery and warehouse

PropertyTypeDescription
DeliveryIdstringID of the delivery this line belongs to
EstimatedTimeOfArrivalDateTime?Expected arrival date for this line
WarehouseCodestringReceiving warehouse (if different from the PO's store)
LocationstringSpecific inventory location within the warehouse
UpdateStockboolWhether goods reception should update inventory
UpdateCostPriceboolWhether to update the product's cost price on receipt

Packaging and units

PropertyTypeDescription
UnitstringUnit of measurement
SupplierPackagingQuantitydecimal?D-Pack quantity (supplier's package size)
AllowSupplierPackageBreakbool?Whether partial packages can be ordered
SelectedUnitstringSelected unit if product supports multiple units
SelectedUnitConversionFactordecimal?Conversion factor from default unit
SelectedUnitQuantitydecimal?Quantity expressed in the selected unit
IsPackageboolWhether this is a bundled/package product
ComponentsList<OmniumProductComponent>Component products in a package

Virtual warehouse allocations

For warehouses with virtual stock locations (VSL), line items can be pre-allocated to specific locations:

PropertyTypeDescription
VirtualWarehouseAllocationsList<OmniumVirtualWarehouseAllocation>Allocations to virtual stock locations
AllocateBasedOnRulesboolAuto-allocate to VSLs using inventory rules
IsDisabledForAllocationboolExclude this line from allocation

Each OmniumVirtualWarehouseAllocation contains:

PropertyTypeDescription
IdstringAllocation ID
WarehouseCodestringVirtual warehouse code
QuantitydecimalAllocated quantity
DeliveredQuantitydecimalQuantity delivered to this location

Creating a purchase order

POST /api/PurchaseOrders

Creates a new purchase order. If Id is not provided, Omnium generates one automatically.

Minimal example

The simplest purchase order needs a supplier, a receiving store, and at least one line item:

{
  "supplierId": "supplier-acme",
  "supplierName": "Acme Supplies",
  "storeId": "warehouse-central",
  "billingCurrency": "NOK",
  "status": "New",
  "requestedDeliveryDate": "2026-03-15T00:00:00Z",
  "purchaseOrderForm": {
    "lineItems": [
      {
        "code": "WIDGET-001",
        "displayName": "Standard Widget",
        "quantity": 500,
        "placedPrice": 12.50,
        "placedPriceExclTax": 10.00,
        "taxRate": 25,
        "currency": "NOK"
      },
      {
        "code": "WIDGET-002",
        "displayName": "Premium Widget",
        "quantity": 200,
        "placedPrice": 25.00,
        "placedPriceExclTax": 20.00,
        "taxRate": 25,
        "currency": "NOK"
      }
    ]
  }
}

Response: Returns the created OmniumPurchaseOrder with auto-generated Id, LineItemId values, and calculated totals.

Creating an internal transfer

Internal transfers move stock between your own warehouses. Set SupplierWarehouseCode to the source warehouse:

{
  "supplierWarehouseCode": "warehouse-central",
  "storeId": "store-oslo",
  "status": "New",
  "purchaseOrderForm": {
    "lineItems": [
      {
        "code": "WIDGET-001",
        "quantity": 50,
        "placedPrice": 12.50,
        "placedPriceExclTax": 10.00,
        "taxRate": 25,
        "currency": "NOK"
      }
    ]
  }
}

Creating with a delivery

When IsDelivery is set to true, Omnium automatically creates a Delivery entity linked to the purchase order. This enables goods reception tracking:

{
  "supplierId": "supplier-acme",
  "storeId": "warehouse-central",
  "status": "New",
  "isDelivery": true,
  "purchaseOrderForm": {
    "lineItems": [
      {
        "code": "WIDGET-001",
        "quantity": 500,
        "placedPrice": 12.50,
        "placedPriceExclTax": 10.00,
        "taxRate": 25,
        "currency": "NOK"
      }
    ]
  }
}

Retrieving purchase orders

Get a single purchase order

GET /api/PurchaseOrders/{purchaseOrderId}

Returns the full purchase order with all line items, properties, and metadata.

Get extended purchase order

GET /api/PurchaseOrders/Extended/{purchaseOrderId}

Returns the purchase order along with related store and supplier details, plus extended line items that include customer order references. This is useful when you need to see which customer orders are waiting for items on this PO.

Response structure:

{
  "purchaseOrder": { /* full OmniumPurchaseOrder */ },
  "supplier": { /* OmniumSupplier with contact info */ },
  "store": { /* OmniumStore with warehouse details */ },
  "purchaseOrderLines": [
    {
      "purchaseOrderLine": { /* line item details */ },
      "customerOrderNumber": "ORD-12345"
    }
  ]
}

Search purchase orders

POST /api/PurchaseOrders/Search

Search and filter purchase orders with pagination. Supports text search, status filtering, date ranges, and more.

Example: Find all confirmed POs from a specific supplier

{
  "supplierId": "supplier-acme",
  "selectedStatuses": ["Confirmed", "InProgress"],
  "take": 20,
  "page": 0,
  "sortOrder": "CreatedDescending"
}

Example: Find POs containing a specific product

{
  "productNumbers": ["WIDGET-001", "WIDGET-002"],
  "selectedStatuses": ["New"],
  "take": 50,
  "page": 0
}

Example: Find POs by date range and tags

{
  "createdFrom": "2026-01-01T00:00:00Z",
  "createdTo": "2026-01-31T23:59:59Z",
  "tags": ["Urgent"],
  "take": 100,
  "page": 0
}

Response:

{
  "totalHits": 42,
  "result": [
    { /* OmniumPurchaseOrder */ },
    { /* OmniumPurchaseOrder */ }
  ]
}

Available search parameters

ParameterTypeDescription
SearchTextstringFree-text search across PO fields
OrderNumberstringSearch by PO number
EmailstringSearch by supplier email
PhonestringSearch by supplier phone
SupplierIdstringFilter by supplier ID
SupplierNamestringFilter by supplier name
SupplierWarehouseCodestringFilter by supplier warehouse (internal transfers)
OrderTypestring"PurchaseOrder" or "InternalTransfer"
SelectedStatusesList<string>Filter by one or more statuses
SelectedStoresList<string>Filter by receiving store names
ProductNumbersList<string>Filter by product SKUs
TagsList<string>Include POs with these tags
ExcludedTagsList<string>Exclude POs with these tags
ExternalIdsList<string>Search by external ID values
CreatedFrom / CreatedToDateTime?Creation date range
ModifiedFrom / ModifiedToDateTime?Modification date range
RequestedDeliveryDateFrom / RequestedDeliveryDateToDateTime?Delivery date range
UrgentboolOnly include urgent POs
TakeintPage size
PageintPage number (0-based)
SortOrderstringSort order (see below)

Sort order options

ValueDescription
IdAscending / IdDescendingSort by purchase order ID
StatusAscending / StatusDescendingSort by status
SupplierAscending / SupplierDescendingSort by supplier name
CreatedAscending / CreatedDescendingSort by creation date
EtaAscending / EtaDescendingSort by estimated delivery date
ReferenceNumberAscending / ReferenceNumberDescendingSort by reference number

Scroll through large result sets

For processing large volumes of purchase orders, use the scroll API instead of paginated search. The scroll API maintains a cursor for efficient iteration.

Step 1: Initialize the scroll

POST /api/PurchaseOrders/Scroll

Request body is the same as the search endpoint (but Page is ignored).

Step 2: Continue scrolling

GET /api/PurchaseOrders/Scroll/{scrollId}

Use the scrollId from the previous response to get the next batch. Continue until the result set is empty.

Response:

{
  "scrollId": "DXF1ZXJ5QW5kRmV0Y2g...",
  "totalHits": 5000,
  "result": [
    { /* OmniumPurchaseOrder */ }
  ]
}

Updating purchase orders

Omnium provides three update strategies depending on your needs:

┌─────────────────────────────────────────────────────────────────┐
│                     Update Strategies                           │
├─────────────────────┬───────────────────┬───────────────────────┤
│       PUT           │      PATCH        │  UpdateStatus (POST)  │
│                     │                   │                       │
│ Full replacement    │ Partial update    │ Change status and     │
│ of the PO           │ of specific       │ run workflow steps    │
│                     │ fields only       │                       │
│ Use when you have   │ Use when you need │ Use when advancing    │
│ the complete PO     │ to change a few   │ the PO through its    │
│ model               │ properties        │ lifecycle             │
└─────────────────────┴───────────────────┴───────────────────────┘

Full update (PUT)

PUT /api/PurchaseOrders?updateAtpValues={true|false}

Replaces the entire purchase order. You must send the complete model. Uses a resource lock (30 seconds) to prevent concurrent updates.

Set updateAtpValues=true if you want Omnium to recalculate Available-to-Promise values for affected inventories.

Returns 409 Conflict if another update is in progress.

Partial update (PATCH)

PATCH /api/PurchaseOrders/{purchaseOrderId}?updateAtpValues={true|false}

Updates only the fields you provide. Unset fields remain unchanged.

Example: Update supplier reference and requested delivery date

{
  "referenceNumber": "SUPP-REF-2026-001",
  "requestedDeliveryDate": "2026-04-01T00:00:00Z"
}

Example: Add a new line item to an existing PO

By default, KeepExistingOrderLines is true, meaning new lines are merged with existing ones:

{
  "purchaseOrderForm": {
    "lineItems": [
      {
        "code": "WIDGET-003",
        "displayName": "Economy Widget",
        "quantity": 1000,
        "placedPrice": 7.50,
        "placedPriceExclTax": 6.00,
        "taxRate": 25,
        "currency": "NOK"
      }
    ]
  }
}

Example: Replace all line items

Set KeepExistingOrderLines to false to replace the entire line item list:

{
  "keepExistingOrderLines": false,
  "purchaseOrderForm": {
    "lineItems": [
      {
        "code": "WIDGET-001",
        "quantity": 750,
        "placedPrice": 11.00,
        "placedPriceExclTax": 8.80,
        "taxRate": 25,
        "currency": "NOK"
      }
    ]
  }
}

Patch merge behavior

PropertyDefaultBehavior
KeepExistingListItemstruetrue: merge new properties with existing. false: replace the entire properties list.
KeepExistingOrderLinestruetrue: merge new lines (matched by LineItemId) with existing. false: replace all lines.

Update and run workflow

POST /api/PurchaseOrders/UpdateAndRunWorkflow?updateAtpValues={true|false}

Updates the purchase order and executes the workflow steps configured for the current status. Use this when you need to both modify data and trigger actions (e.g., export to ERP, send notifications).

Request body: Full OmniumPurchaseOrder

Response: OmniumPurchaseOrderWorkflowExecutionResult (see Workflow execution results)

Update status

POST /api/PurchaseOrders/{purchaseOrderId}/UpdateStatus

Changes the purchase order status and runs the workflow steps configured for the new status. This is the primary endpoint for advancing a PO through its lifecycle.

Uses a resource lock (300 seconds) to ensure workflow steps complete without interference.

Example: Confirm a purchase order

{
  "status": "Confirmed",
  "expectedDeliveryDate": "2026-03-20T00:00:00Z"
}

Response:

{
  "isAborted": false,
  "purchaseOrder": { /* updated OmniumPurchaseOrder */ },
  "purchaseOrderActionExecutionResults": [
    {
      "actionType": "ExportPurchaseOrder",
      "actionStatus": "Success",
      "translateKey": "ExportPurchaseOrder",
      "cancelWorkflow": false,
      "isInvisible": false
    },
    {
      "actionType": "SetExpectedDeliveryDate",
      "actionStatus": "Success",
      "translateKey": "SetExpectedDeliveryDate",
      "cancelWorkflow": false,
      "isInvisible": false
    }
  ]
}

Bulk update

PUT /api/PurchaseOrders/UpdateMany

Updates multiple purchase orders at once. Send a list of complete OmniumPurchaseOrder objects.


Workflow execution results

When you use the status update or workflow endpoints, the response includes detailed results for each workflow step that was executed.

Result model

PropertyTypeDescription
IsAbortedbooltrue if the workflow was aborted due to an error
PurchaseOrderOmniumPurchaseOrderThe updated purchase order after workflow execution
PurchaseOrderActionExecutionResultsListResults for each workflow step

Action execution result

PropertyTypeDescription
ActionTypestringThe workflow action that was executed
ActionStatusstring"Success", "Warning", or "Error"
ErrorReasonstringError description (if the step failed)
TranslateKeystringTranslation key for the result message
CancelWorkflowboolWhether this step caused the workflow to abort
IsInvisibleboolWhether the result should be hidden from end users

If IsAborted is true, the purchase order status is reverted to its previous value. Check the PurchaseOrderActionExecutionResults to identify which step failed and why.


Line item operations

Split a line item

POST /api/PurchaseOrders/{purchaseOrderId}/OrderLines/Split

Splits one or more line items into separate lines. This is useful when part of an order needs to be shipped to a different warehouse, or when you need to track partial deliveries separately.

Before split (Line A: 100 units)
┌──────────────────────────────────────────┐
│  WIDGET-001  │  Qty: 100                │
└──────────────────────────────────────────┘

After split (Line A: 70 units, Line B: 30 units)
┌──────────────────────────────────────────┐
│  WIDGET-001  │  Qty: 70                 │
└──────────────────────────────────────────┘
┌──────────────────────────────────────────┐
│  WIDGET-001  │  Qty: 30  (new line)     │
└──────────────────────────────────────────┘

Example: Split 30 units from a line

[
  {
    "lineItemId": "line-001",
    "quantityToSplit": 30
  }
]

Example: Split with virtual warehouse allocations

When the line has VSL allocations, you can specify which allocations to move to the new line:

[
  {
    "lineItemId": "line-001",
    "quantityToSplit": 30,
    "virtualWarehouseAllocationsToSplit": [
      {
        "id": "alloc-vsl-store-b",
        "quantity": 30
      }
    ],
    "newLineItemId": "line-001-split"
  }
]

Response: Returns the updated OmniumPurchaseOrder with the split lines.

Cancel a line item

POST /api/PurchaseOrders/{purchaseOrderId}/OrderLines/{lineItemId}/Cancel

Cancels the remaining quantity on a line item. The CanceledQuantity is set to the undelivered portion. Already delivered quantities are not affected.


Comments

Purchase orders support both internal and public (supplier-facing) comments. Both endpoints support email and SMS notifications.

Add an internal comment

POST /api/PurchaseOrders/{purchaseOrderId}/AddInternalPurchaseOrderComment

Add a public comment

POST /api/PurchaseOrders/{purchaseOrderId}/AddPublicPurchaseOrderComment

Example:

{
  "body": "Please expedite shipment — customer order is urgent.",
  "subject": "Urgent delivery request"
}

Response: Returns the updated list of comments.


Deleting a purchase order

DELETE /api/PurchaseOrders/{id}

Permanently deletes the purchase order. Returns 404 if the PO does not exist.

Deletion is irreversible. Consider using the OrderCanceled status instead if you need to retain the PO for historical records.


Recalculate ATP values

POST /api/PurchaseOrders/{id}

Triggers a recalculation of Available-to-Promise (ATP) values for all inventories referenced in the purchase order. Use this after external changes to inventory or delivery dates that Omnium may not be aware of.


Working with deliveries

Deliveries represent the physical receipt of goods from a supplier. A purchase order can have one or more deliveries, and each delivery tracks which line items and quantities were received.

Delivery lifecycle

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│   Created    │────►│  In Transit  │────►│   Received   │
│              │     │              │     │              │
│ PO confirmed │     │ Goods shipped│     │ Goods receipt │
│ Delivery     │     │ by supplier  │     │ completed    │
│ created      │     │              │     │              │
└──────────────┘     └──────────────┘     └──────────────┘

Delivery model

PropertyTypeDescription
IdstringDelivery ID
WarehouseCodestringReceiving warehouse
SupplierWarehouseCodestringSource warehouse (for internal transfers)
DeliveryStatusstringCurrent delivery status
DeliveryTrackingNumberstringCarrier tracking number
DeliveryTrackingLinkstringURL to carrier tracking page
DeliveryProviderstringShipping carrier name
InvoiceNumberstringSupplier invoice number
InvoiceDateDateTime?Invoice date
EstimatedTimeOfDepartureDateTime?Expected departure from supplier
EstimatedTimeOfArrivalDateTime?Expected arrival at warehouse
IsCountedboolWhether quantities were verified before goods reception
OrderTypestring"PurchaseOrder" or "InternalTransfer"
SuppliersList<string>Supplier names on this delivery
SupplierIdsList<string>Supplier IDs on this delivery
TagsList<string>Delivery tags
PropertiesList<OmniumPropertyItem>Custom properties
DeliveryNotesList<OmniumComment>Internal comments
PublicDeliveryNotesList<OmniumComment>Public comments

Goods reception

Goods reception is the process of recording which items have arrived at the warehouse. When goods are received:

  1. Line item quantities are updated (DeliveredQuantity incremented)
  2. Inventory levels are increased at the receiving warehouse
  3. Cost prices can be updated on products (if UpdateCostPrice is set)
  4. Customer order reservations are fulfilled (orders waiting for this stock are updated)
  5. PO status may auto-advance to completed (if configured via GoodsReceptionCompletedStatus)

Goods reception line model

PropertyTypeDescription
LineItemIdstringThe PO line item being received
DeliveredQuantitydecimalQuantity received in this reception
CostPricedecimalActual cost at time of receipt (may differ from PO price)
WarehouseCodestringReceiving warehouse (optional, defaults to PO warehouse)
UpdateProductCostPriceboolUpdate the product master with this cost price
ReceiveAsUnallocatedboolFor VSL warehouses: receive as unallocated inventory
AllocateBasedOnRulesboolFor VSL warehouses: auto-allocate using inventory rules
GoodsReceptionIdstringIdempotency key to prevent duplicate reception

Goods reception is performed via the Delivery API. Use POST /api/Deliveries/{deliveryId}/GoodsReception to record received quantities. The delivery ID is available on the purchase order's DeliveryId property.

Goods reception flow

                    Goods Reception Request


                ┌─────────────────────┐
                │ Update PO line      │
                │ DeliveredQuantity   │
                └──────────┬──────────┘

              ┌────────────┼────────────┐
              ▼            ▼            ▼
    ┌──────────────┐ ┌──────────┐ ┌──────────────┐
    │   Update     │ │  Update  │ │   Fulfill    │
    │  inventory   │ │  cost    │ │   customer   │
    │   levels     │ │  prices  │ │   orders     │
    └──────────────┘ └──────────┘ └──────────────┘


                ┌─────────────────────┐
                │ Check if PO is      │
                │ fully received      │
                │ → Auto-complete     │
                └─────────────────────┘

Practical scenarios

Scenario 1: Complete procurement workflow

This example walks through a typical purchase order lifecycle from creation to completion.

Step 1: Create the purchase order

POST /api/PurchaseOrders
{
  "supplierId": "supplier-nordic-parts",
  "supplierName": "Nordic Parts AB",
  "supplierEmail": "orders@nordicparts.se",
  "storeId": "warehouse-oslo",
  "billingCurrency": "NOK",
  "status": "New",
  "requestedDeliveryDate": "2026-03-01T00:00:00Z",
  "isDelivery": true,
  "tags": ["Import"],
  "purchaseOrderForm": {
    "lineItems": [
      {
        "code": "NP-BOLT-M8",
        "displayName": "M8 Stainless Bolt",
        "quantity": 5000,
        "placedPrice": 2.50,
        "placedPriceExclTax": 2.00,
        "taxRate": 25,
        "currency": "NOK",
        "supplierSkuId": "NP-10042"
      },
      {
        "code": "NP-NUT-M8",
        "displayName": "M8 Stainless Nut",
        "quantity": 5000,
        "placedPrice": 1.25,
        "placedPriceExclTax": 1.00,
        "taxRate": 25,
        "currency": "NOK",
        "supplierSkuId": "NP-10043"
      }
    ]
  }
}

Step 2: Confirm and send to supplier

POST /api/PurchaseOrders/PO-2026-001/UpdateStatus
{
  "status": "Confirmed",
  "expectedDeliveryDate": "2026-03-01T00:00:00Z"
}

This triggers configured workflow steps (e.g., export to ERP, send notification to supplier).

Step 3: Move to in-progress when goods are shipped

POST /api/PurchaseOrders/PO-2026-001/UpdateStatus
{
  "status": "InProgress"
}

Step 4: Receive goods (via the delivery/goods reception API)

Goods reception updates the DeliveredQuantity on each line. If all lines are fully received and GoodsReceptionCompletedStatus is configured, the PO automatically moves to the completed status.

Scenario 2: Partial delivery

When a supplier ships part of the order:

First delivery — 3000 of 5000 bolts arrive:

The goods reception records DeliveredQuantity: 3000 on the bolt line. The PO remains in InProgress because not all items are received.

Second delivery — remaining 2000 bolts and all 5000 nuts arrive:

After this reception, all lines are fully delivered. If GoodsReceptionCompletedStatus is set to "Completed", the PO automatically moves to that status.

Scenario 3: Splitting lines for multiple warehouses

You ordered 500 widgets on a single line, but need to distribute them across two warehouses:

POST /api/PurchaseOrders/PO-2026-002/OrderLines/Split
[
  {
    "lineItemId": "line-widgets",
    "quantityToSplit": 200,
    "newLineItemId": "line-widgets-store-b"
  }
]

Now you have two lines:

  • line-widgets: 300 units for the original warehouse
  • line-widgets-store-b: 200 units that can be assigned to a different warehouse via PATCH

Error handling

HTTP status codes

CodeMeaning
200Success
400Bad request — check request body for validation errors
404Purchase order not found
409Conflict — another update is in progress (resource locked)
500Server error — check error details in response

Concurrency control

Omnium uses two mechanisms to prevent conflicting updates:

  1. Resource locking — Write endpoints (PUT, PATCH, UpdateStatus) acquire a distributed lock on the purchase order. If another request is already modifying the same PO, you'll receive a 409 Conflict. The lock is held for the duration of the operation (30 seconds for updates, 300 seconds for status changes with workflows).

  2. Optimistic concurrency — The VersionId field tracks the version of the purchase order. If the PO has been modified since you last retrieved it, the update will fail.

Best practice: Always retrieve the latest version before updating, and handle 409 responses by re-fetching and retrying.

Validation errors

Validation errors are returned in the Errors list on the purchase order:

{
  "errors": [
    {
      "key": "NonPurchasableAssortmentCode",
      "message": "Product WIDGET-X has a non-purchasable assortment code",
      "severity": "Error",
      "occurred": "2026-02-11T10:30:00Z"
    }
  ]
}

Common error keys:

KeyDescription
NonPurchasableAssortmentCodeProduct has an assortment code in the non-purchasable list
WrongOrMissingSupplierOnProductProduct's supplier doesn't match the PO supplier
PackageBreakPackage quantity constraints violated
ProductDiscontinuedProduct has been discontinued

API endpoint reference

MethodEndpointDescription
GET/api/PurchaseOrders/{id}Get a purchase order by ID
GET/api/PurchaseOrders/Extended/{id}Get PO with store, supplier, and customer order details
POST/api/PurchaseOrders/SearchSearch and filter purchase orders
POST/api/PurchaseOrders/ScrollInitialize scroll for large result sets
GET/api/PurchaseOrders/Scroll/{scrollId}Continue scrolling
POST/api/PurchaseOrdersCreate a new purchase order
PUT/api/PurchaseOrdersFull update of a purchase order
PATCH/api/PurchaseOrders/{id}Partial update of a purchase order
POST/api/PurchaseOrders/UpdateAndRunWorkflowUpdate and execute workflow
POST/api/PurchaseOrders/{id}/UpdateStatusChange status and run workflow
PUT/api/PurchaseOrders/UpdateManyBulk update multiple POs
DELETE/api/PurchaseOrders/{id}Delete a purchase order
POST/api/PurchaseOrders/{id}Recalculate ATP values
POST/api/PurchaseOrders/{id}/OrderLines/SplitSplit line items
POST/api/PurchaseOrders/{id}/OrderLines/{lineId}/CancelCancel a line item
POST/api/PurchaseOrders/{id}/AddPublicPurchaseOrderCommentAdd public comment
POST/api/PurchaseOrders/{id}/AddInternalPurchaseOrderCommentAdd internal comment

On this page