Pleaxy

API Reference

Every endpoint in the Pleaxy API — request parameters, response shapes, and errors. For concepts and guides, see Documentation.

Viewing as

Full internal bookkeeping and the complete approval workflow.

Getting started

Base URL

https://api.pleaxy.ai

The base URL is the origin only; every path below starts with the version. The curl examples read it from PLEAXY_API_BASE_URL:

export PLEAXY_API_BASE_URL="https://api.pleaxy.ai"

Versioning

Every resource path is prefixed with the API version — currently /v1. Breaking changes ship as a new version (/v2) served alongside /v1; additive changes such as new endpoints, new optional request fields, and new response fields ship within /v1. Clients must ignore response fields they do not recognize.

The fixed sets of values shown in the response shapes below — statuses, roles, providers, notification types — are open-ended. New values are added within /v1, so a response can carry a value your client was not written against. Branch on the values you handle and keep a default branch for the rest; an unrecognized value is not an error.

Authentication

Every route except /v1/auth/me requires one of two credentials, sent as a header:

  • Authorization: Bearer <accessToken> — a human Cognito access token. Required for key management (API keys) and anywhere else a human session is enforced.
  • X-Api-Key: <keyId>.<secret> — a scoped machine credential minted via CreateApiKey, for Agents calling the API without a human logged in.

Both credentials resolve to the same identity shape server-side, so every workspace-scoped endpoint behaves identically regardless of which one authenticated the request. A credential only grants access to the workspace(s) — regionId — and role (buyer or supplier) it belongs to. Use the Viewing as switch above to see exactly what each role’s credential gets back from a shared endpoint.

Content types

Request and response bodies are application/json unless noted otherwise. The one exception is SubmitInvoice (buyer callers), which also accepts raw cXML or UBL 2.1 XML documents via application/xml or text/xml.

Errors

Errors are returned as a JSON object with an HTTP status code in the 4xx range:

{
  "statusCode": 400,
  "message": "string" | ["string", ...],
  "error": "Bad Request"
}

message is an array when the request body failed field-level validation, and a single string for every other error. Each operation below lists the specific error conditions it can return, beyond the universal 401 (missing/invalid credential) and 403 (no access to this workspace).

Purchase orders

A purchase order commits a buyer to one or more line items with a supplier. Creating one starts an approval chain; once approved it can be flipped into an invoice. Every amount is in the region currency — a workspace's currency is its region's, set on the company profile — and Pleaxy doesn't convert currencies.

/v1/workspaces/{regionId}/purchase-orders

ListPurchaseOrders

GETBoth

Lists every purchase order in the workspace, in no particular order. Optionally scoped to a createdAt range via from/to — pass a calendar month's bounds to get a per-month view. A Supplier caller gets a reduced view of each PO (see GetPurchaseOrder) — buyer-internal bookkeeping and the full approval workflow are withheld.

Request Syntax

GET /v1/workspaces/{regionId}/purchase-orders

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
fromstringISO 8601 date/timestamp — only items created on or after this are returned.
tostringISO 8601 date/timestamp — only items created on or before this are returned.

Response Syntax 200 OKShown for: Buyer caller

[
   {
      "id": "string",
      "buyerOrgId": "string",
      "supplierOrgId": "string",
      "supplier": "string",
      "amount": number,
      "currency": "string",
      "status": "draft" | "pending_approval" | "rejected" | "awaiting_fulfillment" | "invoiced" | "paid" | "canceled" | "closed" | "expired",
      "invoiceId": "string",
      "provider": "aws" | "gcp" | "azure" | "oci",
      "payerAccounts": [
         { "id": "string", "label": "string", "detail": "string" }
      ],
      "billTo": "string",
      "costCenter": "string",
      "budgetOwner": "string",
      "buyerName": "string",
      "supplierSite": "string",
      "notes": "string",
      "lineItems": [
         {
         "lineNo": number,
         "scopeType": "sku" | "service" | "account" | "custom",
         "scopeId": "string" | null,
         "name": "string",
         "ruleType": "quantity" | "amount",
         "unit": "string",
         "unitPrice": number,
         "qty": number,
         "nteAmount": number,
         "currency": "string",
         "paymentTerms": "string",
         "validFrom": "string",
         "validUntil": "string",
         "consumed": number,
         "customerLineNo": "string",
         "supplierSku": "string",
         "priceSource": "contract" | "list" | "manual"
       }
      ],
      "approval": {
         "requester": "string",
         "requesterEmployeeKey": "string",
         "groups": [
           {
             "id": "string",
             "mode": "sequential" | "parallel",
             "label": "string",
             "status": "pending" | "approved" | "rejected",
             "stages": [
               { "id": "string", "label": "string", "approverEmployeeKey": "string", "approverName": "string", "status": "pending" | "approved" | "rejected" }
             ]
           }
         ],
         "groupIndex": number,
         "status": "pending" | "approved" | "rejected",
         "history": [
           { "stage": "string", "by": "string", "at": "string", "action": "approved" | "rejected" | "limit_exceeded" | "reassigned" | "skipped" }
         ],
         "autonomy": boolean,
         "selfApproved": boolean
       },
      "createdAt": "string"
   }
]

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

GetPurchaseOrder

GETBoth

Fetches a single purchase order by id. For a Buyer caller, regionId is the PO's own workspace. For a Supplier caller, regionId is the supplier's own workspace and the PO is looked up among the org's incoming purchase orders regardless of which buyer workspace issued it.

Request Syntax

GET /v1/workspaces/{regionId}/purchase-orders/{poId}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
poIdRequiredstringThe purchase order identifier.
buyerstringSupplier callers only, and only needed after a 409 *_ID_AMBIGUOUS: the buyer's Workspace ID (ending in .buyer) or region id, from the 409's buyers list. Only that buyer's document is considered; none there is a 404. A .supplier Workspace ID is 400 BUYER_QUALIFIER_INVALID ("The buyer parameter must be one buyer's Workspace ID (ending in .buyer), from the buyers list in the 409 response."). Ignored for a Buyer caller.

Response Syntax 200 OKShown for: Buyer caller

