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.
ListInvoices
GETBothLists 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
| Name | Type | Description |
|---|
| regionIdRequired | string | The 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. |
| from | string | ISO 8601 date/timestamp — only items created on or after this are returned. |
| to | string | ISO 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
GETBothFetches 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
| Name | Type | Description |
|---|
| regionIdRequired | string | The 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. |
| invoiceIdRequired | string | The invoice identifier. |
| buyer | string | Supplier 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
POSTBothBuyer 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
| Name | Type | Description |
|---|
| regionIdRequired | string | The 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
| Name | Type | Description |
|---|
| invoiceNumber | string | Supplier-provided invoice number, used to dedupe retried submissions within the region. |
| poId | string | Pleaxy PO id this invoice fulfills, e.g. "PO-AB12CD34". Omit to submit a standalone invoice. |
| supplierRequired | string | Supplier / counterparty display name. |
| amountRequired | number | Invoice total. Must be greater than 0. |
| currency | string | ISO 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. |
| dueInDays | number | Payment terms in days. Defaults to 30. |
| lineItems | Array<{ description, qty?, unitPrice?, amount }> | Optional line-item breakdown of the invoice. |
| remittanceInstructions | object | Where 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. |
| pdfBase64 | string | Base64-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
| Name | Type | Description |
|---|
| id | string | Invoice identifier. "INV-<PO suffix>" when poId was given, otherwise a random "INV-XXXXXXXX". |
| matchStatus | string | "pending" if poId was given and a live billing figure exists for its provider, "unverified" otherwise. |
| unverifiedReason | string | Set when matchStatus is "unverified": "no_purchase_order" or "no_billing_connection". |
| currency | string | The currency the invoice is recorded in, uppercase. |
| source | string | Always "direct" for this endpoint. |
| submittedFormat | string | "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
GETBothThe 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
| Name | Type | Description |
|---|
| regionIdRequired | string | The 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
| Name | Type | Description |
|---|
| connectionId | string | The supplier connection to invoice — CreateInvoice's connectionId. |
| name | string | The counterparty's name: the supplier's for a Buyer caller, the buyer's for a Supplier caller. |
| currency | string | The buyer's currency. Every invoice to this counterparty is recorded in it. |
| openPurchaseOrders | Array | Purchase orders awaiting fulfillment, with no invoice yet, between exactly this buyer and supplier — the poIds CreateInvoice accepts. |
| invoiceFeedEnabled | boolean | Buyer callers only: true when this supplier's automated invoices are on (see SetInvoiceFeed). Absent means off. |
| invoiceFeedUpdatedAt | string | Buyer 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
POSTBothSent 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
| Name | Type | Description |
|---|
| regionIdRequired | string | The 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
| Name | Type | Description |
|---|
| connectionIdRequired | string | The counterparty to invoice — a connectionId from ListInvoiceCounterparties. |
| poId | string | An open purchase order between exactly this buyer and supplier, from the counterparty's openPurchaseOrders. |
| invoiceNumberRequired | string | 1–64 characters: letters, digits, '.', '_', '/' and '-', starting with a letter or digit. Unique per supplier within the buyer's workspace, case-insensitively. |
| invoiceDateRequired | string | YYYY-MM-DD. Not in the future and not more than 365 days back. |
| dueInDays | number | Payment terms in days, 0–365. Defaults to 30. |
| lineItemsRequired | Array<{ 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. |
| adjustments | Array<{ 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). |
| taxAmount | number | Total 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. |
| currency | string | ISO 4217, optional. Checked only: it must be the buyer's currency, which is always the one stored. |
| pdfBase64 | string | Base64-encoded PDF of the invoice, up to 8MB. |
| remittanceInstructions | object | Where 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. |
| taxBreakdown | Array<{ 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. |
| paymentCurrencyAmount | object | Optional. 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. |
| taxCurrencyAmount | object | Optional. 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
| Name | Type | Description |
|---|
| source | string | Always "manual" for this endpoint. |
| amount | number | The 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. |
| lineItems | Array | Each line as stored. amount is the net line, before tax; discount and charge appear only on a line that has them. |
| adjustments | Array | Invoice-level discounts and charges. Absent when there are none. |
| taxAmount | number | The stated tax, already included in amount. Display only: no decision reads it. Absent when none was stated. |
| currency | string | The 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
GETBuyerThe 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
| Name | Type | Description |
|---|
| regionIdRequired | string | The 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
PUTBuyerSets 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
| Name | Type | Description |
|---|
| regionIdRequired | string | The 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
| Name | Type | Description |
|---|
| warningPctRequired | number | 0 to 100. |
Response Syntax 200 OK
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
PUTBuyerSets 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
| Name | Type | Description |
|---|
| regionIdRequired | string | The 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. |
| connectionIdRequired | string | The supplier connection. |
Request Body
| Name | Type | Description |
|---|
| billingCurrency | string | null | ISO 4217. Only for a supplier with no Pleaxy account; one with an account sets its own currencies. |
| paymentCurrency | string | null | ISO 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
PUTBuyerTurns 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
| Name | Type | Description |
|---|
| regionIdRequired | string | The 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. |
| connectionIdRequired | string | The supplier connection. |
Request Body
| Name | Type | Description |
|---|
| enabledRequired | boolean | true 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
POSTBuyerRuns 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
| Name | Type | Description |
|---|
| regionIdRequired | string | The 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. |
| invoiceIdRequired | string | The 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
| Name | Type | Description |
|---|
| matchStatus | string | "matched" or "discrepancy" after running the match, or "unverified" for an older pending invoice with no real usage figure. |
| discrepancyReason | string | Set 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
POSTBuyerManual 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
| Name | Type | Description |
|---|
| regionIdRequired | string | The 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. |
| invoiceIdRequired | string | The 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
POSTBuyerAdvances 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
| Name | Type | Description |
|---|
| regionIdRequired | string | The 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. |
| invoiceIdRequired | string | The 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
POSTBuyerRejects 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
| Name | Type | Description |
|---|
| regionIdRequired | string | The 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. |
| invoiceIdRequired | string | The invoice identifier. |
Request Body
| Name | Type | Description |
|---|
| reasonCodeRequired | string | One of amount_incorrect, not_ordered_or_not_received, duplicate, missing_or_wrong_po, wrong_billing_entity, other. The supplier sees its label. |
| reasonRequired | string | The 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
POSTBuyerThe 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
| Name | Type | Description |
|---|
| regionIdRequired | string | The 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. |
| invoiceIdRequired | string | The invoice identifier. |
Request Body
| Name | Type | Description |
|---|
| stageIdRequired | string | Id of the pending step to reassign. It may sit in any group of the chain, not only the current one. |
| approverEmployeeKeyRequired | string | Employee key (Cognito sub) of the new approver. Must be a buyer member of this workspace, and not the acting administrator. |
| reason | string | Why 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
POSTBuyerRecords 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
| Name | Type | Description |
|---|
| regionIdRequired | string | The 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. |
| invoiceIdRequired | string | The invoice identifier. |
Request Body
| Name | Type | Description |
|---|
| methodRequired | string | How it was paid: ach, wire, check, card or other. |
| paymentDateRequired | string | YYYY-MM-DD the payment was sent. Not later than today in the buyer workspace's time zone, and not before the invoice date. |
| referenceRequired | string | The 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. |
| currency | string | Only for a payment in another currency than the amount due's: ISO 4217. Needs amount and rate. |
| amount | number | With currency: what was paid, in currency. The amount due × rate, within 0.5% or one minor unit. |
| rate | number | With currency: 1 {amount due's currency} = rate {currency}, the rate the payment was made at. |
| rateDate | string | With 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
DELETEBuyerWithdraws 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
| Name | Type | Description |
|---|
| regionIdRequired | string | The 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. |
| invoiceIdRequired | string | The invoice identifier. |
Request Body
| Name | Type | Description |
|---|
| confirmRequired | boolean | Must 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
POSTBuyerDownloads 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
| Name | Type | Description |
|---|
| regionIdRequired | string | The 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
GETBuyerDownloads 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
| Name | Type | Description |
|---|
| regionIdRequired | string | The 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. |
| exportIdRequired | string | X-Pleaxy-Export-Id from ExportInvoicesForPayment. |
Response Syntax 200 OK
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
DELETEBuyerTakes 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
| Name | Type | Description |
|---|
| regionIdRequired | string | The 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. |
| invoiceIdRequired | string | The invoice identifier. |
Request Body
| Name | Type | Description |
|---|
| confirmNotPaidRequired | boolean | Must 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
GETBothThe 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
| Name | Type | Description |
|---|
| regionIdRequired | string | The 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. |
| invoiceIdRequired | string | The invoice identifier. |
Response Syntax 200 OK
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
GETBuyerNot 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
| Name | Type | Description |
|---|
| regionIdRequired | string | The 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. |
| invoiceIdRequired | string | The 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
POSTBuyerNot 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
| Name | Type | Description |
|---|
| regionIdRequired | string | The 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. |
| invoiceIdRequired | string | The 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
POSTBuyerRetired. 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
| Name | Type | Description |
|---|
| regionIdRequired | string | The 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. |
| invoiceIdRequired | string | The invoice identifier. |
Response Syntax 410 Gone
{
"statusCode": 410,
"code": "SETTLE_REPLACED",
"message": "string"
}
Errors
- 410 Gone — Code SETTLE_REPLACED, always.