When to use this endpoint

Use the office Approve action after the order totals and supplier are correct. This does not send the order or receive goods.

Authentication and permissions

Authenticate with signed API-key fields or a logged-in CRM session. Session requests use dispatcher CSRF protection when configured.

Requires explicit View on Purchase Order or Purchase Orders. Writes also require the matching Edit permission; clearing invoice details requires Delete. A non-administrator blocked by the legacy Purchase Orders visibility flag (crm_access bit 256) is denied. API keys use their owner’s permissions.

The primary order is checked against the current tenant and Purchase Order record scope. Configured TeamAccess follows permitted linked Contracts, Customers, Jobs, Quotes and Departments; the legacy department filter follows direct Customer/Quote/customer-Job links. An inaccessible or missing order returns purchase order does not exist.

Parameters

ParameterTypeRequiredDescription
idInteger-compatibleYesPositive local Purchase Order ID/digit string; not the displayed po_num. Missing, malformed or inaccessible IDs fail.

Example request

$result = FM_api('PurchaseOrder_approve', [
	'id'=>'1042',
]);

Example response

{
  "ok": 1
}

Errors and unusual responses

ResponseCondition
{"error":"not logged in"}No authenticated tenant context is available. Invalid signed credentials can be rejected by the dispatcher before the handler runs.
{"error": "missing or invalid parameter: id"}id integer-converts below 1.
{"error": "purchase order does not exist"}No row.
{"error": "Total Net exceeds your Purchase Order approval limit"}The session-version-specific limit check fails.
{"error": "permission denied"}The required page/action or legacy visibility setting denies access.
{"error": "Purchase Order approval is disabled for your role"}The role explicitly disables approval without administrator/full access.
{"error": "Cannot approve a cancelled Purchase Order"}Received status is Cancelled.

Legacy application errors may be returned with HTTP 200. Check the decoded response for error, not only the HTTP status.

Behaviour and edge cases

  • Requires View and Edit and enforces the role’s Disable PO Approve setting, with the existing administrator/full-access exemption. A cancelled order cannot be approved.
  • Approval locks the tenant owner and order, recalculates persisted totals from line totals plus carriage, then checks the user limit and stamps date_approved in the same transaction. Office header and line edits use the same owner lock.
  • The confirmed historical zero-limit rules remain: CRM2 applies only a positive limit; CRM1 applies the limit only when crm_access bit 16384 is set, including a zero ceiling. Equality is allowed.
  • A still-approved order returns {ok:1,warning:"already approved"}. Financial changes clear approval and require approval again; exact no-ops preserve it.
  • Does not send email/PDF, create receipts or set a separate approver column. History attributes the action to the current user; received status and sent history are separate.

Side effects

  • Header writes lock the existing order and tenant owner, check persistence and record history in the same transaction. Errors roll back the header and audit; a true no-op returns {ok:1,warning:"no change needed"} without a new event/history entry. Cache invalidation and edit-event follow-ups run after the owning transaction commits; an event exception returns saved=1 and a no-replay warning.
  • A changed header emits PurchaseOrder_edit after commit; configured account listeners may perform integration work. No order email/PDF is sent directly by this route.

See it in Fieldmotion

CRM1

In CRM1 Purchase Order details, click Approve and watch PurchaseOrder_approve. UI visibility does not add a server-side page-action guard.

CRM2

In CRM2 Purchase Order details, click Approve; role settings control the visible button and the same endpoint stamps approval. App requests go through m/www’s different approval handler, which adds ownership/status/app permissions and also updates meta.status.

Related endpoints