{
   "id": "string",
   "buyerOrgId": "string",
   "supplierOrgId": "string",
   "supplier": "string",
   "amount": number,
   "currency": "string",
   "status": "draft" | "pending_approval" | "rejected" | "awaiting_fulfillment" | "invoiced" | "paid" | "canceled" | "closed" | "expired",
   "invoiceId": "string",
   "provider": "aws" | "gcp" | "azure" | "oci",
   "payerAccounts": [
      { "id": "string", "label": "string", "detail": "string" }
   ],
   "billTo": "string",
   "costCenter": "string",
   "budgetOwner": "string",
   "buyerName": "string",
   "supplierSite": "string",
   "notes": "string",
   "lineItems": [
      {
      "lineNo": number,
      "scopeType": "sku" | "service" | "account" | "custom",
      "scopeId": "string" | null,
      "name": "string",
      "ruleType": "quantity" | "amount",
      "unit": "string",
      "unitPrice": number,
      "qty": number,
      "nteAmount": number,
      "currency": "string",
      "paymentTerms": "string",
      "validFrom": "string",
      "validUntil": "string",
      "consumed": number,
      "customerLineNo": "string",
      "supplierSku": "string",
      "priceSource": "contract" | "list" | "manual"
    }
   ],
   "approval": {
      "requester": "string",
      "requesterEmployeeKey": "string",
      "groups": [
        {
          "id": "string",
          "mode": "sequential" | "parallel",
          "label": "string",
          "status": "pending" | "approved" | "rejected",
          "stages": [
            { "id": "string", "label": "string", "approverEmployeeKey": "string", "approverName": "string", "status": "pending" | "approved" | "rejected" }
          ]
        }
      ],
      "groupIndex": number,
      "status": "pending" | "approved" | "rejected",
      "history": [
        { "stage": "string", "by": "string", "at": "string", "action": "approved" | "rejected" | "limit_exceeded" | "reassigned" | "skipped" }
      ],
      "autonomy": boolean,
      "selfApproved": boolean
    },
   "createdAt": "string"
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 404 Not Found — No PO with this id exists in the caller's workspace.
  • 409 Conflict — Supplier callers only. Code PO_ID_AMBIGUOUS: more than one of your buyers has issued a purchase order with this id (ids are unique only within a buyer), so Pleaxy won't guess which one you mean. Nothing is read or changed. The body's buyers lists each buyer's Workspace ID and name: repeat the call with ?buyer=<that Workspace ID>.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

CreatePurchaseOrder

POSTBuyer

Creates a purchase order from one or more line items, or saves it as a draft. The total amount is the sum of each line's amount (qty × unitPrice for a quantity-rule line, or nteAmount for an amount-rule line). Unless it is a draft, its approval chain is built immediately from the region's purchase order approval flow, autonomy settings, automation rules and spend limits. If it auto-approves, the PO is created directly in awaiting_fulfillment status instead of pending_approval.

Request Syntax

POST /v1/workspaces/{regionId}/purchase-orders
{
   "supplier": "string",
   "supplierRegionId": "string",
   "provider": "aws" | "gcp" | "azure" | "oci",
   "payerAccounts": [
      { "id": "string", "label": "string", "detail": "string" }
   ],
   "billTo": "string",
   "costCenter": "string",
   "budgetOwner": "string",
   "buyerName": "string",
   "supplierSite": "string",
   "notes": "string",
   "lineItems": [
      {
         "scopeType": "sku" | "service" | "account" | "custom",
         "scopeId": "string",
         "name": "string",
         "ruleType": "quantity" | "amount",
         "unit": "string",
         "unitPrice": number,
         "qty": number,
         "nteAmount": number,
         "currency": "string", // optional — the region currency
         "paymentTerms": "string",
         "validFrom": "string",
         "validUntil": "string"
      }
   ]
}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.

Request Body

NameTypeDescription
poNumberstringYour own PO number to use as this PO's id instead of a generated one, e.g. PO-2026-0042. Letters, numbers, '.', '_' and '-', up to 40 characters, unique within the region.
supplierRequiredstringSupplier / counterparty display name.
supplierRegionIdstringregionId of the onboarded supplier org this PO is issued to, if known. You must have an approved supplier connection to it; any other region is refused. Links the PO to that org's real supplier workspace instead of just a free-text name.
currencystringISO 4217: the currency the order is priced in. To a supplier on Pleaxy (supplierRegionId), one its region prices in, its main currency by default (400 PO_CURRENCY_NOT_OFFERED otherwise). To a cloud provider or a supplier not on Pleaxy, any code; by default the "Bills in" currency you set for that supplier (SetSupplierCurrencies), else your workspace's. Every line is in it. An order in another currency than your workspace's goes through every approval step and is never auto-approved, because approval limits are in your workspace's currency.
supplierContactEmailstringOnly for a supplier not on Pleaxy (no supplierRegionId or provider): their contact email. When the PO is sent, now or when a draft is submitted, Pleaxy invites them, or links the supplier this workspace already has.
provider"aws" | "gcp" | "azure" | "oci"Cloud provider this PO covers, if any.
payerAccountsArray<{ id, label, detail }>Only with provider: this region's cloud connections of that provider the PO applies to, by id. A connection still being set up is refused. The PO stores each connection's own label and detail; the ones you send are ignored but must be strings.
billTostringBilling entity or address.
costCenterstringInternal cost center code.
budgetOwnerstringName of the budget owner.
buyerNamestringBuyer contact name; also used as the approval chain's requester if set.
supplierSitestringSupplier site or location identifier.
notesstringFree-text notes.
draftbooleanSave as a draft instead of submitting for approval. Submit it later with SubmitPurchaseOrderDraft.
lineItemsRequiredArray<LineItem>At least one line item. Each item: scopeType ("sku" | "service" | "account" | "custom"), scopeId, name, ruleType ("quantity" | "amount"), unit, unitPrice, qty, nteAmount, currency (optional), paymentTerms, validFrom, validUntil, customerLineNo (optional: your own number for the line, up to 40 characters, shown alongside the server's lineNo). Every line is in the region currency, which the server writes — omit currency, or send the region currency (any case). Pleaxy doesn't convert currencies, so any other currency is refused.

Response Syntax 201 Created

{
   "id": "string",
   "buyerOrgId": "string",
   "supplierOrgId": "string",
   "supplier": "string",
   "amount": number,
   "currency": "string",
   "status": "draft" | "pending_approval" | "rejected" | "awaiting_fulfillment" | "invoiced" | "paid" | "canceled" | "closed" | "expired",
   "invoiceId": "string",
   "provider": "aws" | "gcp" | "azure" | "oci",
   "payerAccounts": [
      { "id": "string", "label": "string", "detail": "string" }
   ],
   "billTo": "string",
   "costCenter": "string",
   "budgetOwner": "string",
   "buyerName": "string",
   "supplierSite": "string",
   "notes": "string",
   "lineItems": [
      {
      "lineNo": number,
      "scopeType": "sku" | "service" | "account" | "custom",
      "scopeId": "string" | null,
      "name": "string",
      "ruleType": "quantity" | "amount",
      "unit": "string",
      "unitPrice": number,
      "qty": number,
      "nteAmount": number,
      "currency": "string",
      "paymentTerms": "string",
      "validFrom": "string",
      "validUntil": "string",
      "consumed": number,
      "customerLineNo": "string",
      "supplierSku": "string",
      "priceSource": "contract" | "list" | "manual"
    }
   ],
   "approval": {
      "requester": "string",
      "requesterEmployeeKey": "string",
      "groups": [
        {
          "id": "string",
          "mode": "sequential" | "parallel",
          "label": "string",
          "status": "pending" | "approved" | "rejected",
          "stages": [
            { "id": "string", "label": "string", "approverEmployeeKey": "string", "approverName": "string", "status": "pending" | "approved" | "rejected" }
          ]
        }
      ],
      "groupIndex": number,
      "status": "pending" | "approved" | "rejected",
      "history": [
        { "stage": "string", "by": "string", "at": "string", "action": "approved" | "rejected" | "limit_exceeded" | "reassigned" | "skipped" }
      ],
      "autonomy": boolean,
      "selfApproved": boolean
    },
   "createdAt": "string"
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Response Elements

NameTypeDescription
idstringPO identifier, e.g. "PO-AB12CD34".
statusstring"pending_approval" or "awaiting_fulfillment" if the amount auto-approved.
amountnumberSum of all line item amounts.
currencystringISO 4217 code the PO and every line are in — the region currency, set by the server.
approvalApprovalChainThe approval chain built for this PO.
lineItemsArray<LineItem>Each line item as stored, plus lineNo and consumed (starts at 0).

Errors

  • 400 Bad Request — Validation failed, a line item's currency isn't the region currency ("Line items must be in this region's currency (EUR). Pleaxy doesn't convert currencies."), or the region's plan has reached its rolling-30-day purchase order limit (Free plan: 10 per rolling 30 days).
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

Example

Request

curl -X POST "$PLEAXY_API_BASE_URL/v1/workspaces/reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer/purchase-orders" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "supplier": "Amazon Web Services",
    "provider": "aws",
    "lineItems": [
      {
        "scopeType": "sku",
        "scopeId": "AWS-EC2-M5LG",
        "name": "EC2 m5.large",
        "ruleType": "quantity",
        "unit": "hr",
        "unitPrice": 0.096,
        "qty": 5000,
        "nteAmount": 0,
        "paymentTerms": "Net 30",
        "validFrom": "2026-01-01",
        "validUntil": "2026-12-31"
      }
    ]
  }'

Response

{
  "id": "PO-AB12CD34",
  "buyerOrgId": "org_buyer_us-east-1",
  "supplier": "Amazon Web Services",
  "amount": 480,
  "currency": "USD",
  "status": "pending_approval",
  "provider": "aws",
  "lineItems": [
    {
      "lineNo": 1,
      "scopeType": "sku",
      "scopeId": "AWS-EC2-M5LG",
      "name": "EC2 m5.large",
      "ruleType": "quantity",
      "unit": "hr",
      "unitPrice": 0.096,
      "qty": 5000,
      "nteAmount": 0,
      "currency": "USD",
      "paymentTerms": "Net 30",
      "validFrom": "2026-01-01",
      "validUntil": "2026-12-31",
      "consumed": 0
    }
  ],
  "approval": {
    "requester": "Amazon Web Services",
    "groups": [
      {
        "id": "g1",
        "mode": "sequential",
        "status": "pending",
        "stages": [
          { "id": "cost-owner", "label": "Cost owner", "approverEmployeeKey": "us-east-1a2b3c4d", "approverName": "Jamie Rivera", "status": "pending" }
        ]
      },
      {
        "id": "g2",
        "mode": "sequential",
        "status": "pending",
        "stages": [
          { "id": "finance", "label": "Finance", "approverEmployeeKey": "us-east-1e5f6g7h", "approverName": "Priya Nair", "status": "pending" }
        ]
      }
    ],
    "groupIndex": 0,
    "status": "pending",
    "history": []
  },
  "createdAt": "2026-01-15T18:04:22.000Z"
}

UpdatePurchaseOrder

PATCHBuyer

Updates a purchase order's bookkeeping fields, or replaces its line items. Not allowed once it is closed, canceled or paid. Sending lineItems replaces the whole set, recalculates the amount and rebuilds the approval chain from scratch, discarding any sign-offs already collected: a draft stays a draft, otherwise the PO becomes awaiting_fulfillment if the new chain auto-approves, or pending_approval if it needs a person. So growing an approved PO's total can send it back through approval. Spend limits are checked for both the PO's requester and the person editing.

Request Syntax

PATCH /v1/workspaces/{regionId}/purchase-orders/{poId}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
poIdRequiredstringThe purchase order identifier.

Request Body

NameTypeDescription
lineItemsArray<LineItem>Replaces every line item, in the same shape as CreatePurchaseOrder. At least one.
billTostringBilling entity or address.
costCenterstringInternal cost center code.
budgetOwnerstringName of the budget owner.
buyerNamestringBuyer contact name.
notesstringFree-text notes.

Response Syntax 200 OK

{
   "id": "string",
   "buyerOrgId": "string",
   "supplierOrgId": "string",
   "supplier": "string",
   "amount": number,
   "currency": "string",
   "status": "draft" | "pending_approval" | "rejected" | "awaiting_fulfillment" | "invoiced" | "paid" | "canceled" | "closed" | "expired",
   "invoiceId": "string",
   "provider": "aws" | "gcp" | "azure" | "oci",
   "payerAccounts": [
      { "id": "string", "label": "string", "detail": "string" }
   ],
   "billTo": "string",
   "costCenter": "string",
   "budgetOwner": "string",
   "buyerName": "string",
   "supplierSite": "string",
   "notes": "string",
   "lineItems": [
      {
      "lineNo": number,
      "scopeType": "sku" | "service" | "account" | "custom",
      "scopeId": "string" | null,
      "name": "string",
      "ruleType": "quantity" | "amount",
      "unit": "string",
      "unitPrice": number,
      "qty": number,
      "nteAmount": number,
      "currency": "string",
      "paymentTerms": "string",
      "validFrom": "string",
      "validUntil": "string",
      "consumed": number,
      "customerLineNo": "string",
      "supplierSku": "string",
      "priceSource": "contract" | "list" | "manual"
    }
   ],
   "approval": {
      "requester": "string",
      "requesterEmployeeKey": "string",
      "groups": [
        {
          "id": "string",
          "mode": "sequential" | "parallel",
          "label": "string",
          "status": "pending" | "approved" | "rejected",
          "stages": [
            { "id": "string", "label": "string", "approverEmployeeKey": "string", "approverName": "string", "status": "pending" | "approved" | "rejected" }
          ]
        }
      ],
      "groupIndex": number,
      "status": "pending" | "approved" | "rejected",
      "history": [
        { "stage": "string", "by": "string", "at": "string", "action": "approved" | "rejected" | "limit_exceeded" | "reassigned" | "skipped" }
      ],
      "autonomy": boolean,
      "selfApproved": boolean
    },
   "createdAt": "string"
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 400 Bad Request — Validation failed; the PO is closed, canceled or paid ("PO '{poId}' can't be edited once it's {status}"); or a line's currency isn't the region currency.
  • 404 Not Found — No PO with this id exists in the workspace.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

SubmitPurchaseOrderDraft

POSTBuyer

Submits a draft for approval. The approval chain is built now, from the current approval flow, autonomy settings and spend limits, not the ones in force when the draft was saved, and the lines take the region's current currency. Spend limits are checked for both the draft's requester and the person submitting. A supplier not on Pleaxy with a supplierContactEmail is invited now. Takes no body.

Request Syntax

POST /v1/workspaces/{regionId}/purchase-orders/{poId}/submit

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
poIdRequiredstringThe purchase order identifier.

Response Syntax 201 Created

{
   "id": "string",
   "buyerOrgId": "string",
   "supplierOrgId": "string",
   "supplier": "string",
   "amount": number,
   "currency": "string",
   "status": "draft" | "pending_approval" | "rejected" | "awaiting_fulfillment" | "invoiced" | "paid" | "canceled" | "closed" | "expired",
   "invoiceId": "string",
   "provider": "aws" | "gcp" | "azure" | "oci",
   "payerAccounts": [
      { "id": "string", "label": "string", "detail": "string" }
   ],
   "billTo": "string",
   "costCenter": "string",
   "budgetOwner": "string",
   "buyerName": "string",
   "supplierSite": "string",
   "notes": "string",
   "lineItems": [
      {
      "lineNo": number,
      "scopeType": "sku" | "service" | "account" | "custom",
      "scopeId": "string" | null,
      "name": "string",
      "ruleType": "quantity" | "amount",
      "unit": "string",
      "unitPrice": number,
      "qty": number,
      "nteAmount": number,
      "currency": "string",
      "paymentTerms": "string",
      "validFrom": "string",
      "validUntil": "string",
      "consumed": number,
      "customerLineNo": "string",
      "supplierSku": "string",
      "priceSource": "contract" | "list" | "manual"
    }
   ],
   "approval": {
      "requester": "string",
      "requesterEmployeeKey": "string",
      "groups": [
        {
          "id": "string",
          "mode": "sequential" | "parallel",
          "label": "string",
          "status": "pending" | "approved" | "rejected",
          "stages": [
            { "id": "string", "label": "string", "approverEmployeeKey": "string", "approverName": "string", "status": "pending" | "approved" | "rejected" }
          ]
        }
      ],
      "groupIndex": number,
      "status": "pending" | "approved" | "rejected",
      "history": [
        { "stage": "string", "by": "string", "at": "string", "action": "approved" | "rejected" | "limit_exceeded" | "reassigned" | "skipped" }
      ],
      "autonomy": boolean,
      "selfApproved": boolean
    },
   "createdAt": "string"
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 400 Bad Request — The PO isn't a draft ("PO '{poId}' isn't a draft"), or it is over a spend limit that names the person submitting as its approver, which would leave nobody able to sign it off ("Not submitted. This draft is over a spend limit that names you as its approver…").
  • 404 Not Found — No PO with this id exists in the workspace.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

CancelPurchaseOrder

POSTBuyer

Cancels a purchase order that is a draft, pending approval, awaiting fulfillment or expired. This is the buyer stopping the order, not an approver rejecting it. The supplier is told, with your reason if you give one.

Request Syntax

POST /v1/workspaces/{regionId}/purchase-orders/{poId}/cancel
{
   "reason": "string"
}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
poIdRequiredstringThe purchase order identifier.

Request Body

NameTypeDescription
reasonstringUp to 500 characters. The supplier sees it exactly as written.

Response Syntax 201 Created

{
   "id": "string",
   "buyerOrgId": "string",
   "supplierOrgId": "string",
   "supplier": "string",
   "amount": number,
   "currency": "string",
   "status": "draft" | "pending_approval" | "rejected" | "awaiting_fulfillment" | "invoiced" | "paid" | "canceled" | "closed" | "expired",
   "invoiceId": "string",
   "provider": "aws" | "gcp" | "azure" | "oci",
   "payerAccounts": [
      { "id": "string", "label": "string", "detail": "string" }
   ],
   "billTo": "string",
   "costCenter": "string",
   "budgetOwner": "string",
   "buyerName": "string",
   "supplierSite": "string",
   "notes": "string",
   "lineItems": [
      {
      "lineNo": number,
      "scopeType": "sku" | "service" | "account" | "custom",
      "scopeId": "string" | null,
      "name": "string",
      "ruleType": "quantity" | "amount",
      "unit": "string",
      "unitPrice": number,
      "qty": number,
      "nteAmount": number,
      "currency": "string",
      "paymentTerms": "string",
      "validFrom": "string",
      "validUntil": "string",
      "consumed": number,
      "customerLineNo": "string",
      "supplierSku": "string",
      "priceSource": "contract" | "list" | "manual"
    }
   ],
   "approval": {
      "requester": "string",
      "requesterEmployeeKey": "string",
      "groups": [
        {
          "id": "string",
          "mode": "sequential" | "parallel",
          "label": "string",
          "status": "pending" | "approved" | "rejected",
          "stages": [
            { "id": "string", "label": "string", "approverEmployeeKey": "string", "approverName": "string", "status": "pending" | "approved" | "rejected" }
          ]
        }
      ],
      "groupIndex": number,
      "status": "pending" | "approved" | "rejected",
      "history": [
        { "stage": "string", "by": "string", "at": "string", "action": "approved" | "rejected" | "limit_exceeded" | "reassigned" | "skipped" }
      ],
      "autonomy": boolean,
      "selfApproved": boolean
    },
   "createdAt": "string"
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 400 Bad Request — The PO is in a status that can't be canceled ("PO '{poId}' can't be canceled once it's {status}"), or reason is over 500 characters.
  • 404 Not Found — No PO with this id exists in the workspace.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

ClosePurchaseOrder

POSTBuyer

Closes out a purchase order that is awaiting fulfillment, invoiced, paid or expired, for example one that is fully used with nothing left to invoice. A closed PO can't be edited. Takes no body.

Request Syntax

POST /v1/workspaces/{regionId}/purchase-orders/{poId}/close

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
poIdRequiredstringThe purchase order identifier.

Response Syntax 201 Created

{
   "id": "string",
   "buyerOrgId": "string",
   "supplierOrgId": "string",
   "supplier": "string",
   "amount": number,
   "currency": "string",
   "status": "draft" | "pending_approval" | "rejected" | "awaiting_fulfillment" | "invoiced" | "paid" | "canceled" | "closed" | "expired",
   "invoiceId": "string",
   "provider": "aws" | "gcp" | "azure" | "oci",
   "payerAccounts": [
      { "id": "string", "label": "string", "detail": "string" }
   ],
   "billTo": "string",
   "costCenter": "string",
   "budgetOwner": "string",
   "buyerName": "string",
   "supplierSite": "string",
   "notes": "string",
   "lineItems": [
      {
      "lineNo": number,
      "scopeType": "sku" | "service" | "account" | "custom",
      "scopeId": "string" | null,
      "name": "string",
      "ruleType": "quantity" | "amount",
      "unit": "string",
      "unitPrice": number,
      "qty": number,
      "nteAmount": number,
      "currency": "string",
      "paymentTerms": "string",
      "validFrom": "string",
      "validUntil": "string",
      "consumed": number,
      "customerLineNo": "string",
      "supplierSku": "string",
      "priceSource": "contract" | "list" | "manual"
    }
   ],
   "approval": {
      "requester": "string",
      "requesterEmployeeKey": "string",
      "groups": [
        {
          "id": "string",
          "mode": "sequential" | "parallel",
          "label": "string",
          "status": "pending" | "approved" | "rejected",
          "stages": [
            { "id": "string", "label": "string", "approverEmployeeKey": "string", "approverName": "string", "status": "pending" | "approved" | "rejected" }
          ]
        }
      ],
      "groupIndex": number,
      "status": "pending" | "approved" | "rejected",
      "history": [
        { "stage": "string", "by": "string", "at": "string", "action": "approved" | "rejected" | "limit_exceeded" | "reassigned" | "skipped" }
      ],
      "autonomy": boolean,
      "selfApproved": boolean
    },
   "createdAt": "string"
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 400 Bad Request — The PO is in a status that can't be closed ("PO '{poId}' can't be closed while it's {status}").
  • 404 Not Found — No PO with this id exists in the workspace.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

ApprovePurchaseOrder

POSTBuyer

Advances the PO's approval chain by one stage. Only the employee the current step names may call this — no role, permission or administrator rights substitute for being named, and an API key can never be named. Once the final stage approves, the PO's status becomes awaiting_fulfillment and a po_approved notification is sent to the supplier. It takes no body and acts as the authenticated caller (an API key acts as apikey:<keyId>).

Signed-in session only. Any other caller, including an API key, gets 403 HUMAN_APPROVAL_REQUIRED before anything is read.

Request Syntax

POST /v1/workspaces/{regionId}/purchase-orders/{poId}/approve

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
poIdRequiredstringThe purchase order identifier.

Response Syntax 201 Created

{
   "id": "string",
   "buyerOrgId": "string",
   "supplierOrgId": "string",
   "supplier": "string",
   "amount": number,
   "currency": "string",
   "status": "draft" | "pending_approval" | "rejected" | "awaiting_fulfillment" | "invoiced" | "paid" | "canceled" | "closed" | "expired",
   "invoiceId": "string",
   "provider": "aws" | "gcp" | "azure" | "oci",
   "payerAccounts": [
      { "id": "string", "label": "string", "detail": "string" }
   ],
   "billTo": "string",
   "costCenter": "string",
   "budgetOwner": "string",
   "buyerName": "string",
   "supplierSite": "string",
   "notes": "string",
   "lineItems": [
      {
      "lineNo": number,
      "scopeType": "sku" | "service" | "account" | "custom",
      "scopeId": "string" | null,
      "name": "string",
      "ruleType": "quantity" | "amount",
      "unit": "string",
      "unitPrice": number,
      "qty": number,
      "nteAmount": number,
      "currency": "string",
      "paymentTerms": "string",
      "validFrom": "string",
      "validUntil": "string",
      "consumed": number,
      "customerLineNo": "string",
      "supplierSku": "string",
      "priceSource": "contract" | "list" | "manual"
    }
   ],
   "approval": {
      "requester": "string",
      "requesterEmployeeKey": "string",
      "groups": [
        {
          "id": "string",
          "mode": "sequential" | "parallel",
          "label": "string",
          "status": "pending" | "approved" | "rejected",
          "stages": [
            { "id": "string", "label": "string", "approverEmployeeKey": "string", "approverName": "string", "status": "pending" | "approved" | "rejected" }
          ]
        }
      ],
      "groupIndex": number,
      "status": "pending" | "approved" | "rejected",
      "history": [
        { "stage": "string", "by": "string", "at": "string", "action": "approved" | "rejected" | "limit_exceeded" | "reassigned" | "skipped" }
      ],
      "autonomy": boolean,
      "selfApproved": boolean
    },
   "createdAt": "string"
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 400 Bad Request — The PO's approval chain has no pending stage (already approved or rejected).
  • 403 Forbidden — Code HUMAN_APPROVAL_REQUIRED. The caller is an API key. Approval steps name a specific employee, and a key can never be one, so a key can neither approve nor reject a step. Ask the employee the step names to act on it in Pleaxy.
  • 403 Forbidden — Code APPROVER_NOT_ASSIGNED. The current step has no approver assigned, so nobody can act on it. An administrator must assign one with ReassignPurchaseOrderApprovalStage / ReassignInvoiceApprovalStage first.
  • 403 Forbidden — The caller is not the employee the current step names. Only that employee may approve or reject it — no role or permission substitutes for being named.
  • 404 Not Found — No PO with this id exists in the workspace.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

RejectPurchaseOrder

POSTBuyer

Rejects the PO at its current approval stage. Only the employee the current step names may call this — rejecting is bound by the same rule as approving. Status becomes rejected and a po_rejected notification is sent to the supplier. Terminal — a rejected PO cannot be resubmitted. It takes no body and acts as the authenticated caller (an API key acts as apikey:<keyId>).

Request Syntax

POST /v1/workspaces/{regionId}/purchase-orders/{poId}/reject

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
poIdRequiredstringThe purchase order identifier.

Response Syntax 201 Created

{
   "id": "string",
   "buyerOrgId": "string",
   "supplierOrgId": "string",
   "supplier": "string",
   "amount": number,
   "currency": "string",
   "status": "draft" | "pending_approval" | "rejected" | "awaiting_fulfillment" | "invoiced" | "paid" | "canceled" | "closed" | "expired",
   "invoiceId": "string",
   "provider": "aws" | "gcp" | "azure" | "oci",
   "payerAccounts": [
      { "id": "string", "label": "string", "detail": "string" }
   ],
   "billTo": "string",
   "costCenter": "string",
   "budgetOwner": "string",
   "buyerName": "string",
   "supplierSite": "string",
   "notes": "string",
   "lineItems": [
      {
      "lineNo": number,
      "scopeType": "sku" | "service" | "account" | "custom",
      "scopeId": "string" | null,
      "name": "string",
      "ruleType": "quantity" | "amount",
      "unit": "string",
      "unitPrice": number,
      "qty": number,
      "nteAmount": number,
      "currency": "string",
      "paymentTerms": "string",
      "validFrom": "string",
      "validUntil": "string",
      "consumed": number,
      "customerLineNo": "string",
      "supplierSku": "string",
      "priceSource": "contract" | "list" | "manual"
    }
   ],
   "approval": {
      "requester": "string",
      "requesterEmployeeKey": "string",
      "groups": [
        {
          "id": "string",
          "mode": "sequential" | "parallel",
          "label": "string",
          "status": "pending" | "approved" | "rejected",
          "stages": [
            { "id": "string", "label": "string", "approverEmployeeKey": "string", "approverName": "string", "status": "pending" | "approved" | "rejected" }
          ]
        }
      ],
      "groupIndex": number,
      "status": "pending" | "approved" | "rejected",
      "history": [
        { "stage": "string", "by": "string", "at": "string", "action": "approved" | "rejected" | "limit_exceeded" | "reassigned" | "skipped" }
      ],
      "autonomy": boolean,
      "selfApproved": boolean
    },
   "createdAt": "string"
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 400 Bad Request — The PO's approval chain has no pending stage.
  • 403 Forbidden — Code HUMAN_APPROVAL_REQUIRED. The caller is an API key. Approval steps name a specific employee, and a key can never be one, so a key can neither approve nor reject a step. Ask the employee the step names to act on it in Pleaxy.
  • 403 Forbidden — Code APPROVER_NOT_ASSIGNED. The current step has no approver assigned, so nobody can act on it. An administrator must assign one with ReassignPurchaseOrderApprovalStage / ReassignInvoiceApprovalStage first.
  • 403 Forbidden — The caller is not the employee the current step names. Only that employee may approve or reject it — no role or permission substitutes for being named.
  • 404 Not Found — No PO with this id exists in the workspace.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

ReassignPurchaseOrderApprovalStage

POSTBuyer

Re-points one pending step of this order's approval chain at a different named employee. Administrator only (approvalSettings:edit), and signed-in sessions only. Because a step is actionable solely by the employee it names, this is the only way to move a step whose approver has left or was never assigned — there is no fallback actor. It changes this document's chain only: the org's configured chain in Settings is untouched, and the change is recorded in the approval history as a reassigned entry.

Request Syntax

POST /v1/workspaces/{regionId}/purchase-orders/{poId}/approval/reassign
{
   "stageId": "string",
   "approverEmployeeKey": "string",
   "reason": "string"
}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
poIdRequiredstringThe purchase order identifier.

Request Body

NameTypeDescription
stageIdRequiredstringId of the pending step to reassign. It may sit in any group of the chain, not only the current one.
approverEmployeeKeyRequiredstringEmployee key (Cognito sub) of the new approver. Must be a buyer member of this workspace, and not the acting administrator.
reasonstringWhy the step was reassigned, up to 280 characters. Shown in the approval history.

Response Syntax 201 Created

{
   "id": "string",
   "buyerOrgId": "string",
   "supplierOrgId": "string",
   "supplier": "string",
   "amount": number,
   "currency": "string",
   "status": "draft" | "pending_approval" | "rejected" | "awaiting_fulfillment" | "invoiced" | "paid" | "canceled" | "closed" | "expired",
   "invoiceId": "string",
   "provider": "aws" | "gcp" | "azure" | "oci",
   "payerAccounts": [
      { "id": "string", "label": "string", "detail": "string" }
   ],
   "billTo": "string",
   "costCenter": "string",
   "budgetOwner": "string",
   "buyerName": "string",
   "supplierSite": "string",
   "notes": "string",
   "lineItems": [
      {
      "lineNo": number,
      "scopeType": "sku" | "service" | "account" | "custom",
      "scopeId": "string" | null,
      "name": "string",
      "ruleType": "quantity" | "amount",
      "unit": "string",
      "unitPrice": number,
      "qty": number,
      "nteAmount": number,
      "currency": "string",
      "paymentTerms": "string",
      "validFrom": "string",
      "validUntil": "string",
      "consumed": number,
      "customerLineNo": "string",
      "supplierSku": "string",
      "priceSource": "contract" | "list" | "manual"
    }
   ],
   "approval": {
      "requester": "string",
      "requesterEmployeeKey": "string",
      "groups": [
        {
          "id": "string",
          "mode": "sequential" | "parallel",
          "label": "string",
          "status": "pending" | "approved" | "rejected",
          "stages": [
            { "id": "string", "label": "string", "approverEmployeeKey": "string", "approverName": "string", "status": "pending" | "approved" | "rejected" }
          ]
        }
      ],
      "groupIndex": number,
      "status": "pending" | "approved" | "rejected",
      "history": [
        { "stage": "string", "by": "string", "at": "string", "action": "approved" | "rejected" | "limit_exceeded" | "reassigned" | "skipped" }
      ],
      "autonomy": boolean,
      "selfApproved": boolean
    },
   "createdAt": "string"
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 400 Bad Request — The chain is already approved or rejected; the step has already been approved or rejected; the new approver isn't an employee of this workspace; the new approver raised the order / entered the invoice, is separation-of-duties excluded, already cleared the spend limit with their own authority, or already holds another seat on the same step; or the step is the spend-limit review, whose approver is a frozen audit fact (change it in Settings › Spend limits).
  • 403 Forbidden — The caller lacks approvalSettings:edit (administrator), or named themselves as the new approver — assigning a live step to yourself makes one person the whole chain with no trace of policy. Code HUMAN_APPROVAL_REQUIRED when the caller is an API key: only a signed-in administrator can reassign an approval step.
  • 404 Not Found — No document with this id exists in the workspace, or the chain has no step with this stageId.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

Invoices

A supplier's invoice is created by flipping a purchase order (source: "po-flip") or by entering it for a connected buyer (source: "manual", see CreateInvoice), or sends it from its billing system with an API key (source: "feed", see SubmitFeedInvoice) once that buyer turns on automated invoices. A buyer can also record one directly (source: "direct", see SubmitInvoice), as JSON, cXML (InvoiceDetailRequest), or UBL 2.1 (<Invoice>). Every invoice lives in the buyer's workspace and moves through matching, approval, and settlement. An invoice is "pending" when there is a real usage figure to match it against: a PO, and a live billing connection for the PO's provider with a synced invoice. usageAmount, usageCurrency and tolerancePct are present only then, and on the "matched" or "discrepancy" result. Otherwise it is "unverified", with unverifiedReason "no_purchase_order" or "no_billing_connection" (buyer view only; absent on older invoices, which read as "no_purchase_order"). A Supplier caller sees matchStatus as it is, but never unverifiedReason or the usage fields. An unverified invoice is never matched and never approved automatically: a person reviews it with ResolveInvoiceDiscrepancy.

/v1/workspaces/{regionId}/invoices

ListInvoices

GETBoth

Lists invoices, in no particular order. A Buyer caller gets the invoices its workspace received; a Supplier caller gets the invoices it issued to connected buyers, whichever buyer workspace each lives in. Optionally scoped to a createdAt range via from/to — pass a calendar month's bounds to get a per-month view. A Supplier caller gets a reduced view of each invoice (see GetInvoice) — the buyer's matching internals and full approval workflow are withheld.

Request Syntax

GET /v1/workspaces/{regionId}/invoices

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
fromstringISO 8601 date/timestamp — only items created on or after this are returned.
tostringISO 8601 date/timestamp — only items created on or before this are returned.

Response Syntax 200 OKShown for: Buyer caller

[
   {
      "id": "string",
      "po": "string",
      "buyerOrgId": "string",
      "supplierOrgId": "string",
      "supplier": "string",
      "amount": number,
      "currency": "string",
      "usageAmount": number,
      "usageCurrency": "string",
      "tolerancePct": number,
      "matchStatus": "pending" | "matched" | "discrepancy" | "unverified",
      "unverifiedReason": "no_purchase_order" | "no_billing_connection",
      "discrepancyReason": "amount" | "currency",
      "paid": boolean,
      "dueInDays": number,
      "approval": {
         "requester": "string",
         "requesterEmployeeKey": "string",
         "groups": [
           {
             "id": "string",
             "mode": "sequential" | "parallel",
             "label": "string",
             "status": "pending" | "approved" | "rejected",
             "stages": [
               { "id": "string", "label": "string", "approverEmployeeKey": "string", "approverName": "string", "status": "pending" | "approved" | "rejected" }
             ]
           }
         ],
         "groupIndex": number,
         "status": "pending" | "approved" | "rejected",
         "history": [
           { "stage": "string", "by": "string", "at": "string", "action": "approved" | "rejected" | "limit_exceeded" | "reassigned" | "skipped" }
         ],
         "autonomy": boolean,
         "selfApproved": boolean
       },
      "createdAt": "string",
      "lastRemindedAt": "string",
      "rejection": { "reasonCode": "amount_incorrect" | "not_ordered_or_not_received" | "duplicate" | "missing_or_wrong_po" | "wrong_billing_entity" | "other", "reason": "string", "at": "string" },
      "replacesInvoiceId": "string",
      "source": "po-flip" | "direct" | "manual" | "feed",
      "submittedFormat": "json" | "cxml" | "ubl",
      "submittedBy": "string",
      "externalInvoiceNumber": "string",
      "lineItems": [
         { "description": "string", "qty": number, "unitPrice": number, "discount": number, "charge": number, "amount": number }
      ],
      "adjustments": [
         { "type": "discount" | "charge", "description": "string", "amount": number }
      ],
      "taxAmount": number,
      "taxBreakdown": [ { "description": "string", "rate": number, "amount": number } ],
      "paymentCurrencyAmount": { "currencyCode": "string", "totalAmount": number, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
      "taxCurrencyAmount": { "currencyCode": "string", "amountBreakdown": { "taxes": { "totalAmount": number } }, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
      "baseCurrencyAmount": { "currencyCode": "string", "totalAmount": number },
      "rateWarnings": [
         { "object": "payment" | "tax", "currency": "string", "statedRate": number, "referenceRate": number, "source": "provider" | "override", "asOf": "string", "deviationPct": number }
      ],
      "payment": { "status": "recorded", "method": "string", "paymentDate": "string", "reference": "string", "paidAmount": { "amount": number, "currency": "string", "rate": number, "rateDate": "string" } }
   }
]

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

GetInvoice

GETBoth

Fetches a single invoice by id. For a Buyer caller, regionId is the invoice's own workspace. For a Supplier caller, regionId is the supplier's own workspace and the invoice is looked up among the org's incoming invoices regardless of which buyer workspace issued it — the same lookup ListSupplierWorkspace uses. GET …/invoices/{invoiceId}/pdf takes the same buyer query parameter.

Request Syntax

GET /v1/workspaces/{regionId}/invoices/{invoiceId}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
invoiceIdRequiredstringThe invoice identifier.
buyerstringSupplier callers only, and only needed after a 409 *_ID_AMBIGUOUS: the buyer's Workspace ID (ending in .buyer) or region id, from the 409's buyers list. Only that buyer's document is considered; none there is a 404. A .supplier Workspace ID is 400 BUYER_QUALIFIER_INVALID ("The buyer parameter must be one buyer's Workspace ID (ending in .buyer), from the buyers list in the 409 response."). Ignored for a Buyer caller.

Response Syntax 200 OKShown for: Buyer caller

{
   "id": "string",
   "po": "string",
   "buyerOrgId": "string",
   "supplierOrgId": "string",
   "supplier": "string",
   "amount": number,
   "currency": "string",
   "usageAmount": number,
   "usageCurrency": "string",
   "tolerancePct": number,
   "matchStatus": "pending" | "matched" | "discrepancy" | "unverified",
   "unverifiedReason": "no_purchase_order" | "no_billing_connection",
   "discrepancyReason": "amount" | "currency",
   "paid": boolean,
   "dueInDays": number,
   "approval": {
      "requester": "string",
      "requesterEmployeeKey": "string",
      "groups": [
        {
          "id": "string",
          "mode": "sequential" | "parallel",
          "label": "string",
          "status": "pending" | "approved" | "rejected",
          "stages": [
            { "id": "string", "label": "string", "approverEmployeeKey": "string", "approverName": "string", "status": "pending" | "approved" | "rejected" }
          ]
        }
      ],
      "groupIndex": number,
      "status": "pending" | "approved" | "rejected",
      "history": [
        { "stage": "string", "by": "string", "at": "string", "action": "approved" | "rejected" | "limit_exceeded" | "reassigned" | "skipped" }
      ],
      "autonomy": boolean,
      "selfApproved": boolean
    },
   "createdAt": "string",
   "lastRemindedAt": "string",
   "rejection": { "reasonCode": "amount_incorrect" | "not_ordered_or_not_received" | "duplicate" | "missing_or_wrong_po" | "wrong_billing_entity" | "other", "reason": "string", "at": "string" },
   "replacesInvoiceId": "string",
   "source": "po-flip" | "direct" | "manual" | "feed",
   "submittedFormat": "json" | "cxml" | "ubl",
   "submittedBy": "string",
   "externalInvoiceNumber": "string",
   "lineItems": [
      { "description": "string", "qty": number, "unitPrice": number, "discount": number, "charge": number, "amount": number }
   ],
   "adjustments": [
      { "type": "discount" | "charge", "description": "string", "amount": number }
   ],
   "taxAmount": number,
   "taxBreakdown": [ { "description": "string", "rate": number, "amount": number } ],
   "paymentCurrencyAmount": { "currencyCode": "string", "totalAmount": number, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
   "taxCurrencyAmount": { "currencyCode": "string", "amountBreakdown": { "taxes": { "totalAmount": number } }, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
   "baseCurrencyAmount": { "currencyCode": "string", "totalAmount": number },
   "rateWarnings": [
      { "object": "payment" | "tax", "currency": "string", "statedRate": number, "referenceRate": number, "source": "provider" | "override", "asOf": "string", "deviationPct": number }
   ],
   "payment": { "status": "recorded", "method": "string", "paymentDate": "string", "reference": "string", "paidAmount": { "amount": number, "currency": "string", "rate": number, "rateDate": "string" } }
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 404 Not Found — No invoice with this id exists in the caller's workspace.
  • 409 Conflict — Supplier callers only. Code INVOICE_ID_AMBIGUOUS: more than one of your buyers holds an invoice from you with this id (ids are unique only within a buyer), so Pleaxy won't guess which one you mean. Nothing is read or changed. The body's buyers lists each buyer's Workspace ID and name: repeat the call with ?buyer=<that Workspace ID>.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

SubmitInvoice

POSTBoth

Buyer operation: records a supplier's invoice document directly, without a prior Pleaxy-created PO flip. A JSON body with a connectionId is CreateInvoice instead. The JSON body below (supplier and amount, no connectionId) is deprecated and accepted until v2. A Supplier caller gets 400 SUPPLIER_DIRECT_INVOICE_RETIRED — an invoice sent this way reached no buyer; suppliers use CreateInvoice or FlipPurchaseOrderToInvoice instead. Accepts three request formats, selected by Content-Type: application/json (a CreateInvoiceDto body), or application/xml / text/xml containing either a cXML InvoiceDetailRequest or a UBL 2.1 Invoice document (auto-detected from the parsed XML). If poId is given, it's validated against an existing PO in awaiting_fulfillment status and the invoice is "pending" when a live billing figure exists for the PO's provider, and otherwise "unverified" with unverifiedReason "no_billing_connection"; if omitted, the invoice starts "unverified" with unverifiedReason "no_purchase_order". An XML document gets the same discount, charge and tax mapping, negative-line refusal and totals reconciliation as SubmitFeedInvoice, and a failure is 400 INVOICE_DOCUMENT_INVALID. The JSON body is unchanged.

Buyer callers only: call with a signed-in session or a buyer workspace's scoped X-Api-Key (see API keys). Supplier callers always get 400. Use CreateInvoice instead.

Request Syntax

POST /v1/workspaces/{regionId}/invoices
// Content-Type: application/json
{
   "invoiceNumber": "string",
   "poId": "string",
   "supplier": "string",
   "amount": number,
   "currency": "string",
   "dueInDays": number,
   "lineItems": [
      { "description": "string", "qty": number, "unitPrice": number, "amount": number }
   ]
}

// Content-Type: application/xml or text/xml — a raw cXML InvoiceDetailRequest
// or UBL 2.1 <Invoice> document, up to 2MB.

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.

Request Body

NameTypeDescription
invoiceNumberstringSupplier-provided invoice number, used to dedupe retried submissions within the region.
poIdstringPleaxy PO id this invoice fulfills, e.g. "PO-AB12CD34". Omit to submit a standalone invoice.
supplierRequiredstringSupplier / counterparty display name.
amountRequirednumberInvoice total. Must be greater than 0.
currencystringISO 4217 code (here, or as UBL's currencyID / cXML's Money currency). Must be the region currency, and an invoice against a PO must be in the PO's currency. Pleaxy doesn't convert currencies.
dueInDaysnumberPayment terms in days. Defaults to 30.
lineItemsArray<{ description, qty?, unitPrice?, amount }>Optional line-item breakdown of the invoice.
remittanceInstructionsobjectWhere the buyer should pay, stored with the invoice and never changed afterwards: { method, payeeName, bankName?, iban?, accountNumber?, bic?, routingNumber?, mailingAddress?, paymentReference?, notes? }. method is bank_transfer (needs iban or accountNumber), wire (iban or accountNumber, plus bic or routingNumber), ach (accountNumber and a 9-digit ABA routingNumber), check (mailingAddress) or other (notes). Pleaxy never verifies it; the buyer is warned when the payee or account differs from this supplier's previous invoice.
pdfBase64stringBase64-encoded PDF of the invoice.

Response Syntax 201 CreatedShown for: Buyer caller

{
   "id": "string",
   "po": "string",
   "buyerOrgId": "string",
   "supplierOrgId": "string",
   "supplier": "string",
   "amount": number,
   "currency": "string",
   "matchStatus": "pending" | "matched" | "discrepancy" | "unverified",
   "paid": boolean,
   "dueInDays": number,
   "approval": { "status": "pending" | "approved" | "rejected" },
   "createdAt": "string",
   "lastRemindedAt": "string",
   "rejection": { "reasonCode": "amount_incorrect" | "not_ordered_or_not_received" | "duplicate" | "missing_or_wrong_po" | "wrong_billing_entity" | "other", "reason": "string", "at": "string" },
   "replacesInvoiceId": "string",
   "source": "po-flip" | "direct" | "manual" | "feed",
   "submittedFormat": "json" | "cxml" | "ubl",
   "externalInvoiceNumber": "string",
   "lineItems": [
      { "description": "string", "qty": number, "unitPrice": number, "discount": number, "charge": number, "amount": number }
   ],
   "adjustments": [
      { "type": "discount" | "charge", "description": "string", "amount": number }
   ],
   "taxAmount": number
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Response Elements

NameTypeDescription
idstringInvoice identifier. "INV-<PO suffix>" when poId was given, otherwise a random "INV-XXXXXXXX".
matchStatusstring"pending" if poId was given and a live billing figure exists for its provider, "unverified" otherwise.
unverifiedReasonstringSet when matchStatus is "unverified": "no_purchase_order" or "no_billing_connection".
currencystringThe currency the invoice is recorded in, uppercase.
sourcestringAlways "direct" for this endpoint.
submittedFormatstring"json", "cxml", or "ubl", based on the request's Content-Type.

Errors

  • 400 Bad Request (SUPPLIER_DIRECT_INVOICE_RETIRED) — The caller is a Supplier. Returned for every content type and credential, before the body is parsed as an invoice or validated, and nothing is stored. The request size limit still applies first, and a body that isn't valid JSON gets the generic 400 from the JSON parser. See ListInvoiceCounterparties and CreateInvoice.
  • 400 Bad Request — amount is not greater than zero, the JSON body fails validation, the referenced PO isn't in awaiting_fulfillment status, the XML body isn't recognizable cXML/UBL or carries a DOCTYPE or entity declaration (INVOICE_DOCUMENT_INVALID; cXML's standard external DTD reference is allowed), or the invoice's currency can't be taken — it isn't the region currency ("This invoice is in GBP, but Europe is invoiced in EUR. Ask the supplier to reissue it in EUR — Pleaxy doesn't convert currencies."), or it isn't the referenced PO's ("This invoice is in GBP, but PO-AB12CD34 is in EUR. Ask the supplier to reissue it in EUR — Pleaxy doesn't convert currencies."). The two currency refusals carry code INVOICE_CURRENCY_MISMATCH and INVOICE_PO_CURRENCY_MISMATCH.
  • 404 Not Found — poId was given but no PO with that id exists in the workspace.
  • 409 Conflict — invoiceNumber was already used by another invoice in this workspace, or the referenced PO already has an invoice.
  • 415 Unsupported Media Type — The Content-Type header is anything other than application/json, application/xml, or text/xml.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or a Buyer caller's procurement role lacks 'create' access to invoices — only administrator and finance have it ("Your procurement role ('read-only') doesn't have 'create' access to 'invoices'"; an API key is also told to re-mint itself with a role that grants it). On a bare regionId, a caller holding both profiles is treated as the Buyer; use the .supplier Workspace ID to act as the Supplier.

Example

Request

curl -X POST "$PLEAXY_API_BASE_URL/v1/workspaces/reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer/invoices" \
  -H "X-Api-Key: $PLEAXY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "supplier": "Acme Cloud Services",
    "amount": 4820.00,
    "currency": "USD",
    "dueInDays": 30,
    "invoiceNumber": "ACME-2026-0091"
  }'

Response

{
  "id": "INV-7F3A9C21",
  "supplier": "Acme Cloud Services",
  "amount": 4820,
  "currency": "USD",
  "matchStatus": "unverified",
  "paid": false,
  "dueInDays": 30,
  "createdAt": "2026-02-03T09:12:44.000Z",
  "source": "direct",
  "submittedFormat": "json",
  "externalInvoiceNumber": "ACME-2026-0091"
}

ListInvoiceCounterparties

GETBoth

The active counterparties the caller can invoice through CreateInvoice: for a Buyer caller its approved suppliers, for a Supplier caller the buyers that approved it. Each entry carries the connection id to pass as connectionId, the buyer's currency, and the open purchase orders between exactly that pair.

Signed-in session only. An API key gets 403.

Request Syntax

GET /v1/workspaces/{regionId}/invoices/counterparties

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.

Response Syntax 200 OK

{
   "counterparties": [
      {
         "connectionId": "string",
         "name": "string",
         "currency": "string",
         "openPurchaseOrders": [
            { "id": "string", "amount": number, "createdAt": "string", "label": "string" }
         ],
         "invoiceFeedEnabled": boolean,
         "invoiceFeedUpdatedAt": "string"
      }
   ]
}

Response Elements

NameTypeDescription
connectionIdstringThe supplier connection to invoice — CreateInvoice's connectionId.
namestringThe counterparty's name: the supplier's for a Buyer caller, the buyer's for a Supplier caller.
currencystringThe buyer's currency. Every invoice to this counterparty is recorded in it.
openPurchaseOrdersArrayPurchase orders awaiting fulfillment, with no invoice yet, between exactly this buyer and supplier — the poIds CreateInvoice accepts.
invoiceFeedEnabledbooleanBuyer callers only: true when this supplier's automated invoices are on (see SetInvoiceFeed). Absent means off.
invoiceFeedUpdatedAtstringBuyer callers only: when automated invoices were last turned on or off. Absent if never.

Errors

  • 403 Forbidden — The caller authenticated with an API key rather than a signed-in session ("This action requires a logged-in user session, not an API key"), its credential doesn't grant access to this workspace, or a Buyer caller's procurement role can't create invoices.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.

CreateInvoice

POSTBoth

Sent as an application/json body with a connectionId; the same path without one is SubmitInvoice. This replaced POST /v1/workspaces/{regionId}/invoices/manual, which now returns 410 ROUTE_MERGED. Records an invoice for one active counterparty from ListInvoiceCounterparties — a Supplier invoicing one of its buyers, or a Buyer entering one of its suppliers' invoices. The invoice is stored in the buyer's workspace, in the buyer's currency, so both sides see it. The server computes each line's amount as qty × unitPrice + charge − discount, and the total as the lines, minus invoice-level discounts, plus invoice-level charges, plus taxAmount. Each computed figure is rounded half up to the buyer currency's minor unit: to the cent for USD and EUR, to whole units for JPY (3 × 333.33 JPY is a line amount of 1,000). That total is the amount matched, approved, capped and paid. With poId it is "pending" when a live billing figure exists for the PO's provider, and otherwise "unverified" with unverifiedReason "no_billing_connection" (buyer view only); without poId it is "unverified" with unverifiedReason "no_purchase_order". A Buyer who enters an invoice can't approve, resolve or settle it.

Buyer callers: a signed-in session or a buyer workspace's X-Api-Key. Whoever recorded it, and for a key also the person who created the key, can't approve, resolve or settle it. Supplier callers: signed-in session only; a supplier API key gets 400 SUPPLIER_DIRECT_INVOICE_RETIRED.

Request Syntax

POST /v1/workspaces/{regionId}/invoices
{
   "connectionId": "string",
   "poId": "string",
   "invoiceNumber": "string",
   "invoiceDate": "string",
   "dueInDays": number,
   "lineItems": [
      { "description": "string", "qty": number, "unitPrice": number, "discount": number, "charge": number }
   ],
   "adjustments": [
      { "type": "discount" | "charge", "description": "string", "amount": number }
   ],
   "taxAmount": number,
   "currency": "string",
   "pdfBase64": "string"
}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.

Request Body

NameTypeDescription
connectionIdRequiredstringThe counterparty to invoice — a connectionId from ListInvoiceCounterparties.
poIdstringAn open purchase order between exactly this buyer and supplier, from the counterparty's openPurchaseOrders.
invoiceNumberRequiredstring1–64 characters: letters, digits, '.', '_', '/' and '-', starting with a letter or digit. Unique per supplier within the buyer's workspace, case-insensitively.
invoiceDateRequiredstringYYYY-MM-DD. Not in the future and not more than 365 days back.
dueInDaysnumberPayment terms in days, 0–365. Defaults to 30.
lineItemsRequiredArray<{ description, qty, unitPrice, discount?, charge? }>1–100 lines. description is 1–200 characters, qty is greater than 0 and at most 1,000,000, unitPrice is 0 to 1,000,000,000. A reduction is a discount, never a negative price: a negative unitPrice is 400 ("lineItems[1].unitPrice can't be negative. Send a reduction as that line's "discount", or as an "adjustments" entry with "type": "discount"."). discount and charge are optional totals for the line, 0 or more with no more decimal places than the buyer's currency allows (2 for USD and EUR, none for JPY: otherwise 400, e.g. "taxAmount must be a whole number in JPY."), and the line's discount can't be more than qty × unitPrice + charge. null isn't accepted: omit a field that doesn't apply. The total after discounts must be greater than 0, and the total at most 1,000,000,000,000,000.
adjustmentsArray<{ type, description, amount }>Invoice-level discounts and charges, before tax. At most 20. type is "discount" or "charge"; description is 1–200 characters, stored without control characters, line breaks or bidirectional-text controls; amount is a positive magnitude, 0.01 or more with no more decimal places than the buyer's currency allows (type gives the sign).
taxAmountnumberTotal tax on the invoice, as stated by the supplier. It is included in the invoice total. Pleaxy doesn't calculate tax. 0 or more, with no more decimal places than the buyer's currency allows. Omit it when the invoice states no tax; null is refused.
currencystringISO 4217, optional. Checked only: it must be the buyer's currency, which is always the one stored.
pdfBase64stringBase64-encoded PDF of the invoice, up to 8MB.
remittanceInstructionsobjectWhere the buyer should pay, stored with the invoice and never changed afterwards: { method, payeeName, bankName?, iban?, accountNumber?, bic?, routingNumber?, mailingAddress?, paymentReference?, notes? }. method is bank_transfer (needs iban or accountNumber), wire (iban or accountNumber, plus bic or routingNumber), ach (accountNumber and a 9-digit ABA routingNumber), check (mailingAddress) or other (notes). Pleaxy never verifies it; the buyer is warned when the payee or account differs from this supplier's previous invoice.
taxBreakdownArray<{ description, amount, rate? }>Optional. Tax by type in the invoice's currency, e.g. [{ "description": "IVA", "rate": 21, "amount": 199.50 }, { "description": "Percepción IIBB", "amount": 28.50 }]. rate is a percentage and optional. Must sum to taxAmount, which is then required (400 TAX_BREAKDOWN_MISMATCH). At most 20. Display only. UBL: the TaxSubtotals of the TaxTotal in the document currency, kept when they add up.
paymentCurrencyAmountobjectOptional. Only when the invoice names the amount owed in another currency (e.g. AWS billing an Argentine customer in ARS); then totalAmount is the amount due, which payables, payments and the payment record use: { currencyCode, totalAmount, totalAmountBeforeTax?, amountBreakdown?: { subTotalAmount?, discounts?, fees?, taxes? }, currencyExchangeDetails: { rate, rateDate?, rateSource?, sourceCurrencyCode?, targetCurrencyCode? } }, the same shape as AWS Invoicing's PaymentCurrencyAmount. rate is 1 invoice currency = rate currencyCode, as printed on the invoice. Leave it off when the invoice is converted at the payment-date rate. Each stated figure must be within 0.5% (or one minor unit) of the invoice's figure × rate (400 STATED_AMOUNT_RATE_MISMATCH, naming both figures), each breakdown must add up to its total, and currencyCode can't be the invoice's own (400 SAME_CURRENCY). rateDate is YYYY-MM-DD, not in the future and at most a day after the invoice date. UBL: cbc:PaymentCurrencyCode with cac:PaymentExchangeRate; cXML: Money@alternateCurrency/alternateAmount on DueAmount. Recorded as stated; Pleaxy doesn't convert anything.
taxCurrencyAmountobjectOptional. Tax in the legal currency of the buyer's country when that isn't the invoice's currency. Same shape as paymentCurrencyAmount; amountBreakdown.taxes.totalAmount is required and totalAmount is optional. Needs taxAmount. UBL: cbc:TaxCurrencyCode's cac:TaxTotal with cac:TaxExchangeRate, or without it (EN 16931 BT-111) a rate derived from the two tax totals; cXML: Money@alternateAmount on Tax. A derived rate has rateSource "derived" and isn't checked against the amounts it came from. Display only.

Response Syntax 201 CreatedShown for: Buyer caller

{
   "id": "string",
   "po": "string",
   "buyerOrgId": "string",
   "supplierOrgId": "string",
   "supplier": "string",
   "amount": number,
   "currency": "string",
   "usageAmount": number,
   "usageCurrency": "string",
   "tolerancePct": number,
   "matchStatus": "pending" | "matched" | "discrepancy" | "unverified",
   "unverifiedReason": "no_purchase_order" | "no_billing_connection",
   "discrepancyReason": "amount" | "currency",
   "paid": boolean,
   "dueInDays": number,
   "approval": {
      "requester": "string",
      "requesterEmployeeKey": "string",
      "groups": [
        {
          "id": "string",
          "mode": "sequential" | "parallel",
          "label": "string",
          "status": "pending" | "approved" | "rejected",
          "stages": [
            { "id": "string", "label": "string", "approverEmployeeKey": "string", "approverName": "string", "status": "pending" | "approved" | "rejected" }
          ]
        }
      ],
      "groupIndex": number,
      "status": "pending" | "approved" | "rejected",
      "history": [
        { "stage": "string", "by": "string", "at": "string", "action": "approved" | "rejected" | "limit_exceeded" | "reassigned" | "skipped" }
      ],
      "autonomy": boolean,
      "selfApproved": boolean
    },
   "createdAt": "string",
   "lastRemindedAt": "string",
   "rejection": { "reasonCode": "amount_incorrect" | "not_ordered_or_not_received" | "duplicate" | "missing_or_wrong_po" | "wrong_billing_entity" | "other", "reason": "string", "at": "string" },
   "replacesInvoiceId": "string",
   "source": "po-flip" | "direct" | "manual" | "feed",
   "submittedFormat": "json" | "cxml" | "ubl",
   "submittedBy": "string",
   "externalInvoiceNumber": "string",
   "lineItems": [
      { "description": "string", "qty": number, "unitPrice": number, "discount": number, "charge": number, "amount": number }
   ],
   "adjustments": [
      { "type": "discount" | "charge", "description": "string", "amount": number }
   ],
   "taxAmount": number,
   "taxBreakdown": [ { "description": "string", "rate": number, "amount": number } ],
   "paymentCurrencyAmount": { "currencyCode": "string", "totalAmount": number, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
   "taxCurrencyAmount": { "currencyCode": "string", "amountBreakdown": { "taxes": { "totalAmount": number } }, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
   "baseCurrencyAmount": { "currencyCode": "string", "totalAmount": number },
   "rateWarnings": [
      { "object": "payment" | "tax", "currency": "string", "statedRate": number, "referenceRate": number, "source": "provider" | "override", "asOf": "string", "deviationPct": number }
   ],
   "payment": { "status": "recorded", "method": "string", "paymentDate": "string", "reference": "string", "paidAmount": { "amount": number, "currency": "string", "rate": number, "rateDate": "string" } }
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Response Elements

NameTypeDescription
sourcestringAlways "manual" for this endpoint.
amountnumberThe payable total: the lines after their own discounts and charges, minus invoice-level discounts, plus invoice-level charges, plus taxAmount. Matching, approval, autonomy, contract caps and settlement all use it.
lineItemsArrayEach line as stored. amount is the net line, before tax; discount and charge appear only on a line that has them.
adjustmentsArrayInvoice-level discounts and charges. Absent when there are none.
taxAmountnumberThe stated tax, already included in amount. Display only: no decision reads it. Absent when none was stated.
currencystringThe buyer's currency.

Errors

  • 400 Bad Request — The body fails validation (including more than 20 adjustments, a negative taxAmount, or more than 2 decimal places on a discount, charge, adjustment or taxAmount), INVOICE_DOCUMENT_INVALID (a negative unitPrice; a line discount larger than its line: "Line 2's discount (12.00 USD) is more than the line (10.00 USD). Check lineItems[1].discount."; a total after discounts that isn't greater than 0: "The invoice total after discounts must be greater than zero."; a total over 1,000,000,000,000,000; or more decimal places than the buyer's currency allows on a discount, charge, adjustment or taxAmount ("lineItems[0].discount must be a whole number in JPY.")), invoiceDate is out of range (INVOICE_DATE_OUT_OF_RANGE), the counterparty isn't connected yet (COUNTERPARTY_NOT_ACTIVE), poId can't be invoiced (PO_NOT_INVOICEABLE), or currency isn't the buyer's (INVOICE_CURRENCY_MISMATCH).
  • 404 Not Found — No counterparty with this connectionId is visible to the caller.
  • 409 Conflict — This supplier already has an invoice with this number (INVOICE_NUMBER_TAKEN).
  • 400 Bad Request — The body has an amount (AMOUNT_NOT_ACCEPTED: "Leave amount off: Pleaxy computes the total from the line items."), or a Supplier called with an API key (SUPPLIER_DIRECT_INVOICE_RETIRED).
  • 403 Forbidden — The credential doesn't grant access to this workspace, or a Buyer caller's procurement role can't create invoices.
  • 422 Unprocessable Entity — The counterparty is the caller's own buyer or supplier profile (SELF_DEALING).
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.

GetExchangeRates

GETBuyer

The rates Pleaxy uses only to compare orders and invoices in other currencies with your approval thresholds, amount conditions, auto-approval limits, automation rules and spend limits. Amounts, invoices and payments are never converted. One row per currency your orders, invoices and suppliers use: the reference rate into your workspace's currency (a daily rate from Exchange Rate API, used while it is at most 3 days old). Workspaces can't set their own rates. With no fresh rate, a document in that currency goes through every approval step and is never auto-approved. A chain built with a rate records it as approval.fx: { from, to, rate, source: provider | none, asOf, controlAmount, supplierStated? }. When an invoice states its own figure in your workspace's currency (its paymentCurrencyAmount, or its total at its taxCurrencyAmount rate), controlAmount is the larger of that figure and the figure at the reference rate, so a low stated rate can't lower the routing; supplierStated { amount, rateSource, rateDate } shows the invoice's figure. With no fresh reference rate, the chain still goes through every approval step, never to the invoice's figure. An invoice whose stated rate is more than warningPct from the reference rate when it arrives carries rateWarnings (buyer view only); they never block it. Rates By Exchange Rate API (https://www.exchangerate-api.com).

Signed-in session only, with 'view' access to approval settings. An API key gets 403 HUMAN_SESSION_REQUIRED: the rate provider's terms don't allow its rates to be offered through an API.

Request Syntax

GET /v1/workspaces/{regionId}/exchange-rates

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.

Response Syntax 200 OK

{
   "workspaceCurrency": "string",
   "warningPct": number,
   "rows": [
      { "currency": "string", "providerRate": number, "providerAsOf": "string" }
   ],
   "provider": { "asOf": "string", "fresh": boolean, "attribution": { "text": "string", "url": "string" } }
}

Errors

  • 403 Forbidden — An API key (HUMAN_SESSION_REQUIRED), a Supplier caller, or a procurement role without 'view' on approval settings.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.

SetExchangeRateWarning

PUTBuyer

Sets how far, in percent, an invoice's own stated rate (its paymentCurrencyAmount or taxCurrencyAmount rate) may be from the reference rate (a fresh daily rate from Exchange Rate API) before the invoice carries rateWarnings. Default 5. Checked when an invoice arrives; invoices already received keep their warnings. A warning never blocks an invoice. With no fresh reference rate there's nothing to compare and no warning.

Signed-in session only (an API key gets 403 HUMAN_SESSION_REQUIRED), with 'edit' access to approval settings: administrators.

Request Syntax

PUT /v1/workspaces/{regionId}/exchange-rates
{
   "warningPct": number
}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.

Request Body

NameTypeDescription
warningPctRequirednumber0 to 100.

Response Syntax 200 OK

{
   "warningPct": number
}

Errors

  • 400 Bad Request — warningPct missing, or not a number from 0 to 100.
  • 403 Forbidden — An API key, a Supplier caller, or a procurement role without 'edit' on approval settings.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.

SetSupplierCurrencies

PUTBuyer

Sets two defaults for one supplier: "Bills in" (billingCurrency), the currency a supplier with no Pleaxy account invoices you in, used for its new orders and invoices; and "Pay in" (paymentCurrency), the currency you usually pay it in, suggested as a new invoice's payment currency. Absent leaves a field, null removes it. Existing orders and invoices keep their currencies. The supplier never sees either.

Signed-in session or a buyer workspace's X-Api-Key, with 'edit' access to suppliers: administrator or contracts-and-suppliers.

Request Syntax

PUT /v1/workspaces/{regionId}/supplier-connections/{connectionId}/currencies
{
   "billingCurrency": "string" | null,
   "paymentCurrency": "string" | null
}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
connectionIdRequiredstringThe supplier connection.

Request Body

NameTypeDescription
billingCurrencystring | nullISO 4217. Only for a supplier with no Pleaxy account; one with an account sets its own currencies.
paymentCurrencystring | nullISO 4217. For a supplier with an account, one it accepts payment in.

Response Syntax 200 OK

{
   "id": "string",
   "supplierName": "string",
   "billingCurrency": "string",
   "paymentCurrency": "string"
}

Errors

  • 400 Bad Request — A code that isn't ISO 4217, billingCurrency for a supplier with an account (BILLING_CURRENCY_SUPPLIER_OWNED), or a paymentCurrency it doesn't accept (PAYMENT_CURRENCY_NOT_ACCEPTED).
  • 403 Forbidden — A Supplier caller, or a procurement role without 'edit' on suppliers.
  • 404 Not Found — No such connection in this workspace.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.

SetInvoiceFeed

PUTBuyer

Turns a supplier's automated invoices (SubmitFeedInvoice) on or off for one approved connection. Off by default. Turning it off refuses the supplier's very next request; invoices already received stay in Invoices & matching and keep moving through approval. The supplier is notified (invoice_feed_changed). Setting the value it already has changes nothing. Returns the connection with invoiceFeedEnabled, invoiceFeedUpdatedAt and invoiceFeedUpdatedBy. ListInvoiceCounterparties shows each connection's current setting.

Signed-in session only (an API key gets 403), with 'edit' access to invoices: administrator or finance.

Request Syntax

PUT /v1/workspaces/{regionId}/supplier-connections/{connectionId}/invoice-feed
{
   "enabled": boolean
}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
connectionIdRequiredstringThe supplier connection.

Request Body

NameTypeDescription
enabledRequiredbooleantrue to accept this supplier's automated invoices, false to stop.

Response Syntax 200 OK

{
   "id": "string",
   "supplierName": "string",
   "status": "approved",
   "invoiceFeedEnabled": boolean,
   "invoiceFeedUpdatedAt": "string",
   "invoiceFeedUpdatedBy": "string"
}

Errors

  • 400 Bad Request — The connection isn't approved yet (COUNTERPARTY_NOT_ACTIVE), or enabled isn't a boolean.
  • 403 Forbidden — An API key rather than a signed-in session, a Supplier caller, or a procurement role without 'edit' on invoices (read-only, contracts-and-suppliers, relationships, requisitions).
  • 404 Not Found — No connection with this id in the workspace.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.

RunInvoiceMatch

POSTBuyer

Runs 3-way matching: compares the invoiced amount (amount) against metered usage (usageAmount, in usageCurrency — the provider's own currency, from a live billing connection). Only a real figure is compared: an older pending invoice with no real usage figure becomes "unverified" (unverifiedReason "no_billing_connection") instead, with no approval chain. Only a figure from an issued AWS invoice lets autonomy or an automation rule approve it with nobody acting; a match against any other source is sent to a person. Within tolerance (3%) the invoice becomes matched and an approval chain is built. Outside tolerance, it auto-resolves to matched if the overage qualifies for autonomy, otherwise it becomes discrepancy (discrepancyReason "amount") and waits for ResolveInvoiceDiscrepancy. Pleaxy doesn't convert currencies: when usage is in another currency than the invoice, nothing is compared and no autonomy or automation rule applies — the invoice becomes discrepancy with discrepancyReason "currency" and needs ResolveInvoiceDiscrepancy. The approval chain's requester is the authenticated caller (an API key is recorded as apikey:<keyId>), never a name passed in; the request takes no body.

Request Syntax

POST /v1/workspaces/{regionId}/invoices/{invoiceId}/run-match

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
invoiceIdRequiredstringThe invoice identifier.

Response Syntax 201 Created

{
   "id": "string",
   "po": "string",
   "buyerOrgId": "string",
   "supplierOrgId": "string",
   "supplier": "string",
   "amount": number,
   "currency": "string",
   "usageAmount": number,
   "usageCurrency": "string",
   "tolerancePct": number,
   "matchStatus": "pending" | "matched" | "discrepancy" | "unverified",
   "unverifiedReason": "no_purchase_order" | "no_billing_connection",
   "discrepancyReason": "amount" | "currency",
   "paid": boolean,
   "dueInDays": number,
   "approval": {
      "requester": "string",
      "requesterEmployeeKey": "string",
      "groups": [
        {
          "id": "string",
          "mode": "sequential" | "parallel",
          "label": "string",
          "status": "pending" | "approved" | "rejected",
          "stages": [
            { "id": "string", "label": "string", "approverEmployeeKey": "string", "approverName": "string", "status": "pending" | "approved" | "rejected" }
          ]
        }
      ],
      "groupIndex": number,
      "status": "pending" | "approved" | "rejected",
      "history": [
        { "stage": "string", "by": "string", "at": "string", "action": "approved" | "rejected" | "limit_exceeded" | "reassigned" | "skipped" }
      ],
      "autonomy": boolean,
      "selfApproved": boolean
    },
   "createdAt": "string",
   "lastRemindedAt": "string",
   "rejection": { "reasonCode": "amount_incorrect" | "not_ordered_or_not_received" | "duplicate" | "missing_or_wrong_po" | "wrong_billing_entity" | "other", "reason": "string", "at": "string" },
   "replacesInvoiceId": "string",
   "source": "po-flip" | "direct" | "manual" | "feed",
   "submittedFormat": "json" | "cxml" | "ubl",
   "submittedBy": "string",
   "externalInvoiceNumber": "string",
   "lineItems": [
      { "description": "string", "qty": number, "unitPrice": number, "discount": number, "charge": number, "amount": number }
   ],
   "adjustments": [
      { "type": "discount" | "charge", "description": "string", "amount": number }
   ],
   "taxAmount": number,
   "taxBreakdown": [ { "description": "string", "rate": number, "amount": number } ],
   "paymentCurrencyAmount": { "currencyCode": "string", "totalAmount": number, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
   "taxCurrencyAmount": { "currencyCode": "string", "amountBreakdown": { "taxes": { "totalAmount": number } }, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
   "baseCurrencyAmount": { "currencyCode": "string", "totalAmount": number },
   "rateWarnings": [
      { "object": "payment" | "tax", "currency": "string", "statedRate": number, "referenceRate": number, "source": "provider" | "override", "asOf": "string", "deviationPct": number }
   ],
   "payment": { "status": "recorded", "method": "string", "paymentDate": "string", "reference": "string", "paidAmount": { "amount": number, "currency": "string", "rate": number, "rateDate": "string" } }
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Response Elements

NameTypeDescription
matchStatusstring"matched" or "discrepancy" after running the match, or "unverified" for an older pending invoice with no real usage figure.
discrepancyReasonstringSet only on a discrepancy: "amount" (out of tolerance) or "currency" (usage in another currency, never compared).

Errors

  • 400 Bad Request — The invoice's matchStatus isn't "pending". For an unverified invoice (no purchase order, or no billing connection): "Invoice '{invoiceId}' can't be matched: it's unverified, with no usage figure to compare against. Nothing was matched. A person must review it in Pleaxy before it can go for approval." Otherwise: "Invoice '{invoiceId}' has already been matched (result: {matchStatus})."
  • 404 Not Found — No invoice with this id exists in the workspace.
  • 409 Conflict — Another run or decision landed concurrently; re-read the invoice.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

ResolveInvoiceDiscrepancy

POSTBuyer

Manual override for an invoice a human has reviewed — starts a normal (non-autonomy) approval chain for a discrepancy or unverified invoice. A discrepancy becomes "matched"; an unverified invoice stays "unverified" with its pending chain, because nothing was checked. The approval chain's requester is the authenticated caller (an API key is recorded as apikey:<keyId>), never a name passed in; the request takes no body.

An API key can resolve an amount or currency discrepancy. Only a signed-in session can resolve an unverified invoice, for either unverifiedReason; an API key gets 403 HUMAN_REVIEW_REQUIRED.

Request Syntax

POST /v1/workspaces/{regionId}/invoices/{invoiceId}/resolve-discrepancy

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
invoiceIdRequiredstringThe invoice identifier.

Response Syntax 201 Created

{
   "id": "string",
   "po": "string",
   "buyerOrgId": "string",
   "supplierOrgId": "string",
   "supplier": "string",
   "amount": number,
   "currency": "string",
   "usageAmount": number,
   "usageCurrency": "string",
   "tolerancePct": number,
   "matchStatus": "pending" | "matched" | "discrepancy" | "unverified",
   "unverifiedReason": "no_purchase_order" | "no_billing_connection",
   "discrepancyReason": "amount" | "currency",
   "paid": boolean,
   "dueInDays": number,
   "approval": {
      "requester": "string",
      "requesterEmployeeKey": "string",
      "groups": [
        {
          "id": "string",
          "mode": "sequential" | "parallel",
          "label": "string",
          "status": "pending" | "approved" | "rejected",
          "stages": [
            { "id": "string", "label": "string", "approverEmployeeKey": "string", "approverName": "string", "status": "pending" | "approved" | "rejected" }
          ]
        }
      ],
      "groupIndex": number,
      "status": "pending" | "approved" | "rejected",
      "history": [
        { "stage": "string", "by": "string", "at": "string", "action": "approved" | "rejected" | "limit_exceeded" | "reassigned" | "skipped" }
      ],
      "autonomy": boolean,
      "selfApproved": boolean
    },
   "createdAt": "string",
   "lastRemindedAt": "string",
   "rejection": { "reasonCode": "amount_incorrect" | "not_ordered_or_not_received" | "duplicate" | "missing_or_wrong_po" | "wrong_billing_entity" | "other", "reason": "string", "at": "string" },
   "replacesInvoiceId": "string",
   "source": "po-flip" | "direct" | "manual" | "feed",
   "submittedFormat": "json" | "cxml" | "ubl",
   "submittedBy": "string",
   "externalInvoiceNumber": "string",
   "lineItems": [
      { "description": "string", "qty": number, "unitPrice": number, "discount": number, "charge": number, "amount": number }
   ],
   "adjustments": [
      { "type": "discount" | "charge", "description": "string", "amount": number }
   ],
   "taxAmount": number,
   "taxBreakdown": [ { "description": "string", "rate": number, "amount": number } ],
   "paymentCurrencyAmount": { "currencyCode": "string", "totalAmount": number, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
   "taxCurrencyAmount": { "currencyCode": "string", "amountBreakdown": { "taxes": { "totalAmount": number } }, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
   "baseCurrencyAmount": { "currencyCode": "string", "totalAmount": number },
   "rateWarnings": [
      { "object": "payment" | "tax", "currency": "string", "statedRate": number, "referenceRate": number, "source": "provider" | "override", "asOf": "string", "deviationPct": number }
   ],
   "payment": { "status": "recorded", "method": "string", "paymentDate": "string", "reference": "string", "paidAmount": { "amount": number, "currency": "string", "rate": number, "rateDate": "string" } }
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 400 Bad Request — The invoice's matchStatus is neither "discrepancy" nor "unverified".
  • 403 Forbidden — HUMAN_REVIEW_REQUIRED: the invoice is unverified and the caller is an API key ("An unverified invoice has nothing to match it against, so a person must review it. Sign in to Pleaxy to resolve it."). Nothing is changed.
  • 404 Not Found — No invoice with this id exists in the workspace.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

ApproveInvoice

POSTBuyer

Advances the invoice's approval chain by one stage. Only the employee the current step names may call this — no role, permission or administrator rights substitute for being named, and an API key can never be named. A completed approval sends an invoice_approved notification to the supplier. It takes no body and acts as the authenticated caller (an API key acts as apikey:<keyId>).

Signed-in session only. Any other caller, including an API key, gets 403 HUMAN_APPROVAL_REQUIRED before anything is read.

Request Syntax

POST /v1/workspaces/{regionId}/invoices/{invoiceId}/approve

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
invoiceIdRequiredstringThe invoice identifier.

Response Syntax 201 Created

{
   "id": "string",
   "po": "string",
   "buyerOrgId": "string",
   "supplierOrgId": "string",
   "supplier": "string",
   "amount": number,
   "currency": "string",
   "usageAmount": number,
   "usageCurrency": "string",
   "tolerancePct": number,
   "matchStatus": "pending" | "matched" | "discrepancy" | "unverified",
   "unverifiedReason": "no_purchase_order" | "no_billing_connection",
   "discrepancyReason": "amount" | "currency",
   "paid": boolean,
   "dueInDays": number,
   "approval": {
      "requester": "string",
      "requesterEmployeeKey": "string",
      "groups": [
        {
          "id": "string",
          "mode": "sequential" | "parallel",
          "label": "string",
          "status": "pending" | "approved" | "rejected",
          "stages": [
            { "id": "string", "label": "string", "approverEmployeeKey": "string", "approverName": "string", "status": "pending" | "approved" | "rejected" }
          ]
        }
      ],
      "groupIndex": number,
      "status": "pending" | "approved" | "rejected",
      "history": [
        { "stage": "string", "by": "string", "at": "string", "action": "approved" | "rejected" | "limit_exceeded" | "reassigned" | "skipped" }
      ],
      "autonomy": boolean,
      "selfApproved": boolean
    },
   "createdAt": "string",
   "lastRemindedAt": "string",
   "rejection": { "reasonCode": "amount_incorrect" | "not_ordered_or_not_received" | "duplicate" | "missing_or_wrong_po" | "wrong_billing_entity" | "other", "reason": "string", "at": "string" },
   "replacesInvoiceId": "string",
   "source": "po-flip" | "direct" | "manual" | "feed",
   "submittedFormat": "json" | "cxml" | "ubl",
   "submittedBy": "string",
   "externalInvoiceNumber": "string",
   "lineItems": [
      { "description": "string", "qty": number, "unitPrice": number, "discount": number, "charge": number, "amount": number }
   ],
   "adjustments": [
      { "type": "discount" | "charge", "description": "string", "amount": number }
   ],
   "taxAmount": number,
   "taxBreakdown": [ { "description": "string", "rate": number, "amount": number } ],
   "paymentCurrencyAmount": { "currencyCode": "string", "totalAmount": number, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
   "taxCurrencyAmount": { "currencyCode": "string", "amountBreakdown": { "taxes": { "totalAmount": number } }, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
   "baseCurrencyAmount": { "currencyCode": "string", "totalAmount": number },
   "rateWarnings": [
      { "object": "payment" | "tax", "currency": "string", "statedRate": number, "referenceRate": number, "source": "provider" | "override", "asOf": "string", "deviationPct": number }
   ],
   "payment": { "status": "recorded", "method": "string", "paymentDate": "string", "reference": "string", "paidAmount": { "amount": number, "currency": "string", "rate": number, "rateDate": "string" } }
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 400 Bad Request — The invoice has no pending approval stage.
  • 403 Forbidden — Code HUMAN_APPROVAL_REQUIRED. The caller is an API key. Approval steps name a specific employee, and a key can never be one, so a key can neither approve nor reject a step. Ask the employee the step names to act on it in Pleaxy.
  • 403 Forbidden — Code APPROVER_NOT_ASSIGNED. The current step has no approver assigned, so nobody can act on it. An administrator must assign one with ReassignPurchaseOrderApprovalStage / ReassignInvoiceApprovalStage first.
  • 403 Forbidden — The caller is not the employee the current step names. Only that employee may approve or reject it — no role or permission substitutes for being named.
  • 404 Not Found — No invoice with this id exists in the workspace.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

RejectInvoice

POSTBuyer

Rejects the invoice at its current approval stage, with a reason the supplier sees. Only the employee the current step names may call this. An unverified or discrepancy invoice that has no approval chain yet is a separate case: any buyer with invoices:edit may reject it, except the buyer who entered it. Signed-in sessions only: an API key can't reject. The supplier sees the reason and the note word for word, but not who rejected it, and gets a notification naming the reason only. The rejection is stored on the invoice as `rejection`. Rejecting an invoice that holds its purchase order's claim reopens that purchase order in the same write, so the supplier can send a corrected invoice against it: it gets the id INV-<PO suffix>-R<n> and `replacesInvoiceId`, and needs a new invoice number, because the rejected invoice keeps its own.

Request Syntax

POST /v1/workspaces/{regionId}/invoices/{invoiceId}/reject
{
   "reasonCode": "amount_incorrect" | "not_ordered_or_not_received" | "duplicate" | "missing_or_wrong_po" | "wrong_billing_entity" | "other",
   "reason": "string"
}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
invoiceIdRequiredstringThe invoice identifier.

Request Body

NameTypeDescription
reasonCodeRequiredstringOne of amount_incorrect, not_ordered_or_not_received, duplicate, missing_or_wrong_po, wrong_billing_entity, other. The supplier sees its label.
reasonRequiredstringThe note the supplier sees: what they should fix. Line breaks become spaces, and control and invisible characters are removed. It then needs at least 10 letters or numbers and at most 500 characters. Plain text; never shown as a link. Don't include personal details or internal comments.

Response Syntax 201 Created

{
   "id": "string",
   "po": "string",
   "buyerOrgId": "string",
   "supplierOrgId": "string",
   "supplier": "string",
   "amount": number,
   "currency": "string",
   "usageAmount": number,
   "usageCurrency": "string",
   "tolerancePct": number,
   "matchStatus": "pending" | "matched" | "discrepancy" | "unverified",
   "unverifiedReason": "no_purchase_order" | "no_billing_connection",
   "discrepancyReason": "amount" | "currency",
   "paid": boolean,
   "dueInDays": number,
   "approval": {
      "requester": "string",
      "requesterEmployeeKey": "string",
      "groups": [
        {
          "id": "string",
          "mode": "sequential" | "parallel",
          "label": "string",
          "status": "pending" | "approved" | "rejected",
          "stages": [
            { "id": "string", "label": "string", "approverEmployeeKey": "string", "approverName": "string", "status": "pending" | "approved" | "rejected" }
          ]
        }
      ],
      "groupIndex": number,
      "status": "pending" | "approved" | "rejected",
      "history": [
        { "stage": "string", "by": "string", "at": "string", "action": "approved" | "rejected" | "limit_exceeded" | "reassigned" | "skipped" }
      ],
      "autonomy": boolean,
      "selfApproved": boolean
    },
   "createdAt": "string",
   "lastRemindedAt": "string",
   "rejection": { "reasonCode": "amount_incorrect" | "not_ordered_or_not_received" | "duplicate" | "missing_or_wrong_po" | "wrong_billing_entity" | "other", "reason": "string", "at": "string" },
   "replacesInvoiceId": "string",
   "source": "po-flip" | "direct" | "manual" | "feed",
   "submittedFormat": "json" | "cxml" | "ubl",
   "submittedBy": "string",
   "externalInvoiceNumber": "string",
   "lineItems": [
      { "description": "string", "qty": number, "unitPrice": number, "discount": number, "charge": number, "amount": number }
   ],
   "adjustments": [
      { "type": "discount" | "charge", "description": "string", "amount": number }
   ],
   "taxAmount": number,
   "taxBreakdown": [ { "description": "string", "rate": number, "amount": number } ],
   "paymentCurrencyAmount": { "currencyCode": "string", "totalAmount": number, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
   "taxCurrencyAmount": { "currencyCode": "string", "amountBreakdown": { "taxes": { "totalAmount": number } }, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
   "baseCurrencyAmount": { "currencyCode": "string", "totalAmount": number },
   "rateWarnings": [
      { "object": "payment" | "tax", "currency": "string", "statedRate": number, "referenceRate": number, "source": "provider" | "override", "asOf": "string", "deviationPct": number }
   ],
   "payment": { "status": "recorded", "method": "string", "paymentDate": "string", "reference": "string", "paidAmount": { "amount": number, "currency": "string", "rate": number, "rateDate": "string" } }
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 400 Bad Request — The invoice has no pending approval stage.
  • 400 Bad Request — Code INVOICE_REJECTION_REASON_INVALID. reasonCode is missing or not one of the six codes, or reason is missing, not a string, has fewer than 10 letters or numbers, or is longer than 500 characters once cleaned.
  • 403 Forbidden — Code HUMAN_APPROVAL_REQUIRED: "Rejecting needs you, signed in to Pleaxy." The caller is an API key. Rejecting is a decision the supplier sees, so it needs a signed-in person.
  • 403 Forbidden — Code APPROVER_NOT_ASSIGNED. The current step has no approver assigned, so nobody can act on it. An administrator must assign one with ReassignPurchaseOrderApprovalStage / ReassignInvoiceApprovalStage first.
  • 403 Forbidden — The caller is not the employee the current step names. Only that employee may approve or reject it — no role or permission substitutes for being named.
  • 404 Not Found — No invoice with this id exists in the workspace.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

ReassignInvoiceApprovalStage

POSTBuyer

The invoice equivalent of ReassignPurchaseOrderApprovalStage: re-points one pending step of this invoice's approval chain at a different named employee. Administrator only (approvalSettings:edit), signed-in sessions only, document-scoped, and recorded in the approval history. This is how a supplier-linked invoice that failed closed onto an unassigned "Manual approval" step is unblocked.

Request Syntax

POST /v1/workspaces/{regionId}/invoices/{invoiceId}/approval/reassign
{
   "stageId": "string",
   "approverEmployeeKey": "string",
   "reason": "string"
}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
invoiceIdRequiredstringThe invoice identifier.

Request Body

NameTypeDescription
stageIdRequiredstringId of the pending step to reassign. It may sit in any group of the chain, not only the current one.
approverEmployeeKeyRequiredstringEmployee key (Cognito sub) of the new approver. Must be a buyer member of this workspace, and not the acting administrator.
reasonstringWhy the step was reassigned, up to 280 characters. Shown in the approval history.

Response Syntax 201 Created

{
   "id": "string",
   "po": "string",
   "buyerOrgId": "string",
   "supplierOrgId": "string",
   "supplier": "string",
   "amount": number,
   "currency": "string",
   "usageAmount": number,
   "usageCurrency": "string",
   "tolerancePct": number,
   "matchStatus": "pending" | "matched" | "discrepancy" | "unverified",
   "unverifiedReason": "no_purchase_order" | "no_billing_connection",
   "discrepancyReason": "amount" | "currency",
   "paid": boolean,
   "dueInDays": number,
   "approval": {
      "requester": "string",
      "requesterEmployeeKey": "string",
      "groups": [
        {
          "id": "string",
          "mode": "sequential" | "parallel",
          "label": "string",
          "status": "pending" | "approved" | "rejected",
          "stages": [
            { "id": "string", "label": "string", "approverEmployeeKey": "string", "approverName": "string", "status": "pending" | "approved" | "rejected" }
          ]
        }
      ],
      "groupIndex": number,
      "status": "pending" | "approved" | "rejected",
      "history": [
        { "stage": "string", "by": "string", "at": "string", "action": "approved" | "rejected" | "limit_exceeded" | "reassigned" | "skipped" }
      ],
      "autonomy": boolean,
      "selfApproved": boolean
    },
   "createdAt": "string",
   "lastRemindedAt": "string",
   "rejection": { "reasonCode": "amount_incorrect" | "not_ordered_or_not_received" | "duplicate" | "missing_or_wrong_po" | "wrong_billing_entity" | "other", "reason": "string", "at": "string" },
   "replacesInvoiceId": "string",
   "source": "po-flip" | "direct" | "manual" | "feed",
   "submittedFormat": "json" | "cxml" | "ubl",
   "submittedBy": "string",
   "externalInvoiceNumber": "string",
   "lineItems": [
      { "description": "string", "qty": number, "unitPrice": number, "discount": number, "charge": number, "amount": number }
   ],
   "adjustments": [
      { "type": "discount" | "charge", "description": "string", "amount": number }
   ],
   "taxAmount": number,
   "taxBreakdown": [ { "description": "string", "rate": number, "amount": number } ],
   "paymentCurrencyAmount": { "currencyCode": "string", "totalAmount": number, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
   "taxCurrencyAmount": { "currencyCode": "string", "amountBreakdown": { "taxes": { "totalAmount": number } }, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
   "baseCurrencyAmount": { "currencyCode": "string", "totalAmount": number },
   "rateWarnings": [
      { "object": "payment" | "tax", "currency": "string", "statedRate": number, "referenceRate": number, "source": "provider" | "override", "asOf": "string", "deviationPct": number }
   ],
   "payment": { "status": "recorded", "method": "string", "paymentDate": "string", "reference": "string", "paidAmount": { "amount": number, "currency": "string", "rate": number, "rateDate": "string" } }
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 400 Bad Request — The chain is already approved or rejected; the step has already been approved or rejected; the new approver isn't an employee of this workspace; the new approver raised the order / entered the invoice, is separation-of-duties excluded, already cleared the spend limit with their own authority, or already holds another seat on the same step; or the step is the spend-limit review, whose approver is a frozen audit fact (change it in Settings › Spend limits).
  • 403 Forbidden — The caller lacks approvalSettings:edit (administrator), or named themselves as the new approver — assigning a live step to yourself makes one person the whole chain with no trace of policy. Code HUMAN_APPROVAL_REQUIRED when the caller is an API key: only a signed-in administrator can reassign an approval step.
  • 404 Not Found — No document with this id exists in the workspace, or the chain has no step with this stageId.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

RecordPayment

POSTBuyer

Records that an approved invoice was paid outside Pleaxy, with the method, date and reference. This moves no money and doesn't check that the payment happened. A payment covers the full amount due: the invoice's paymentCurrencyAmount.totalAmount when it states one, else its amount, in that currency. A partial payment can't be recorded. Paid in another currency than the amount due's? Send amount, currency and rate too: the amount must be the amount due × rate, within 0.5% or one minor unit, and is recorded as paidAmount; Pleaxy doesn't convert anything. The invoice reads as paid, and the supplier is notified that the buyer recorded the payment and gets a remittance advice marked as recorded by the buyer and not made through Pleaxy. Works on an exported invoice too, which completes its export. Recording the same details again answers 200 with alreadyRecorded: true and changes nothing.

A signed-in session with 'edit' access to invoices. Changed 2026-09-24, a security fix inside v1: an API key can't record a payment (403 HUMAN_SESSION_REQUIRED). The person who entered the invoice, and members of the supplier's profile, can't record its payment.

Request Syntax

POST /v1/workspaces/{regionId}/invoices/{invoiceId}/payment-record
{
   "method": "ach" | "wire" | "check" | "card" | "other",
   "paymentDate": "YYYY-MM-DD",
   "reference": "string",
   "currency": "string",
   "amount": number,
   "rate": number,
   "rateDate": "YYYY-MM-DD"
}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
invoiceIdRequiredstringThe invoice identifier.

Request Body

NameTypeDescription
methodRequiredstringHow it was paid: ach, wire, check, card or other.
paymentDateRequiredstringYYYY-MM-DD the payment was sent. Not later than today in the buyer workspace's time zone, and not before the invoice date.
referenceRequiredstringThe bank or payment tool's reference, such as an ACH trace or check number, 1–64 characters once cleaned. The supplier sees it. Never a bank account number.
currencystringOnly for a payment in another currency than the amount due's: ISO 4217. Needs amount and rate.
amountnumberWith currency: what was paid, in currency. The amount due × rate, within 0.5% or one minor unit.
ratenumberWith currency: 1 {amount due's currency} = rate {currency}, the rate the payment was made at.
rateDatestringWith currency: YYYY-MM-DD the rate is for, not later than today. Defaults to paymentDate.

Response Syntax 201 Created

{
   "id": "string",
   "po": "string",
   "buyerOrgId": "string",
   "supplierOrgId": "string",
   "supplier": "string",
   "amount": number,
   "currency": "string",
   "usageAmount": number,
   "usageCurrency": "string",
   "tolerancePct": number,
   "matchStatus": "pending" | "matched" | "discrepancy" | "unverified",
   "unverifiedReason": "no_purchase_order" | "no_billing_connection",
   "discrepancyReason": "amount" | "currency",
   "paid": boolean,
   "dueInDays": number,
   "approval": {
      "requester": "string",
      "requesterEmployeeKey": "string",
      "groups": [
        {
          "id": "string",
          "mode": "sequential" | "parallel",
          "label": "string",
          "status": "pending" | "approved" | "rejected",
          "stages": [
            { "id": "string", "label": "string", "approverEmployeeKey": "string", "approverName": "string", "status": "pending" | "approved" | "rejected" }
          ]
        }
      ],
      "groupIndex": number,
      "status": "pending" | "approved" | "rejected",
      "history": [
        { "stage": "string", "by": "string", "at": "string", "action": "approved" | "rejected" | "limit_exceeded" | "reassigned" | "skipped" }
      ],
      "autonomy": boolean,
      "selfApproved": boolean
    },
   "createdAt": "string",
   "lastRemindedAt": "string",
   "rejection": { "reasonCode": "amount_incorrect" | "not_ordered_or_not_received" | "duplicate" | "missing_or_wrong_po" | "wrong_billing_entity" | "other", "reason": "string", "at": "string" },
   "replacesInvoiceId": "string",
   "source": "po-flip" | "direct" | "manual" | "feed",
   "submittedFormat": "json" | "cxml" | "ubl",
   "submittedBy": "string",
   "externalInvoiceNumber": "string",
   "lineItems": [
      { "description": "string", "qty": number, "unitPrice": number, "discount": number, "charge": number, "amount": number }
   ],
   "adjustments": [
      { "type": "discount" | "charge", "description": "string", "amount": number }
   ],
   "taxAmount": number,
   "taxBreakdown": [ { "description": "string", "rate": number, "amount": number } ],
   "paymentCurrencyAmount": { "currencyCode": "string", "totalAmount": number, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
   "taxCurrencyAmount": { "currencyCode": "string", "amountBreakdown": { "taxes": { "totalAmount": number } }, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
   "baseCurrencyAmount": { "currencyCode": "string", "totalAmount": number },
   "rateWarnings": [
      { "object": "payment" | "tax", "currency": "string", "statedRate": number, "referenceRate": number, "source": "provider" | "override", "asOf": "string", "deviationPct": number }
   ],
   "payment": { "status": "recorded", "method": "string", "paymentDate": "string", "reference": "string", "paidAmount": { "amount": number, "currency": "string", "rate": number, "rateDate": "string" } }
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 400 Bad Request — A field is missing or invalid (codes PAYMENT_METHOD_INVALID, PAYMENT_DATE_INVALID, PAYMENT_DATE_IN_FUTURE, PAYMENT_DATE_BEFORE_INVOICE, PAYMENT_REFERENCE_INVALID), the paid amount doesn't fit (PAID_AMOUNT_SAME_CURRENCY: currency is the amount due's own, so leave amount, currency and rate off; PAID_AMOUNT_INVALID: amount or rate missing or not above 0, or a bad rateDate; PAID_AMOUNT_RATE_MISMATCH: amount isn't the amount due × rate), or the body carries a field it doesn't accept.
  • 403 Forbidden — Code HUMAN_SESSION_REQUIRED: the caller is an API key. Or a separation-of-duties refusal: the caller entered the invoice or is a member of the supplier's profile.
  • 404 Not Found — No invoice with this id exists in the workspace.
  • 409 Conflict — Code PAYMENT_ALREADY_RECORDED (a different payment is already recorded: withdraw it first), STRIPE_PAYMENT_IN_PROGRESS (a payment through Pleaxy with Stripe was started), EXPORT_COOLING_OFF (its export was withdrawn; try again next business day) or PAYMENT_CHANGED.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

WithdrawPaymentRecord

DELETEBuyer

Withdraws the buyer's record of a payment. The invoice reads as unpaid again and the supplier is told that the record was withdrawn. This doesn't cancel or reverse the payment itself. The remittance advice is then marked Withdrawn.

A signed-in session with 'edit' access to invoices; an API key gets 403 HUMAN_SESSION_REQUIRED.

Request Syntax

DELETE /v1/workspaces/{regionId}/invoices/{invoiceId}/payment-record
{
   "confirm": true
}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
invoiceIdRequiredstringThe invoice identifier.

Request Body

NameTypeDescription
confirmRequiredbooleanMust be true.

Response Syntax 200 OK

{
   "id": "string",
   "po": "string",
   "buyerOrgId": "string",
   "supplierOrgId": "string",
   "supplier": "string",
   "amount": number,
   "currency": "string",
   "usageAmount": number,
   "usageCurrency": "string",
   "tolerancePct": number,
   "matchStatus": "pending" | "matched" | "discrepancy" | "unverified",
   "unverifiedReason": "no_purchase_order" | "no_billing_connection",
   "discrepancyReason": "amount" | "currency",
   "paid": boolean,
   "dueInDays": number,
   "approval": {
      "requester": "string",
      "requesterEmployeeKey": "string",
      "groups": [
        {
          "id": "string",
          "mode": "sequential" | "parallel",
          "label": "string",
          "status": "pending" | "approved" | "rejected",
          "stages": [
            { "id": "string", "label": "string", "approverEmployeeKey": "string", "approverName": "string", "status": "pending" | "approved" | "rejected" }
          ]
        }
      ],
      "groupIndex": number,
      "status": "pending" | "approved" | "rejected",
      "history": [
        { "stage": "string", "by": "string", "at": "string", "action": "approved" | "rejected" | "limit_exceeded" | "reassigned" | "skipped" }
      ],
      "autonomy": boolean,
      "selfApproved": boolean
    },
   "createdAt": "string",
   "lastRemindedAt": "string",
   "rejection": { "reasonCode": "amount_incorrect" | "not_ordered_or_not_received" | "duplicate" | "missing_or_wrong_po" | "wrong_billing_entity" | "other", "reason": "string", "at": "string" },
   "replacesInvoiceId": "string",
   "source": "po-flip" | "direct" | "manual" | "feed",
   "submittedFormat": "json" | "cxml" | "ubl",
   "submittedBy": "string",
   "externalInvoiceNumber": "string",
   "lineItems": [
      { "description": "string", "qty": number, "unitPrice": number, "discount": number, "charge": number, "amount": number }
   ],
   "adjustments": [
      { "type": "discount" | "charge", "description": "string", "amount": number }
   ],
   "taxAmount": number,
   "taxBreakdown": [ { "description": "string", "rate": number, "amount": number } ],
   "paymentCurrencyAmount": { "currencyCode": "string", "totalAmount": number, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
   "taxCurrencyAmount": { "currencyCode": "string", "amountBreakdown": { "taxes": { "totalAmount": number } }, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
   "baseCurrencyAmount": { "currencyCode": "string", "totalAmount": number },
   "rateWarnings": [
      { "object": "payment" | "tax", "currency": "string", "statedRate": number, "referenceRate": number, "source": "provider" | "override", "asOf": "string", "deviationPct": number }
   ],
   "payment": { "status": "recorded", "method": "string", "paymentDate": "string", "reference": "string", "paidAmount": { "amount": number, "currency": "string", "rate": number, "rateDate": "string" } }
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 403 Forbidden — Code HUMAN_SESSION_REQUIRED, or a separation-of-duties refusal.
  • 404 Not Found — No invoice with this id exists, or code NO_PAYMENT_RECORD: no payment is recorded for it.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

ExportInvoicesForPayment

POSTBuyer

Downloads approved, unpaid invoices as CSV for your own payment run, due date first, up to 1,000. Each exported invoice is locked until its payment is recorded or the export is withdrawn, so it can't be exported or recorded twice. The file has no bank details. The supplier doesn't see the export. Response headers: X-Pleaxy-Export-Id, X-Pleaxy-Export-Count, X-Pleaxy-Export-Truncated.

A signed-in session with 'edit' access to invoices; an API key gets 403 HUMAN_SESSION_REQUIRED.

Request Syntax

POST /v1/workspaces/{regionId}/invoices/payment-export
{}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.

Response Syntax 200 OK

text/csv: one row per invoice, with a status column of approved_unpaid

Errors

  • 403 Forbidden — Code HUMAN_SESSION_REQUIRED.
  • 422 Unprocessable Entity — Code NOTHING_TO_EXPORT: no approved, unpaid invoice is eligible.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

GetPaymentExport

GETBuyer

Downloads an export again: the same CSV, rebuilt from the invoices still exported under that export id. For when a download failed after the invoices were locked.

Request Syntax

GET /v1/workspaces/{regionId}/invoices/payment-exports/{exportId}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
exportIdRequiredstringX-Pleaxy-Export-Id from ExportInvoicesForPayment.

Response Syntax 200 OK

text/csv

Errors

  • 404 Not Found — Code EXPORT_NOT_FOUND: no invoice is still exported under this id.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

WithdrawPaymentExport

DELETEBuyer

Takes an invoice back out of its export, after you confirm your payment run hasn't paid it. Pleaxy can't see or cancel anything in your own payment tool. The invoice goes back to Approved but can't be exported or recorded again until the next business day, and the next export marks it previously_exported_at.

A signed-in session with 'edit' access to invoices; an API key gets 403 HUMAN_SESSION_REQUIRED.

Request Syntax

DELETE /v1/workspaces/{regionId}/invoices/{invoiceId}/payment-export
{
   "confirmNotPaid": true
}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
invoiceIdRequiredstringThe invoice identifier.

Request Body

NameTypeDescription
confirmNotPaidRequiredbooleanMust be true: your payment run hasn't paid this invoice.

Response Syntax 200 OK

{
   "id": "string",
   "po": "string",
   "buyerOrgId": "string",
   "supplierOrgId": "string",
   "supplier": "string",
   "amount": number,
   "currency": "string",
   "usageAmount": number,
   "usageCurrency": "string",
   "tolerancePct": number,
   "matchStatus": "pending" | "matched" | "discrepancy" | "unverified",
   "unverifiedReason": "no_purchase_order" | "no_billing_connection",
   "discrepancyReason": "amount" | "currency",
   "paid": boolean,
   "dueInDays": number,
   "approval": {
      "requester": "string",
      "requesterEmployeeKey": "string",
      "groups": [
        {
          "id": "string",
          "mode": "sequential" | "parallel",
          "label": "string",
          "status": "pending" | "approved" | "rejected",
          "stages": [
            { "id": "string", "label": "string", "approverEmployeeKey": "string", "approverName": "string", "status": "pending" | "approved" | "rejected" }
          ]
        }
      ],
      "groupIndex": number,
      "status": "pending" | "approved" | "rejected",
      "history": [
        { "stage": "string", "by": "string", "at": "string", "action": "approved" | "rejected" | "limit_exceeded" | "reassigned" | "skipped" }
      ],
      "autonomy": boolean,
      "selfApproved": boolean
    },
   "createdAt": "string",
   "lastRemindedAt": "string",
   "rejection": { "reasonCode": "amount_incorrect" | "not_ordered_or_not_received" | "duplicate" | "missing_or_wrong_po" | "wrong_billing_entity" | "other", "reason": "string", "at": "string" },
   "replacesInvoiceId": "string",
   "source": "po-flip" | "direct" | "manual" | "feed",
   "submittedFormat": "json" | "cxml" | "ubl",
   "submittedBy": "string",
   "externalInvoiceNumber": "string",
   "lineItems": [
      { "description": "string", "qty": number, "unitPrice": number, "discount": number, "charge": number, "amount": number }
   ],
   "adjustments": [
      { "type": "discount" | "charge", "description": "string", "amount": number }
   ],
   "taxAmount": number,
   "taxBreakdown": [ { "description": "string", "rate": number, "amount": number } ],
   "paymentCurrencyAmount": { "currencyCode": "string", "totalAmount": number, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
   "taxCurrencyAmount": { "currencyCode": "string", "amountBreakdown": { "taxes": { "totalAmount": number } }, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
   "baseCurrencyAmount": { "currencyCode": "string", "totalAmount": number },
   "rateWarnings": [
      { "object": "payment" | "tax", "currency": "string", "statedRate": number, "referenceRate": number, "source": "provider" | "override", "asOf": "string", "deviationPct": number }
   ],
   "payment": { "status": "recorded", "method": "string", "paymentDate": "string", "reference": "string", "paidAmount": { "amount": number, "currency": "string", "rate": number, "rateDate": "string" } }
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 403 Forbidden — Code HUMAN_SESSION_REQUIRED, or a separation-of-duties refusal.
  • 404 Not Found — No invoice with this id exists in the workspace.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

GetRemittanceAdvice

GETBoth

The remittance advice for a recorded payment, as a PDF. It is marked as recorded by the buyer and not made through Pleaxy, and is not a receipt or proof of payment. Marked Revised after a re-record and Withdrawn after a withdrawal. Send Accept: application/pdf. A supplier whose buyers share an invoice id adds ?buyer=<buyer Workspace ID>.

Request Syntax

GET /v1/workspaces/{regionId}/invoices/{invoiceId}/remittance-advice

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
invoiceIdRequiredstringThe invoice identifier.

Response Syntax 200 OK

application/pdf

Errors

  • 404 Not Found — No invoice with this id, or code NO_PAYMENT_RECORD: no payment was ever recorded for it.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

GetStripePayment

GETBuyer

Not available yet: paying through Pleaxy isn't switched on, so this answers available: false with reason not_configured. Once it is, it says whether this invoice can be paid through Pleaxy with Stripe, or why not, and any Stripe payment under way. quote is what the buyer would pay: the invoice plus Stripe's bank-transfer fee (0.8%, capped at $5), which the buyer covers so the supplier receives the full invoice amount.

Request Syntax

GET /v1/workspaces/{regionId}/invoices/{invoiceId}/stripe-payment

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
invoiceIdRequiredstringThe invoice identifier.

Response Syntax 200 OK

{
   "available": boolean,
   "reason": "not_configured" | "not_approved" | "payment_locked" | "supplier_not_on_pleaxy" | "supplier_not_connected" | "unsupported_currency" | "amount_too_small",
   "state": "checkout_open" | "processing" | "succeeded",
   "paymentMethodType": "us_bank_account",
   "quote": { "amount": number, "fee": number, "total": number, "currency": "USD" }
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 404 Not Found — No invoice with this id exists in the workspace.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

StartStripePayment

POSTBuyer

Not available yet: paying through Pleaxy isn't switched on, so this answers 503. Once it is, it starts paying an approved invoice in full through Pleaxy, by US bank transfer with Stripe, and returns the url of a Stripe Checkout page. The payment is made on the supplier's own Stripe account: the money goes from the buyer's bank account to the supplier and never through Pleaxy. The buyer pays the invoice plus Stripe's fee, so the supplier receives the full invoice amount; Pleaxy charges nothing. The invoice is locked while Checkout is open (up to an hour) and marked paid when Stripe confirms the transfer. Resumes an open Checkout; answers { state } with no url when a payment is already processing or done.

A signed-in session with 'edit' access to invoices; an API key gets 403 HUMAN_SESSION_REQUIRED. The same separation-of-duties rules as RecordPayment.

Request Syntax

POST /v1/workspaces/{regionId}/invoices/{invoiceId}/stripe-payment

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
invoiceIdRequiredstringThe invoice identifier.

Response Syntax 201 Created

{
   "url": "string",
   "state": "checkout_open" | "processing" | "succeeded"
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 403 Forbidden — Code HUMAN_SESSION_REQUIRED, or a separation-of-duties refusal.
  • 409 Conflict — Code PAYMENT_ALREADY_RECORDED, PAYMENT_ACTIVE (exported or recorded: withdraw that first) or PAYMENT_CHANGED.
  • 422 Unprocessable Entity — Code STRIPE_SUPPLIER_NOT_ON_PLEAXY, STRIPE_SUPPLIER_NOT_CONNECTED, STRIPE_UNSUPPORTED_CURRENCY or STRIPE_AMOUNT_TOO_SMALL.
  • 503 Service Unavailable — Paying through Pleaxy isn't switched on yet ("Paying suppliers through Pleaxy isn't available yet.").
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

SettleInvoice

POSTBuyer

Retired. Always answers 410 SETTLE_REPLACED and changes nothing. Record a payment made outside Pleaxy with RecordPayment.

Changed 2026-09-24, a security fix inside v1: settling was narrowed to signed-in sessions and then retired; it never pays anything.

Request Syntax

POST /v1/workspaces/{regionId}/invoices/{invoiceId}/settle

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
invoiceIdRequiredstringThe invoice identifier.

Response Syntax 410 Gone

{
   "statusCode": 410,
   "code": "SETTLE_REPLACED",
   "message": "string"
}

Errors

  • 410 Gone — Code SETTLE_REPLACED, always.

Cloud connections

A cloud connection gives Pleaxy read-only access to one cloud provider account's billing, through access you create in your own account with a setup script. AWS uses a cross-account IAM role, Google Cloud Workload Identity Federation and Azure a federated credential, each for short-lived sessions with no long-lived key stored. Oracle Cloud uses an API signing key Pleaxy generates for the connection; you upload only its public key. Connecting is two calls: init returns the values the setup script needs, then CreateCloudConnection verifies the access and completes the connection. Only the person who started a setup can complete or cancel it.

/v1/workspaces/{regionId}/cloud-connections

ListCloudConnections

GETBuyer

Lists the workspace's cloud connections, including setups still in progress (status pending). Never returns secrets or the identifiers the setup used (role ARN, external ID, service account, OCIDs, tenant or app IDs).

Request Syntax

GET /v1/workspaces/{regionId}/cloud-connections

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.

Response Syntax 200 OK

[
   {
      "id": "string",
      "provider": "aws" | "gcp" | "azure" | "oci",
      "accountLabel": "string",
      "detail": "string",
      "spend": number,
      "status": "connected" | "pending" | "error",
      "connectedAt": "string",
      "awsAccountId": "string",
      "dataSources": [ "invoices" | "current_cost" ],
      "gcp": { "billingAccountId": "string", "providerId": "string" },
      "oci": { "homeRegion": "string", "keyFingerprint": "string" },
      "azure": { "scopeDisplayName": "string", "agreementType": "string" },
      "lastSyncedAt": "string",
      "lastSyncStatus": "ok" | "error" | "throttled" | "never",
      "lastSyncError": "string",
      "lastSyncErrorCode": "throttled" | "access_revoked" | "platform_config" | "data_not_enabled" | "unknown"
   }
]

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Response Elements

NameTypeDescription
dataSourcesArray<"invoices" | "current_cost">What Pleaxy may read: finalized invoices and/or month-to-date cost. Absent means both.
lastSyncStatus"ok" | "error" | "throttled" | "never"How the last billing sync went. lastSyncErrorCode says why one failed: throttled, access_revoked, platform_config, data_not_enabled or unknown.

Errors

  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

InitAwsCloudConnection

POSTBuyer

Starts connecting an AWS account and returns the values to give the setup script (CloudFormation, CDK or CLI), which creates a read-only IAM role in your account. Calling it again returns your own unfinished setup, with the same external ID. Takes no body.

Request Syntax

POST /v1/workspaces/{regionId}/cloud-connections/aws/init

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.

Response Syntax 201 Created

{
   "connectionId": "string",
   "platformAwsAccountId": "string",
   "externalId": "string",
   "cloudFormationTemplateUrl": "string"
}

Response Elements

NameTypeDescription
connectionIdstringPass it to CreateCloudConnection as awsConnectionId.
cloudFormationTemplateUrlstringAbsent when this deployment publishes no CloudFormation template; use the CDK or CLI script then.

Errors

  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

InitCloudConnection

POSTBuyer

The same as InitAwsCloudConnection for Google Cloud, Azure or Oracle Cloud: returns the per-connection values that provider's setup script pins, including a connectionId to pass to CreateCloudConnection (as gcpConnectionId, azureConnectionId or ociConnectionId). Calling it again returns your own unfinished setup. Takes no body.

Request Syntax

POST /v1/workspaces/{regionId}/cloud-connections/{provider}/init

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
providerRequired"gcp" | "azure" | "oci"The provider to connect.

Response Syntax 201 Created

{
   "connectionId": "string",
   ...the provider's setup values
}

Response Elements

NameTypeDescription
gcpobjectconnectionId, platformAwsAccountId, federationRoleArn, expectedSubject, poolId and providerId: the Workload Identity Federation values the setup script pins.
azureobjectconnectionId, issuer, subject, audience, appDisplayName and credentialName: the federated credential the setup script creates on your app.
ociobjectconnectionId, publicKeyPem, fingerprint and regions: the public key to upload to the pleaxy-billing user, and the home regions to choose from.

Errors

  • 404 Not Found — This provider's live connector isn't switched on in this deployment ("Real connections to this provider aren't switched on.").
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

DiscoverAzureBillingScopes

POSTBuyer

Azure only, between init and CreateCloudConnection: proves the tenant ID and app (client) ID the setup script printed, and reads the Microsoft Customer Agreement billing account and billing profile you name, so you can pick the profile to connect. Only the person who started the setup may call it, and it counts against the same attempt limit and cooldown as CreateCloudConnection.

Request Syntax

POST /v1/workspaces/{regionId}/cloud-connections/azure/discover

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.

Request Body

NameTypeDescription
azureConnectionIdRequiredstringconnectionId from InitCloudConnection.
azureTenantIdRequiredstringThe Microsoft Entra tenant the app was registered in (GUID).
azureClientIdRequiredstringThe app registration's Application (client) ID (GUID).
azureBillingAccountIdRequiredstringThe Microsoft Customer Agreement billing account ID (<guid>:<guid>_YYYY-MM-DD).
azureBillingProfileIdRequiredstringThe billing profile ID the app has Billing profile reader on.

Response Syntax 201 Created

{
   "scopes": [
      { "billingAccountId": "string", "billingProfileId": "string", "displayName": "string", "billingAccountDisplayName": "string", "agreementType": "string" }
   ]
}

Errors

  • 404 Not Found — Azure's live connector isn't switched on, or the setup is gone or was started by someone else.
  • 429 Too Many Requests — Tried again too soon; retryAfterSeconds says when.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

CreateCloudConnection

POSTBuyer

Completes a setup started with init: Pleaxy checks it can read your billing with the access the setup script created, then marks the connection connected and adds the provider to your suppliers. Send the fields for your provider. Only the person who started the setup may complete it. Gated by the region's plan: the Free plan allows one connected provider.

Request Syntax

POST /v1/workspaces/{regionId}/cloud-connections
{
   "provider": "aws",
   "accountLabel": "string",
   "awsConnectionId": "string",
   "roleArn": "string",
   "dataSources": [ "invoices", "current_cost" ]
}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.

Request Body

NameTypeDescription
providerRequired"aws" | "gcp" | "azure" | "oci"The cloud provider to connect.
accountLabelRequiredstringYour label for this account, e.g. "Production payer".
dataSourcesArray<"invoices" | "current_cost">What Pleaxy may read: finalized invoices and/or month-to-date cost. Omitted: both. Pleaxy never calls the provider for a source you leave out, including on a manual sync. Some providers charge per request for current cost (AWS Cost Explorer).
supplierContactEmailstringOptional contact at the provider for the supplier entry this creates.
awsConnectionIdstringAWS, required: connectionId from InitAwsCloudConnection.
roleArnstringAWS, required: the IAM role the setup script created, arn:aws:iam::<12-digit account>:role/<name>.
gcpConnectionIdstringGoogle Cloud: connectionId from InitCloudConnection.
gcpProjectNumberstringGoogle Cloud: the billing export project's number (digits).
gcpServiceAccountEmailstringGoogle Cloud: the service account the setup script created in that project.
gcpExportTablestringGoogle Cloud: the standard usage cost export table, project.dataset.gcp_billing_export_v1_XXXXXX_XXXXXX_XXXXXX.
azureConnectionIdstringAzure: connectionId from InitCloudConnection.
azureBillingAccountIdstringAzure: the billing account of the profile picked with DiscoverAzureBillingScopes.
azureBillingProfileIdstringAzure: the billing profile picked with DiscoverAzureBillingScopes.
ociConnectionIdstringOracle Cloud: connectionId from InitCloudConnection.
ociTenancyOcidstringOracle Cloud: the tenancy OCID (ocid1.tenancy.oc1..…).
ociUserOcidstringOracle Cloud: the OCID of the pleaxy-billing user the setup script created.
ociHomeRegionstringOracle Cloud: the tenancy's home region, one of those InitCloudConnection lists.
detailstringOnly for a demo connection, where a provider's live connector is switched off: an account identifier you type. A live connection reads it from the provider instead.

Response Syntax 201 Created

{
   "id": "string",
   "provider": "aws" | "gcp" | "azure" | "oci",
   "accountLabel": "string",
   "detail": "string",
   "spend": number,
   "status": "connected" | "pending" | "error",
   "connectedAt": "string",
   "awsAccountId": "string",
   "dataSources": [ "invoices" | "current_cost" ],
   "gcp": { "billingAccountId": "string", "providerId": "string" },
   "oci": { "homeRegion": "string", "keyFingerprint": "string" },
   "azure": { "scopeDisplayName": "string", "agreementType": "string" },
   "lastSyncedAt": "string",
   "lastSyncStatus": "ok" | "error" | "throttled" | "never",
   "lastSyncError": "string",
   "lastSyncErrorCode": "throttled" | "access_revoked" | "platform_config" | "data_not_enabled" | "unknown"
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 400 Bad Request — Validation failed; Pleaxy couldn't read billing with the access given (the code says which provider, e.g. CLOUD_VERIFY_FAILED); too many failed attempts for this setup, so start again with init (code ending ATTEMPTS_EXHAUSTED); or the region's plan has reached its connected-provider limit (Free: 1).
  • 404 Not Found — The setup is gone, expired, or was started by someone else.
  • 409 Conflict — This setup, or the same billing account or scope, is already connected.
  • 429 Too Many Requests — Tried again too soon after a failed attempt; retryAfterSeconds says when.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

SyncCloudConnection

POSTBuyer

Syncs one connected account's billing data now, reading only the connection's dataSources. Once every 15 minutes per connection. Takes no body.

Request Syntax

POST /v1/workspaces/{regionId}/cloud-connections/{id}/sync

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
idRequiredstringThe cloud connection id.

Response Syntax 201 Created

{
   "id": "string",
   "provider": "aws" | "gcp" | "azure" | "oci",
   "accountLabel": "string",
   "detail": "string",
   "spend": number,
   "status": "connected" | "pending" | "error",
   "connectedAt": "string",
   "awsAccountId": "string",
   "dataSources": [ "invoices" | "current_cost" ],
   "gcp": { "billingAccountId": "string", "providerId": "string" },
   "oci": { "homeRegion": "string", "keyFingerprint": "string" },
   "azure": { "scopeDisplayName": "string", "agreementType": "string" },
   "lastSyncedAt": "string",
   "lastSyncStatus": "ok" | "error" | "throttled" | "never",
   "lastSyncError": "string",
   "lastSyncErrorCode": "throttled" | "access_revoked" | "platform_config" | "data_not_enabled" | "unknown"
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 400 Bad Request — Synced less than 15 minutes ago ("…try again in {n}m."), or the connection isn't a connected account with a live billing connector.
  • 404 Not Found — No such connection in the workspace, or it was disconnected.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

DisconnectCloudConnection

DELETEBuyer

Disconnects a cloud account, or cancels a setup in progress. Deletes the connection with everything Pleaxy stored for it (for Oracle Cloud, its encrypted API key) and records who disconnected it. For Oracle Cloud the response names the key fingerprint to delete from user pleaxy-billing. Remove the access in your own account too.

Signed-in session with 'edit' access to suppliers; an API key gets 403. A setup in progress can only be cancelled by the person who started it.

Request Syntax

DELETE /v1/workspaces/{regionId}/cloud-connections/{id}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
idRequiredstringThe cloud connection id.

Response Syntax 200 OK

{
   "id": "string",
   "provider": "aws" | "gcp" | "azure" | "oci",
   "ociKeyFingerprint": "string"
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 403 Forbidden — The caller is an API key, lacks 'edit' on suppliers, or is cancelling someone else's setup ("Only the person who started this setup can cancel it.").
  • 404 Not Found — No such connection in the workspace.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.

AddCloudProviderAsSupplier

POSTBuyer

Adds a cloud provider to your suppliers list without connecting an account, so you can raise POs to it. Nothing is read from the provider. Returns the provider's existing supplier entry if there is one, and connecting later links to the same entry. Needs 'create' access to suppliers. Takes no body.

Request Syntax

POST /v1/workspaces/{regionId}/cloud-connections/{provider}/supplier

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
providerRequired"aws" | "gcp" | "azure" | "oci"The provider to add.

Response Syntax 201 Created

{
   "id": "string",
   "buyerOrgId": "string",
   "supplierName": "string",
   "cloudProvider": "aws" | "gcp" | "azure" | "oci",
   "status": "pending_invite" | "invited" | "in_progress" | "submitted" | "approved" | "rejected",
   "createdAt": "string"
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 404 Not Found — Unknown provider.
  • 409 Conflict — Code SUPPLIER_ALREADY_LISTED: a supplier with that name already has an onboarding under way or approved.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

Settlement

Autonomy settings control when POs and invoices skip manual approval; the plan controls what the workspace is entitled to. The plan itself is read-only over the API — Stripe is the system of record for Pleaxy subscriptions, and the entitlement is written only from Stripe's signed webhooks.

/v1/workspaces/{regionId}/settlement

GetAutonomySettings

GETBuyer

Returns the workspace's autonomous-settlement settings, defaulting to { enabled: false, maxAutoApproveAmount: 300, approvalThreshold: 25000 } if never configured.

Request Syntax

GET /v1/workspaces/{regionId}/settlement/autonomy

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.

Response Syntax 200 OK

{
   "enabled": boolean,
   "maxAutoApproveAmount": number,
   "approvalThreshold": number
}

Response Elements

NameTypeDescription
maxAutoApproveAmountnumberWith autonomy on, a PO or invoice at or below this amount (in the region's currency) is approved as Pleaxy Autonomy with nobody acting, unless a conditional or high-value step applies, a PO went over a spend limit, or the invoice can't be auto-approved (only an invoice matched against an issued AWS invoice can be, never one a buyer entered or one in another currency). For an invoice over the 3% match tolerance, it caps the difference instead.
approvalThresholdnumberThe high-value threshold: approval steps marked highValueOnly run only for amounts above it, and autonomy never approves a document such a step applies to. Applies whether or not autonomy is on.

Errors

  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

UpdateAutonomySettings

PATCHBuyer

Updates one or more autonomy settings. Enabling autonomy requires the workspace's plan to be Autonomous or Enterprise.

Signed-in session only: autonomy decides whether a person approves at all, so an API key gets 403 whatever its procurement role. Reading the settings accepts a buyer API key.

Request Syntax

PATCH /v1/workspaces/{regionId}/settlement/autonomy
{
   "enabled": boolean,
   "maxAutoApproveAmount": number,
   "approvalThreshold": number
}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.

Request Body

NameTypeDescription
enabledbooleanTurn autonomous approval on or off.
maxAutoApproveAmountnumberNew auto-approve ceiling.
approvalThresholdnumberNew high-value threshold.

Response Syntax 200 OK

{
   "enabled": boolean,
   "maxAutoApproveAmount": number,
   "approvalThreshold": number
}

Errors

  • 400 Bad Request — enabled: true was sent, but the region's plan is Free or Managed (autonomy requires Autonomous or higher).
  • 403 Forbidden — The caller isn't a signed-in person (for example, an API key): "This action requires a logged-in user session, not an API key". Nothing is saved.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

GetBilling

GETBuyer

Returns the workspace's plan and the state of its Pleaxy subscription. The plan is an entitlement cache written only from signed Stripe webhooks (or, for enterprise agreements, by Pleaxy operations) — there is no API for changing it, and no Pleaxy API ever accepts a plan from a caller. Self-serve changes go through Stripe-hosted Checkout and the Stripe Customer Portal, whose URLs are issued by the two session endpoints on this path; both require a human session, so API keys cannot start or change a subscription. Stripe identifiers are deliberately not part of this response.

Request Syntax

GET /v1/workspaces/{regionId}/settlement/billing

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.

Response Syntax 200 OK

{
   "billingEnabled": boolean,
   "plan": "assisted" | "managed" | "autonomous" | "enterprise",
   "managedBy": "self-serve" | "platform",
   "hasBillingAccount": boolean,
   "subscriptionStatus": "trialing" | "active" | "past_due" | "unpaid" | "canceled" | "incomplete" | "incomplete_expired" | "paused",
   "currentPeriodEnd": "string",
   "cancelAtPeriodEnd": boolean,
   "pastDueSince": "string",
   "availablePlans": [
      {
         "plan": "managed" | "autonomous",
         "monthlyPriceLabel": "string"
      }
   ]
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Response Elements

NameTypeDescription
billingEnabledbooleanfalse when the environment has no Stripe configuration; the session endpoints then return 503.
planstringThe entitlement plan limits are enforced against.
managedBystring"platform" for an enterprise agreement assigned by Pleaxy operations — no self-serve changes; otherwise "self-serve".
hasBillingAccountbooleanA Stripe Customer exists for this workspace, so the Customer Portal is available.
subscriptionStatusstringStripe's subscription status, copied verbatim. Absent when the workspace has never subscribed.
currentPeriodEndstringISO 8601 renewal date, or the end-of-access date when cancelAtPeriodEnd is true.
cancelAtPeriodEndbooleanThe subscription is set to end at currentPeriodEnd rather than renew.
pastDueSincestringISO 8601 timestamp of the failed payment. The paid plan is kept for the duration of Stripe's dunning window.
availablePlansarrayThe purchasable plans with their current monthly price label, read from Stripe. Empty when billing is disabled or Stripe pricing is temporarily unavailable.

Errors

  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

Approval chains

Configures who approves purchase orders and invoice payments — separate from Settlement's autonomy settings, which control whether a person is needed at all rather than which person. Each of the two processes (purchase_order, invoice_payment) has one approval flow, under Workflows in Pleaxy, or is linked to share the other process's flow. A flow is an ordered list of groups, run one after another. A sequential group is one step with one approver; a parallel group is several steps that must all be approved, in any order, by different people. A group with conditions runs only when every condition matches the document (amount, supplier or requester); a step marked highValueOnly runs only when the amount is above Settlement's approvalThreshold. Every step names a specific employee, and only that employee can act on it. A new region starts with one step, 'Region administrator'. When a PO or invoice is created, its chain is a point-in-time snapshot of the flow, so editing the flow later doesn't change documents already waiting.

/v1/workspaces/{regionId}/approval-chains

GetApprovalChainSettings

GETBuyer

Returns the approval flow for a process, or { process, groups: [] } if the process has never had one. Accepts a buyer API key.

Request Syntax

GET /v1/workspaces/{regionId}/approval-chains/{process}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
processRequired"purchase_order" | "invoice_payment"Which flow's approval chain to fetch.

Response Syntax 200 OK

{
   "process": "purchase_order" | "invoice_payment",
   "linkedTo": "purchase_order" | "invoice_payment",
   "groups": [
      {
         "id": "string",
         "mode": "sequential" | "parallel",
         "label": "string",
         "stages": [
            {
               "id": "string",
               "label": "string",
               "approverEmployeeKey": "string",
               "approverName": "string",
               "approverEmail": "string",
               "highValueOnly": boolean
            }
         ],
         "conditions": [
            { "field": "amount" | "supplier" | "requester", "operator": "gt" | "gte" | "lt" | "lte" | "is" | "is_not", "value": number | "string" }
         ]
      }
   ],
   "source": "default",
   "defaultApproverIsAdmin": boolean,
   "requesterRule": { "eligibleApproverCount": number }
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Response Elements

NameTypeDescription
linkedTo"purchase_order" | "invoice_payment"When set, this process uses the other process's flow (a live link, not a copy), and groups is ignored.
groups[].mode"sequential" | "parallel"sequential: exactly one step. parallel: every step must be approved, in any order, each by a different employee.
groups[].conditionsarrayWhen present, the group runs only if every condition matches. amount uses gt, gte, lt or lte with a number in the region's currency; supplier uses is or is_not with a supplier Workspace ID (ending in .supplier); requester uses is or is_not with an employee key. A document in another currency than the region's meets every amount condition.
groups[].stages[].approverEmployeeKeystringThe only employee who can act on the step. '' means the step has no approver yet — it needs one assigned before anyone can act on it. Only a flow saved before named approvers were required can carry it.
groups[].stages[].highValueOnlybooleanThe step runs only when the amount is above Settlement's approvalThreshold (the high-value threshold).
source"default"Present when Pleaxy wrote this flow as the region's default. Any other save clears it.
defaultApproverIsAdminbooleanOnly with source "default": whether the default step's approver is still a region administrator.
requesterRule.eligibleApproverCountnumberHow many buyer members can approve this process's documents. A requester can approve their own document only when nobody else could.

Errors

  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

UpdateApprovalChainSettings

PATCHBuyer

Replaces a process's flow with groups, links it to the other process's flow with linkedTo, or resets it to the region default with useDefault: true. Sending groups clears any linkedTo; sending linkedTo keeps the current groups on record but inactive while the link holds. Each approverEmployeeKey is checked against the workspace's employees. The flow must keep at least one group with no conditions that runs for ordinary amounts, so every document reaches a person. stages, a flat list of single-step groups, is still accepted for older clients; prefer groups.

Changed 2026-09-27, a security fix inside v1: Signed-in session only, for every process: an approval chain decides who approves spend, so an API key gets 403 HUMAN_SESSION_REQUIRED whatever its procurement role.

Request Syntax

PATCH /v1/workspaces/{regionId}/approval-chains/{process}
{
   "groups": [
      {
         "id": "string",
         "mode": "sequential" | "parallel",
         "label": "string",
         "stages": [
            { "id": "string", "label": "string", "approverEmployeeKey": "string", "highValueOnly": boolean }
         ],
         "conditions": [
            { "field": "amount" | "supplier" | "requester", "operator": "gt" | "gte" | "lt" | "lte" | "is" | "is_not", "value": number | "string" }
         ]
      }
   ]
}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
processRequired"purchase_order" | "invoice_payment"Which flow's approval chain to update.

Request Body

NameTypeDescription
groupsarrayThe new flow, in order. Each { id, mode, label?, stages, conditions? }. stages are { id, label, approverEmployeeKey, highValueOnly? }: a sequential group has exactly one, a parallel group at least one, with unique ids and different approvers. Up to 10 conditions per group. At least one group.
linkedTo"purchase_order" | "invoice_payment"Use the other process's flow instead of this one's own.
useDefaulttrueReset to the region default: one step, 'Region administrator', naming the region's owning administrator, or else the first administrator by name. Can't be combined with groups, stages or linkedTo.
stagesarrayLegacy flat shape, one sequential step per entry, { id, label, approverEmployeeKey, highValueOnly? }. Saved as the equivalent groups.

Response Syntax 200 OK

{
   "process": "purchase_order" | "invoice_payment",
   "linkedTo": "purchase_order" | "invoice_payment",
   "groups": [
      {
         "id": "string",
         "mode": "sequential" | "parallel",
         "label": "string",
         "stages": [
            {
               "id": "string",
               "label": "string",
               "approverEmployeeKey": "string",
               "approverName": "string",
               "approverEmail": "string",
               "highValueOnly": boolean
            }
         ],
         "conditions": [
            { "field": "amount" | "supplier" | "requester", "operator": "gt" | "gte" | "lt" | "lte" | "is" | "is_not", "value": number | "string" }
         ]
      }
   ],
   "source": "default",
   "defaultApproverIsAdmin": boolean,
   "requesterRule": { "eligibleApproverCount": number }
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 400 Bad Request — A step has no approver ("'{label}' has no approver — every approval step must name a specific employee.") or names someone who isn't an employee of the workspace; a parallel group names the same approver twice; a sequential group doesn't have exactly one step; a condition is malformed; the flow has no group with no conditions that runs for ordinary amounts ("A chain needs at least one approval step with no conditions that applies to ordinary purchase amounts…"); groups or stages is empty; linkedTo names this process, or a process already linked back to it; or useDefault is combined with groups, stages or linkedTo.
  • 409 Conflict — Code NO_REGION_ADMIN, with useDefault: "This region has no administrator to route approvals to. Make someone an administrator first."
  • 403 Forbidden — Code HUMAN_SESSION_REQUIRED. The caller isn't a signed-in person (for example, an API key), for any process ("Approval chains can only be changed by a signed-in person. Change them in Pleaxy."). Nothing is saved.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

Automation rules

Conditional overrides on top of Settlement's autonomy settings, per process (purchase_order, invoice_payment): a rule matches by supplier account (or, for a supplier with no Pleaxy account, by name) and amount, and either auto-approves a match with nobody acting, or forces the full approval chain. An auto-approve rule needs the Autonomous plan and never applies to an invoice that autonomy couldn't approve either (one a buyer entered, one in another currency, or one not matched against an issued AWS invoice), nor to a PO over a spend limit. Rules are evaluated in order, and the first enabled match wins.

/v1/workspaces/{regionId}/automation-rules

GetAutomationRules

GETBuyer

Returns a process's automation rules in priority order, or { process, rules: [] } if none are configured, and whether the workspace's plan lets auto-approve rules run.

Request Syntax

GET /v1/workspaces/{regionId}/automation-rules/{process}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
processRequired"purchase_order" | "invoice_payment"Which flow's rules to fetch.

Response Syntax 200 OK

{
   "process": "purchase_order" | "invoice_payment",
   "rules": [
      { "id": "string", "label": "string", "conditions": { "supplierWorkspaceId": "string", "supplierContains": "string", "maxAmount": number }, "autoApprove": boolean, "enabled": boolean, "createdAt": "string" }
   ],
   "autoApproveAllowed": boolean
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Response Elements

NameTypeDescription
autoApproveAllowedbooleanWhether the plan allows autonomy (Autonomous or Enterprise). When false, rules with autoApprove true are kept but skipped when orders and invoices are evaluated, so a rule behind them can still match; upgrading makes them run again. Computed on every read, never stored.

Errors

  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

UpdateAutomationRules

PATCHBuyer

Replaces a process's automation rules, in priority order. A rule that keeps its id keeps its createdAt.

Changed 2026-09-27, a security fix inside v1: Signed-in session only, for every process: an auto-approve rule approves purchase orders or invoices with nobody acting, so an API key gets 403 HUMAN_SESSION_REQUIRED whatever its procurement role. Reading the rules still accepts a buyer API key.

Request Syntax

PATCH /v1/workspaces/{regionId}/automation-rules/{process}
{
   "rules": [
      { "id": "string", "label": "string", "conditions": { "supplierWorkspaceId": "string", "supplierContains": "string", "maxAmount": number }, "autoApprove": boolean, "enabled": boolean }
   ]
}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
processRequired"purchase_order" | "invoice_payment"Which flow's rules to replace.

Request Body

NameTypeDescription
rulesRequiredarrayEach { id?, label, conditions: { supplierWorkspaceId?, supplierContains?, maxAmount? }, autoApprove, enabled }. supplierWorkspaceId is the supplier account the rule applies to (its Workspace ID, e.g. reg_….supplier) and must be one of your connected suppliers; suppliers are matched by account, never by name. supplierContains is legacy and matches only suppliers with no Pleaxy account. autoApprove true skips approval for a match; false forces the full chain.

Response Syntax 200 OK

{
   "process": "purchase_order" | "invoice_payment",
   "rules": [
      { "id": "string", "label": "string", "conditions": { "supplierWorkspaceId": "string", "supplierContains": "string", "maxAmount": number }, "autoApprove": boolean, "enabled": boolean, "createdAt": "string" }
   ]
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 400 Bad Request — AUTONOMY_PLAN_REQUIRED: a new rule, or a change to an existing one, sets autoApprove true on a plan without autonomy ("Auto-approve rules need the Autonomous plan — the {Plan} plan allows rules that require approval only. Upgrade to Autonomous to use auto-approve rules."). Nothing is saved. A rule already stored with autoApprove true can be sent back with its conditions unchanged (its label and on/off switch may change), so the rest of the list stays editable; changing its conditions is refused.
  • 400 Bad Request — DUPLICATE_RULE_ID: the list carries the same rule id more than once ("Rule '{id}' is in the list more than once. Send each rule once."). Nothing is saved.
  • 403 Forbidden — Code HUMAN_SESSION_REQUIRED. The caller isn't a signed-in person (for example, an API key), for any process ("Automation rules can only be changed by a signed-in person. Change them in Pleaxy."). Nothing is saved.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

Approvals

The caller's own approval queue: the purchase orders and invoices whose current step names them.

/v1/workspaces/{regionId}/approvals

ListApprovals

GETBuyer

Lists the purchase orders and invoices in this workspace whose current approval step names the caller, and, for an administrator, the documents whose current step names nobody and so needs an approver assigned.

Changed 2026-09-24, a security fix inside v1: needsAssignment is filled only for a signed-in administrator, the only caller who can reassign a step. For an API key, whatever its procurement role, every list is empty: no approval step can name a key.

Request Syntax

GET /v1/workspaces/{regionId}/approvals

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.

Response Syntax 200 OK

{
   "purchaseOrders": [ PurchaseOrder ],
   "invoices": [ Invoice ],
   "needsAssignment": { "purchaseOrders": [ PurchaseOrder ], "invoices": [ Invoice ] }
}

Response Elements

NameTypeDescription
purchaseOrders / invoicesarrayDocuments at a step that names the caller.
needsAssignmentobjectDocuments whose current step names nobody. Empty unless the caller is a signed-in administrator.

Errors

  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

Employees

Workspace members available to assign as approval-chain approvers.

/v1/workspaces/{regionId}/employees

ListEmployees

GETBoth

Lists employees in this workspace, e.g. to populate the approval-chain approver picker in Settings.

Request Syntax

GET /v1/workspaces/{regionId}/employees

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
roleRequired"buyer" | "supplier"Which side of the workspace to list employees for.

Response Syntax 200 OK

[ {
      "key": "string",
      "name": "string",
      "email": "string",
      "title": "string"
   } ]

Errors

  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

Workspaces

Dashboard snapshots that aggregate the other resources into the two views the console renders.

/v1/workspaces/{regionId}

GetBuyerWorkspace

GETBuyer

Returns the buyer dashboard snapshot: this region's connected cloud accounts, purchase orders, and invoices. Requires the buyer role in this workspace. Optionally scoped to a createdAt range via from/to — pass a calendar month's bounds to get a per-month view.

Request Syntax

GET /v1/workspaces/{regionId}/buyer

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
fromstringISO 8601 date/timestamp — only items created on or after this are returned.
tostringISO 8601 date/timestamp — only items created on or before this are returned.

Response Syntax 200 OK

{
   "cloudConnections": [ {
      "id": "string",
      "provider": "aws" | "gcp" | "azure" | "oci",
      "accountLabel": "string",
      "detail": "string",
      "spend": number,
      "status": "connected" | "pending" | "error",
      "connectedAt": "string",
      "awsAccountId": "string",
      "dataSources": [ "invoices" | "current_cost" ],
      "gcp": { "billingAccountId": "string", "providerId": "string" },
      "oci": { "homeRegion": "string", "keyFingerprint": "string" },
      "azure": { "scopeDisplayName": "string", "agreementType": "string" },
      "lastSyncedAt": "string",
      "lastSyncStatus": "ok" | "error" | "throttled" | "never",
      "lastSyncError": "string",
      "lastSyncErrorCode": "throttled" | "access_revoked" | "platform_config" | "data_not_enabled" | "unknown"
   } ],
   "purchaseOrders": [ {
      "id": "string",
      "buyerOrgId": "string",
      "supplierOrgId": "string",
      "supplier": "string",
      "amount": number,
      "currency": "string",
      "status": "draft" | "pending_approval" | "rejected" | "awaiting_fulfillment" | "invoiced" | "paid" | "canceled" | "closed" | "expired",
      "invoiceId": "string",
      "provider": "aws" | "gcp" | "azure" | "oci",
      "payerAccounts": [
         { "id": "string", "label": "string", "detail": "string" }
      ],
      "billTo": "string",
      "costCenter": "string",
      "budgetOwner": "string",
      "buyerName": "string",
      "supplierSite": "string",
      "notes": "string",
      "lineItems": [
         {
         "lineNo": number,
         "scopeType": "sku" | "service" | "account" | "custom",
         "scopeId": "string" | null,
         "name": "string",
         "ruleType": "quantity" | "amount",
         "unit": "string",
         "unitPrice": number,
         "qty": number,
         "nteAmount": number,
         "currency": "string",
         "paymentTerms": "string",
         "validFrom": "string",
         "validUntil": "string",
         "consumed": number,
         "customerLineNo": "string",
         "supplierSku": "string",
         "priceSource": "contract" | "list" | "manual"
       }
      ],
      "approval": {
         "requester": "string",
         "requesterEmployeeKey": "string",
         "groups": [
           {
             "id": "string",
             "mode": "sequential" | "parallel",
             "label": "string",
             "status": "pending" | "approved" | "rejected",
             "stages": [
               { "id": "string", "label": "string", "approverEmployeeKey": "string", "approverName": "string", "status": "pending" | "approved" | "rejected" }
             ]
           }
         ],
         "groupIndex": number,
         "status": "pending" | "approved" | "rejected",
         "history": [
           { "stage": "string", "by": "string", "at": "string", "action": "approved" | "rejected" | "limit_exceeded" | "reassigned" | "skipped" }
         ],
         "autonomy": boolean,
         "selfApproved": boolean
       },
      "createdAt": "string"
   } ],
   "invoices": [ {
      "id": "string",
      "po": "string",
      "buyerOrgId": "string",
      "supplierOrgId": "string",
      "supplier": "string",
      "amount": number,
      "currency": "string",
      "usageAmount": number,
      "usageCurrency": "string",
      "tolerancePct": number,
      "matchStatus": "pending" | "matched" | "discrepancy" | "unverified",
      "unverifiedReason": "no_purchase_order" | "no_billing_connection",
      "discrepancyReason": "amount" | "currency",
      "paid": boolean,
      "dueInDays": number,
      "approval": {
         "requester": "string",
         "requesterEmployeeKey": "string",
         "groups": [
           {
             "id": "string",
             "mode": "sequential" | "parallel",
             "label": "string",
             "status": "pending" | "approved" | "rejected",
             "stages": [
               { "id": "string", "label": "string", "approverEmployeeKey": "string", "approverName": "string", "status": "pending" | "approved" | "rejected" }
             ]
           }
         ],
         "groupIndex": number,
         "status": "pending" | "approved" | "rejected",
         "history": [
           { "stage": "string", "by": "string", "at": "string", "action": "approved" | "rejected" | "limit_exceeded" | "reassigned" | "skipped" }
         ],
         "autonomy": boolean,
         "selfApproved": boolean
       },
      "createdAt": "string",
      "lastRemindedAt": "string",
      "rejection": { "reasonCode": "amount_incorrect" | "not_ordered_or_not_received" | "duplicate" | "missing_or_wrong_po" | "wrong_billing_entity" | "other", "reason": "string", "at": "string" },
      "replacesInvoiceId": "string",
      "source": "po-flip" | "direct" | "manual" | "feed",
      "submittedFormat": "json" | "cxml" | "ubl",
      "submittedBy": "string",
      "externalInvoiceNumber": "string",
      "lineItems": [
         { "description": "string", "qty": number, "unitPrice": number, "discount": number, "charge": number, "amount": number }
      ],
      "adjustments": [
         { "type": "discount" | "charge", "description": "string", "amount": number }
      ],
      "taxAmount": number,
      "taxBreakdown": [ { "description": "string", "rate": number, "amount": number } ],
      "paymentCurrencyAmount": { "currencyCode": "string", "totalAmount": number, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
      "taxCurrencyAmount": { "currencyCode": "string", "amountBreakdown": { "taxes": { "totalAmount": number } }, "currencyExchangeDetails": { "rate": number, "rateDate": "string", "rateSource": "string" } },
      "baseCurrencyAmount": { "currencyCode": "string", "totalAmount": number },
      "rateWarnings": [
         { "object": "payment" | "tax", "currency": "string", "statedRate": number, "referenceRate": number, "source": "provider" | "override", "asOf": "string", "deviationPct": number }
      ],
      "payment": { "status": "recorded", "method": "string", "paymentDate": "string", "reference": "string", "paidAmount": { "amount": number, "currency": "string", "rate": number, "rateDate": "string" } }
   } ]
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller doesn't have the buyer role in this workspace, or no access to it at all.

Org profiles

A buyer or supplier organization's business profile — billing details and primary contact.

/v1/workspaces/{regionId}/profile

GetOrgProfile

GETBoth

Returns the caller's organization profile for this workspace (buyer or supplier, based on the caller's role).

Request Syntax

GET /v1/workspaces/{regionId}/profile

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.

Response Syntax 200 OK

{
   "id": "string",
   "role": "buyer" | "supplier",
   "regionId": "string",
   "regionName": "string",
   "companyName": "string",
   "billingEmail": "string",
   "billingAddress": "string",
   "taxId": "string",
   "currency": "string",
   "countries": [ "string" ],
   "timeZone": "string",
   "primaryContactName": "string",
   "primaryContactEmail": "string",
   "createdAt": "string"
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 404 Not Found — No org profile exists yet for this workspace and role.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

UpdateOrgProfile

PATCHBoth

Updates one or more fields of the caller's organization profile.

Request Syntax

PATCH /v1/workspaces/{regionId}/profile
{
   "regionName": "string",
   "currency": "string",
   "companyName": "string",
   "billingEmail": "string",
   "billingAddress": "string",
   "taxId": "string",
   "primaryContactName": "string",
   "primaryContactEmail": "string",
   "countries": [ "string" ],
   "geographyPresetId": "string",
   "timeZone": "string"
}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.

Request Body

NameTypeDescription
regionNamestringThe region's display name, up to 40 characters.
currencystringISO 4217 code, case-insensitive (stored uppercase). A buyer workspace's currency can't change once it has any purchase order (drafts aside) or invoice.
companyNamestringLegal or display company name.
billingEmailstringEmail address invoices/receipts are sent to.
billingAddressstringBilling address.
taxIdstringTax identification number.
primaryContactNamestringPrimary contact's name.
primaryContactEmailstringPrimary contact's email.
countriesstring[]Replaces the region's countries, as ISO 3166-1 alpha-2 codes, unique, up to 250. Never changes currency.
geographyPresetIdstring | nullRecords which preset (for example eu, uk, apac, global) the country list came from; null clears it.
timeZonestringIANA time zone, e.g. Europe/London. Sets the workspace's "today" for payment dates.

Response Syntax 200 OK

{
   "id": "string",
   "role": "buyer" | "supplier",
   "regionId": "string",
   "regionName": "string",
   "companyName": "string",
   "billingEmail": "string",
   "billingAddress": "string",
   "taxId": "string",
   "currency": "string",
   "countries": [ "string" ],
   "timeZone": "string",
   "primaryContactName": "string",
   "primaryContactEmail": "string",
   "createdAt": "string"
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 400 Bad Request — A field failed validation — for example regionName is longer than 40 characters, or currency isn't an ISO 4217 code.
  • 409 Conflict — currency differs from the stored one, and this buyer workspace already has purchase orders or invoices recorded in it. Nothing is changed. To buy in another currency, add a region.
  • 404 Not Found — No org profile exists yet for this workspace and role.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

Notifications

In-app notifications for buyer and supplier events (approvals, discrepancies, payments), plus supplier-side reminder autonomy.

/v1/workspaces/{regionId}/notifications

ListNotifications

GETBoth

Lists up to the 50 most recent notifications for the given audience, newest first. Notifications are kept for 90 days. Use ListNotificationsPage to page through older ones.

Request Syntax

GET /v1/workspaces/{regionId}/notifications

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
audienceRequired"buyer" | "supplier"Which side's notification feed to return.

Response Syntax 200 OK

[
   {
      "id": "string",
      "audience": "buyer" | "supplier",
      "type": "po_approved" | "po_rejected" | "po_canceled" | "po_approval_requested" | "po_needs_approver" | "invoice_submitted" | "invoice_ready_for_approval" | "invoice_approval_requested" | "invoice_discrepancy" | "invoice_approved" | "invoice_paid" | "invoice_payment_withdrawn" | "invoice_payment_failed" | "invoice_reminder" | "invoice_rejected" | "invoice_payment_reminder" | "invoice_entered_by_buyer" | "invoice_needs_approver" | "invoice_feed_changed" | "supplier_connection_invited" | "supplier_connection_submitted" | "supplier_connection_approved" | "supplier_connection_rejected" | "supplier_connection_region_mismatch" | "support_case_opened" | "support_case_updated" | "supplier_late_fee_terms_changed",
      "title": "string",
      "body": "string",
      "relatedId": "string",
      "createdAt": "string",
      "read": boolean
   }
]

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

ListNotificationsPage

GETBoth

Lists notifications for the given audience one page at a time, newest first. Pass a page's nextCursor back as cursor to get the next older page. A page can be shorter than limit, or even empty, and still have a nextCursor, so keep going until nextCursor is absent. Notifications are kept for 90 days.

Request Syntax

GET /v1/workspaces/{regionId}/notifications/inbox

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
audienceRequired"buyer" | "supplier"Which side's notification feed to return.
limitnumberPage size, 1 to 50. Defaults to 20.
cursorstringThe nextCursor from the previous page. Omit it for the newest page.

Response Syntax 200 OK

{
   "items": [
      {
         "id": "string",
         "audience": "buyer" | "supplier",
         "type": "po_approved" | "po_rejected" | "po_canceled" | "po_approval_requested" | "po_needs_approver" | "invoice_submitted" | "invoice_ready_for_approval" | "invoice_approval_requested" | "invoice_discrepancy" | "invoice_approved" | "invoice_paid" | "invoice_payment_withdrawn" | "invoice_payment_failed" | "invoice_reminder" | "invoice_rejected" | "invoice_payment_reminder" | "invoice_entered_by_buyer" | "invoice_needs_approver" | "invoice_feed_changed" | "supplier_connection_invited" | "supplier_connection_submitted" | "supplier_connection_approved" | "supplier_connection_rejected" | "supplier_connection_region_mismatch" | "support_case_opened" | "support_case_updated" | "supplier_late_fee_terms_changed",
         "title": "string",
         "body": "string",
         "relatedId": "string",
         "createdAt": "string",
         "read": boolean
      }
   ],
   "nextCursor": "string",
   "unreadCount": number
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Response Elements

NameTypeDescription
itemsNotification[]This page's notifications, newest first.
nextCursorstringOpaque. Absent when there are no older notifications.
unreadCountnumberUnread notifications visible to you, counted up to 10 (a value of 10 means 10 or more). Returned on the first page only (no cursor).

Errors

  • 400 Bad Request — The cursor is not one this API issued, or limit is outside 1 to 50.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

MarkNotificationRead

POSTBoth

Marks a single notification as read. Silently succeeds if the notification doesn't exist.

Request Syntax

POST /v1/workspaces/{regionId}/notifications/{notificationId}/read

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
notificationIdRequiredstringThe notification identifier.

Response Syntax 201 Created

(no content)

Errors

  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

MarkAllNotificationsRead

POSTBoth

Marks the unread notifications visible to you for the given audience as read. Read state is shared: a notification sent to the whole workspace is marked read for everyone who can see it. One call marks up to 200; when remaining is true, call again with nextCursor to mark the rest.

Request Syntax

POST /v1/workspaces/{regionId}/notifications/read-all

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
audienceRequired"buyer" | "supplier"Which side's notifications to mark read.
cursorstringThe nextCursor from the previous call, to continue where it stopped. Omit it to start from the newest.

Response Syntax 201 Created

{
   "updated": number,
   "remaining": boolean,
   "nextCursor": "string"
}

Response Elements

NameTypeDescription
updatednumberHow many notifications this call marked read.
remainingbooleanTrue when this call stopped at its limit and unread notifications may remain.
nextCursorstringOpaque. Present when remaining is true; pass it back as cursor on the next call.

Errors

  • 400 Bad Request — The cursor is not one this API issued.
  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

GetReminderSettings

GETBoth

Returns the workspace's reminder-autonomy settings, defaulting to { enabled: false, idleDays: 3 } if never configured.

Request Syntax

GET /v1/workspaces/{regionId}/notifications/reminder-settings

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.

Response Syntax 200 OK

{
   "enabled": boolean,
   "idleDays": number
}

Response Elements

NameTypeDescription
idleDaysnumberDays an invoice can sit idle in the same approval stage before an automatic reminder fires.

Errors

  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

UpdateReminderSettings

PATCHBoth

Updates one or more reminder-autonomy settings.

Request Syntax

PATCH /v1/workspaces/{regionId}/notifications/reminder-settings
{
   "enabled": boolean,
   "idleDays": number
}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.

Request Body

NameTypeDescription
enabledbooleanTurn automatic reminders on or off.
idleDaysnumberNew idle threshold in days.

Response Syntax 200 OK

{
   "enabled": boolean,
   "idleDays": number
}

Errors

  • 401 Unauthorized — No credential was presented, or the Bearer token / X-Api-Key value is missing, invalid, expired, or revoked.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

API keys

Scoped machine-to-machine credentials, one per workspace membership (regionId + role). An Agent presents a key via the X-Api-Key header to call endpoints like SubmitInvoice (buyer keys) or FlipPurchaseOrderToInvoice (supplier keys) without a signed-in session.

/v1/workspaces/{regionId}/api-keys

CreateApiKey

POSTBoth

Mints a new API key scoped to the caller's own role in this workspace — a supplier can't mint a buyer-scoped key, and vice versa. The plaintext key is returned only in this response; only its HMAC digest is ever stored. Only an administrator can mint a key: on the Buyer side anyone else gets 403 ("Only an administrator of this workspace can manage its API keys."), and on the Supplier side 403 ("Only a supplier administrator can create API keys for this workspace."). A buyer key acts with the procurementRole it is minted with, read-only unless you choose another. A supplier administrator can add the invoices:feed scope, which lets the key call SubmitFeedInvoice on top of everything a supplier key already does. A non-administrator asking for a scope gets 403 API_KEY_SCOPE_FORBIDDEN, and a Buyer caller sending scopes gets 400 API_KEY_SCOPE_INVALID ("Scopes are for supplier-side keys only. Buyer keys are scoped by procurementRole. Remove scopes and try again.").

Requires a human Cognito session (Bearer token) — an existing API key can never mint or revoke another key. Only an administrator of the workspace can mint, list or revoke its keys.

Request Syntax

POST /v1/workspaces/{regionId}/api-keys
{
   "label": "string",
   "procurementRole": "read-only",
   "scopes": [ "invoices:feed" ]
}

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.

Request Body

NameTypeDescription
labelRequiredstringHuman-readable label, e.g. "Billing agent - prod".
procurementRole"administrator" | "read-only" | "contracts-and-suppliers" | "relationships" | "requisitions" | "finance"Buyer keys only (ignored for a supplier key): what the key may do, by the same procurement roles people have. Defaults to read-only. For example, requisitions can create and edit purchase orders, and finance can work invoices. Whatever the role, a key never approves, rejects, records a payment, or changes approval flows, autonomy or automation rules.
scopesArray<"invoices:feed">Supplier keys only, and only from a supplier administrator. invoices:feed lets the key submit invoices through SubmitFeedInvoice. Omit for a key without scopes.

Response Syntax 201 Created

{
   "apiKey": "string",
   "keyId": "string",
   "regionId": "string",
   "role": "buyer" | "supplier",
   "scopes": [ "invoices:feed" ],
   "label": "string",
   "createdAt": "string",
   "revoked": false
}

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Response Elements

NameTypeDescription
apiKeystringThe plaintext credential, formatted "<keyId>.<secret>" — pass it verbatim as the X-Api-Key header. Shown only once; it cannot be retrieved again.

Errors

  • 403 Forbidden — The caller authenticated with an API key rather than a human Bearer token ("This action requires a logged-in user session, not an API key"), or, for Supplier callers, isn't a supplier administrator of this workspace ("Only a supplier administrator can create API keys for this workspace.").
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

Example

Request

curl -X POST "$PLEAXY_API_BASE_URL/v1/workspaces/reg_01k5x8p9j3q7vn2mhb4tzc6rde.supplier/api-keys" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "label": "Billing agent - prod" }'

Response

{
  "apiKey": "5b1c2e90-....-9a3f.9f2c7a1e8b6d4c0a2f1e3d5b7a9c8e6f",
  "keyId": "5b1c2e90-....-9a3f",
  "regionId": "reg_01k5x8p9j3q7vn2mhb4tzc6rde",
  "role": "supplier",
  "label": "Billing agent - prod",
  "createdAt": "2026-02-03T09:00:00.000Z",
  "revoked": false
}

ListApiKeys

GETBoth

Lists API keys for the workspace. Never includes the plaintext secret or its hash. scopes is empty for a key minted without one.

Requires a human Cognito session (Bearer token).

Request Syntax

GET /v1/workspaces/{regionId}/api-keys

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.

Response Syntax 200 OK

[
   {
      "keyId": "string",
      "regionId": "string",
      "role": "buyer" | "supplier",
      "scopes": [ "invoices:feed" ],
      "label": "string",
      "createdAt": "string",
      "revoked": boolean
   }
]

The value sets above are open-ended — new values ship within /v1. Keep a default branch for values you do not recognize (see Versioning).

Errors

  • 403 Forbidden — The caller authenticated with an API key rather than a human Bearer token. The response message is "This action requires a logged-in user session, not an API key".
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.

RevokeApiKey

POSTBoth

Revokes an API key immediately and permanently. A revoked key fails authentication on its next use.

Requires a human Cognito session (Bearer token).

Request Syntax

POST /v1/workspaces/{regionId}/api-keys/{keyId}/revoke

Request Parameters

NameTypeDescription
regionIdRequiredstringThe Workspace ID (path parameter) — copy it from the Pleaxy console under Company profile → Workspace ID, e.g. reg_01k5x8p9j3q7vn2mhb4tzc6rde.buyer. Every route is scoped to one workspace's data. Older workspace ids (such as us) keep working.
keyIdRequiredstringThe API key identifier (not the plaintext secret).

Response Syntax 201 Created

(no content)

Errors

  • 403 Forbidden — The caller authenticated with an API key rather than a human Bearer token. The response message is "This action requires a logged-in user session, not an API key".
  • 404 Not Found — No key with this id exists in the workspace.
  • 403 Forbidden — The caller's Bearer token or API key doesn't grant access to this workspace (regionId), or lacks the required role.
Talk to us