Update Purchase Order
PUT/api/purchase-orders/:purchase_order
Update an existing purchase order. All fields are optional (sometimes).
purchase-orders:writeGrant this scope to your token under Settings → Developer → Personal Access Tokens.
Authentication: Requires Bearer token.
Note: Updates acquire a Redis lock to serialize concurrent edits to the same PO. The request blocks for up to 10 seconds waiting for the lock; if the lock still cannot be acquired it returns 409 Conflict.
Update-only fields:
- po_number: alias for purchase_order_number
- purchase_order_lines[].id: ID of existing line to update (omit line from array to delete it)
- financial_lines[].id: ID of existing financial line to update
Cost lines: financial_lines replaces the order's entire set of cost lines. Any existing line omitted from the array is deleted, including lines currently attached to one of this order's purchase invoices. Send an empty array to clear them all. Omit the key entirely to leave cost lines untouched.
Cannot reduce line quantity below received or invoiced quantity.
Requires permission: purchase_orders.update
Removing lines: a line omitted from lines is deleted. The update is refused with 422 when that line has already been received, or when an authorized vendor credit reduces the cost of its goods — void or unauthorize that credit first.
Approval policy:
When the purchase-order approval policy is enabled, sending approval_status: approved approves and opens the order only if you can approve it in one call (its amount is within your limit, or its tier auto-approves). Otherwise the order stays in draft, an approval request is raised to the policy's approvers, and the response adds top-level approval: {requested: true, request_id}. The data object also adds approval — present only while the purchase-order approval policy is enabled (omitted otherwise, so responses are unchanged without a policy). Fields: policy_enabled, request_id, status (pending, approved, rejected, withdrawn, superseded; null when no request was ever raised), reason, tier_name, waiting_on[] (id, name of users who can act now), reapproval_pending (an open order whose changes await re-approval), awaiting_viewer (you can act on it now), and on single-order responses can_approve_directly (you can approve it in one call — within your limit), blocked_reason (why not), send_blocked_reason (why it cannot be sent to the supplier yet) and deposit_blocked_reason (why no new vendor deposit can be taken against it). Use GET /api/approval-requests/for/purchase_order/{id} for the full approval timeline.
Editing an approved order so that its total rises by more than the policy's re-approval tolerance, its supplier or currency changes, or lines are added raises a re-approval request: the order stays open, but it cannot be re-sent to the supplier or take new vendor deposits until the change is approved.
purchase_order_number may be omitted but cannot be cleared — sending null or an empty string returns 422.
Request
Responses
- 200
- 401
- 403
- 404
- 409
- 422
- 429
OK
Response Headers
Unauthenticated — the bearer token is missing, revoked, expired, or malformed. Never retry automatically; fix the credential. See the Errors guide.
Forbidden — the token lacks a required scope, the endpoint is not available to API tokens, or the user behind the token lacks the permission. A human must adjust the token scopes or user permissions; do not retry.
Not found — no record with the given identifier (or the route does not exist). Verify the ID before retrying.
Conflict
Response Headers
Unprocessable Content
Response Headers
Rate limited — platform limit is 1,000 requests/min; individual tokens may carry lower limits. Honor the Retry-After header before retrying. See the Rate Limits guide.