How to Create Purchase Orders Using Unified's Accounting API
January 15, 2026
Most purchase order features fail before the request is even sent.
On paper, creating a purchase order looks trivial. Pick a supplier. Add line items. Send the request. In practice, those steps only work if the accounting system on the other end agrees on what a purchase order is, which fields are writable, and how line items are modeled.
That's where product complexity shows up.
Accounting platforms don't treat purchase orders consistently. Some provide full CRUD support. Others surface purchase orders as read-only artifacts generated elsewhere. Even among systems that support creation, required fields, writable properties, and lifecycle behavior vary by provider.
For product teams, this creates uncomfortable questions:
- Can we rely on a single payload shape across providers?
- Do we need vendor-specific logic just to create a PO?
- Which fields are safe to set, and which will be ignored or rejected?
- Can this feature ship broadly, or only to a narrow subset of integrations?
Many products solve this by hardcoding per-vendor rules or limiting purchase order creation to one or two accounting systems. That works initially, but it doesn't scale.
Unified's Accounting API is designed to remove that constraint. Instead of pushing provider differences into your application logic, Unified normalizes purchase order objects upstream and provides a consistent API surface where write support exists.
This guide shows how to create purchase orders using that normalized layer—step by step—without branching logic for QuickBooks vs. Xero vs. NetSuite, and without assuming that every provider behaves the same.
Prerequisites
- Node.js v18+
- A Unified account
- An Accounting integration enabled for your customer
- Your Unified API key
- A customer Accounting
connectionId
Step 1: Set up your project
mkdir purchase-order-demo
cd purchase-order-demo
npm init -y
npm install @unified-api/typescript-sdk dotenv
Create a .env file:
UNIFIED_API_KEY=your_unified_api_key
CONNECTION_ACCOUNTING=your_customer_accounting_connection_id
Step 2: Initialize the SDK
import "dotenv/config";
import { UnifiedTo } from "@unified-api/typescript-sdk";
const { UNIFIED_API_KEY, CONNECTION_ACCOUNTING } = process.env;
const sdk = new UnifiedTo({
security: { jwt: UNIFIED_API_KEY! },
});
At this point, you're authenticated and ready to interact with the Accounting and Commerce APIs through a single client.
Step 3: Understand the normalized Purchase Order model (critical)
Unified represents purchase orders using the normalized AccountingPurchaseorder object. Field names are snake_case and consistent across providers.
At the top level, a purchase order can include:
contact_id— the supplier (AccountingContact)account_id— optional accounting accountcurrency— ISO 4217 currency codestatus— lifecycle state (DRAFT,AUTHORIZED, etc.)lineitems[]— goods or services being purchased
Each line item supports:
- quantity and unit price
- optional linkage to a Commerce catalog item (
item_id) - optional free-text identifiers (
item_name,item_sku) - optional accounting references (account, category, tax rate)
Unified does not assume which fields are required. Provider-specific constraints are enforced by the integration.
Step 4: Resolve the supplier (contact_id)
Purchase orders typically reference a supplier, which is an AccountingContact with type = SUPPLIER.
const suppliers = await sdk.accounting.listAccountingContacts({
connectionId: CONNECTION_ACCOUNTING!,
type: "SUPPLIER",
limit: 50,
});
Each result includes an id. Select the appropriate one and store it as supplierId.
Step 5 (Optional): Resolve an accounting account
Some providers allow or require purchase orders (or line items) to reference an accounting account.
const accounts = await sdk.accounting.listAccountingAccounts({
connectionId: CONNECTION_ACCOUNTING!,
limit: 50,
});
If your provider supports writable account_id on purchase orders or line items, select the relevant AccountingAccount.id. Otherwise, omit it.
Step 6 (Recommended): Resolve a Commerce item (item_id)
Line items can reference a CommerceItem via item_id. This is the preferred approach when Commerce items are available for the integration.
const items = await sdk.commerce.listCommerceItems({
connectionId: CONNECTION_ACCOUNTING!,
limit: 50,
query: "Widget",
});
Each result is a CommerceItem. Use item.id as lineitems[].item_id.
If Commerce items are not provided for your provider, you can skip this step and use item_name or item_sku instead.
Step 7: Build a minimum viable purchase order payload
Here's a provider-safe baseline payload that uses only documented fields:
const purchaseOrder = {
contact_id: supplierId,
currency: "USD",
status: "DRAFT",
lineitems: [
{
unit_quantity: 2,
unit_amount: 49.99,
// Preferred when Commerce Items are available:
item_id: commerceItemId,
// Fallback if Commerce Items are not exposed:
// item_name: "Widget",
// item_sku: "WID-001",
},
],
};
Why this payload works
- All fields are explicitly documented in the data model
- No undocumented required fields are assumed
- Line items support either:
- normalized catalog linkage (
item_id), or - free-text identifiers (
item_name,item_sku)
- normalized catalog linkage (
- Line-item totals are defined as
unit_quantity * unit_amount + tax_amount
Provider behavior around validating or computing totals may vary. If you supply totals explicitly, ensure they align with provider expectations.
Step 8: Create the purchase order
const result = await sdk.accounting.createAccountingPurchaseorder({
connectionId: CONNECTION_ACCOUNTING!,
accountingPurchaseorder: purchaseOrder,
});
The response is a single AccountingPurchaseorder object containing:
- the created
id - timestamps
- normalized status
- line items and totals as returned by the provider
Optional: Limit returned fields
If you only need a subset of fields, use the fields parameter:
const result = await sdk.accounting.createAccountingPurchaseorder({
connectionId: CONNECTION_ACCOUNTING!,
accountingPurchaseorder: purchaseOrder,
fields: ["id", "status", "total_amount"],
});
fields must be an array of strings. Omit it entirely if you don't need filtering.
Provider considerations you must handle
Write support varies
Not all providers support creating purchase orders. Always verify writable support for ACCOUNTING PURCHASEORDER before enabling this feature.
Required fields vary
Unified's schema is permissive, but providers may enforce required fields (account, tax rate, etc.). Handle errors gracefully.
Status values may differ
Unified documents standard status values, but providers may return additional states. Treat status as an open set.
To create purchase orders with Unified:
- Authenticate and obtain a valid
connectionId - Resolve a supplier contact
- Optionally resolve accounts and commerce items
- Build a normalized, minimal payload
- Call
createAccountingPurchaseorder - Handle provider-specific constraints