Approve a purchase order
Approves a draft purchase order, transitioning it to status: approved. Approved POs
increment the quantity-on-order count for each line item’s product-location pair.
Requires purchasing:write scope.
This endpoint does NOT email the vendor. Sending the purchase order to the vendor is a
separate, explicit verb (POST /purchase-orders/{id}/send), which is also the only place
the sent_at stamp is written. (Corrected 2026-08-17: this description previously said an
approval email is “optionally sent to the vendor when notify_vendor: true is specified”.
approvePurchaseOrder has never read a notify_vendor field — the only handler in
routes/purchasing.mjs that reads one is cancelPurchaseOrder. The sentence was authored
by the 2026-05-12 description backfill and had no implementation behind it at any point.)
Re-approving a stale approval (reapprove_stale, 2026-08-17). When a line edit raises a
purchase order’s total ABOVE the total that was approved, the order stays approved and
non-blocking (receiving, billing and every other action keep working) while
GET /purchase-orders/{id} reports approval_stale: true alongside the approved_total
snapshot. Send reapprove_stale: true to record a fresh approval at the current amount.
This is an explicit opt-in, and WITHOUT it the endpoint’s behaviour is unchanged: a purchase
order that is not pending still returns 400. WITH it, the endpoint accepts ONLY a
genuinely stale order and refuses everything else with a structured 422:
po_not_approved (the order is not approved), approval_basis_missing (approved before
approval totals were recorded, so there is no basis to refresh), or approval_not_stale
(the total does not exceed the approved amount — also what a duplicate request receives,
since the check is re-evaluated under a row lock).
A re-approval re-stamps the approver, the timestamp and the approved total. It does NOT move
the order back to pending (that would stop receiving) and does NOT re-run the status
transition (an order already in processing stays there). Separation of duties, the
purchasing:approve scope and the high-value approval band are all enforced exactly as they
are on a first approval — and the band is re-evaluated against the NEW total, which may
have crossed a threshold the original approval did not.
Authorizations
API key issued per entity via Settings > Developers > API Keys.
Each key carries scopes (e.g. orders:read, products:write).
Bearer token format: Authorization: Bearer ark_live_ent_Test keys use ark_test_ent_. Both are issued per entity
via Settings > Developers > API Keys.
Path Parameters
Body
Response
Approved purchase order
A purchase order issued to a vendor.
purchase_order draft, approved, sent, partially_received, received, closed, cancelled The order total as it stood when an approver approved this purchase order -- the basis they actually saw. NULL on every approval predating 2026-08-17 (no backfill) and NULL means "basis unknown", which is treated as fail-safe.
DERIVED, not stored. True when the order is approved AND its total has since risen above approved_total. The purchase order still receives normally; this is a non-blocking prompt to re-approve.
Per-field verdicts for the header fields, from the same predicate the write handlers enforce. Keys: notes, internal_notes, due_date, expected_date, location_id, po_tracking_reference. Each is { allowed: boolean, reason: string|null } where reason is a token from the PoLineEditReason vocabulary.

