Unified.to
All articles

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 account
  • currency — ISO 4217 currency code
  • status — 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.

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)
  • 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:

  1. Authenticate and obtain a valid connectionId
  2. Resolve a supplier contact
  3. Optionally resolve accounts and commerce items
  4. Build a normalized, minimal payload
  5. Call createAccountingPurchaseorder
  6. Handle provider-specific constraints

Start your 30-day free trial

Book a demo

All articles