Skip to main content

What this guide covers

The purchase-to-pay (P2P) workflow moves goods and money through four stages:
  1. Purchase order — commit to buying from a vendor
  2. Goods receipt — receive the goods into inventory
  3. Vendor bill — record the vendor’s invoice and clear the GRNI accrual
  4. AP payment — pay the bill and close the liability
Each stage is a separate API call. GL entries post automatically at the right stage. No manual journal entries are needed for the standard flow.

Prerequisites

  • API key with scopes: purchasing:write, purchasing:read, accounting:write
  • An active vendor account (account_type: vendor)
  • One or more product IDs to order
  • A warehouse location ID and, for the PO, a payment term ID

Stage 1: Create and approve the purchase order

POST /v1/purchase-orders creates the purchase order in draft status. No GL entry fires at this stage. Identify the vendor with vendor_id (the older account_id alias is still accepted), and send each line with product_id, quantity and unit_cost. Purchase orders carry their own approve, send and receive calls, so use this resource rather than POST /v1/orders for the purchasing side.
curl
Save the id from the response (the PO UUID); you reference it in later stages. The PO’s status moves through draft, approved, sent, partially_received, received, closed and cancelled.

Approve and send the PO

A draft PO has to be approved before it can be received. Approval raises the on-order quantity for each line’s product and location; it does not send anything to the vendor.
If a later line edit raises the total above what was approved, send "reapprove_stale": true to record a fresh approval at the current amount. Sending the PO to the vendor is a separate call, POST /v1/purchase-orders/$PO_ID/send-to-vendor. It emails the PO from the entity’s purchase-order template, or, with "send_mode": "external", records it as sent when you contacted the vendor outside Arcus. It returns 422 vendor_email_missing when the vendor has no email address, 422 po_pending_approval when the PO still awaits approval, and 422 po_cancelled when it was cancelled.
The order endpoint PATCH /v1/orders/{id} cannot move a document between statuses: totals, status and the entity are read-only on it, and sending any of them returns 400. Use the approve call above to advance a purchase order.

Stage 2: Receive goods

POST /v1/purchase-orders/{po_id}/receive records the goods arriving at your warehouse. It creates inventory receive transactions and raises on-hand for each received line. GL entries that fire at this stage: GRNI is a liability accrual that stays open until you post the vendor bill in Stage 3.
curl
Each line needs product_id and quantity_received; name the PO line with po_item_id (or order_item_id) when the same product is on the PO more than once. A damaged line takes damaged_qty, condition (new or damaged), disposition (restock, quarantine, return_to_vendor, or writeoff) and damage_notes. You can also pass vendor_shipment (tracking_number, carrier, shipped_at, note), and create_bill: true to have Arcus draft the vendor bill from the receipt. A new receipt answers 201 with the receipt (plus an auto_bill object when create_bill was set, or auto_bill_error when that part failed). An identical retry within a short window answers 200 with the original receipt and idempotent: true, so nothing is received twice. Inventory on-hand increases immediately. Partial receipt: you can receive fewer than the ordered quantity. The PO moves to partially_received and the remaining quantity stays open. Call receive again when the rest arrives. Receiving more than the entity’s over-receipt tolerance allows is refused with 400 over_receipt_blocked; the body carries ordered, already_received, remaining, attempted, tolerance_pct and cap.

Stage 3: Enter and post the vendor bill

When the vendor’s invoice arrives, create a vendor bill linked to the PO. Posting the bill clears the GRNI accrual and records the AP liability.

Create the draft bill

vendor_id, bill_date and at least one line are required, and every line needs a description and an amount. Line quantity and unit_cost are optional extras on top of amount. Use line_kind (goods, expense, or landed) on a line to say what it is. Posting date. The bill’s entry posts on effective_date, which defaults to bill_date. That date must sit inside the entity’s posting window (180 days back and no days ahead, by default), in an open accounting period, and outside a locked AP tax year; otherwise the call returns 422 and writes nothing. To post outside the window, send backdate_reason or future_reason; the override is granted only when the identity behind the key holds the accounting.close_period permission and an accounting period covers the date. The 422 codes are backdate_beyond_allowed_range, future_beyond_allowed_range, backdate_override_reason_invalid, future_override_reason_invalid, backdate_override_requires_accounting_period, future_override_requires_accounting_period, bill_date_in_closed_period and tax_year_locked. Duplicates. A second bill with the same vendor_invoice_number for the vendor returns 409 duplicate_vendor_invoice_number, and a landed charge that looks like one already on the same receipts returns 409 landed_duplicate_charge. If the second bill is a genuine separate charge, resend with allow_duplicate_override: true and a duplicate_override_reason; the override is audited. The bill is created in draft status. No GL entry yet. Until it is posted you can correct it with PATCH /v1/vendor-bills/$BILL_ID (allowed fields: vendor_invoice_number, due_date, payment_terms, notes, attachment_urls and metadata; any other field, including bill_date and vendor_id, returns 400 validation_failed), or void it with DELETE /v1/vendor-bills/$BILL_ID. Voiding posts a reversing journal entry that undoes the AP accrual; only a bill in draft or approved status with no recorded payments can be voided, a voided bill cannot be reopened, and the call needs the accounting.approve_bill permission.

Post the bill

The call needs purchasing:write and is idempotent on the Idempotency-Key header. Posting moves the bill out of draft and writes the AP journal entry. GL entries that fire when the bill is posted: This clears the GRNI accrual from Stage 2. The AP liability is now open until Stage 4.

Stage 4: Pay the vendor bill

POST /v1/ap-payments closes the AP liability and records the outgoing cash.
curl
vendor_id and payment_method (check, ach, wire, credit_card, cash, or other) are required. Name the bills being paid in bill_applications[], each with a bill_id and an amount. The call needs purchasing:write. GL entries that fire at payment: The AP liability is cleared. The vendor bill transitions to paid status, and the payment itself starts in pending and moves through sent, cleared, voided or returned.

GL summary: the full P2P transaction trail

At Stage 4, GRNI nets to zero and Inventory FG reflects the real cost. This is the standard accrual accounting pattern (NetSuite, Acumatica, QuickBooks Enterprise all follow the same sequence).

Partial payments and multi-bill payments

A single AP payment can apply to multiple bills:
You can also pay a bill partially. The bill becomes partially_paid with a reduced balance_due until the remaining balance is paid or written off.

Vendor credits

To apply a vendor credit against a bill payment, include vendor_credit_ids[] in the payment body:
The credit reduces the cash portion of the payment. If credits fully cover the bill, no bank JE posts.

Common errors


Idempotency

Every write in this flow accepts Idempotency-Key. Use a stable key tied to your source record (e.g. po-<source-po-id>, recv-<source-receipt-id>, bill-<vendor-invoice-number>). Keys expire after 24 hours.

Verifying the full chain

After all four stages, run these checks:
AP aging ages against the entity’s current business day, so omit the deprecated as_of_date parameter: it is refused with 400 unsupported_param. You can narrow the report to one vendor with account_id.

Next steps