Billing & FinOps

Preview — pending review

This reference is generated from the platform API contract and is still under review. Operations, fields, and descriptions can change before publication.

Declare a bank transfer against an outstanding ledger entry

POST
/scopes/{scopeId}/billing/transactions/{transactionId}/actions/pay

Executes the "Pay now" flow: the tenant DECLARES that it has wired the money for an outstanding ledger entry to the published payee account (see GET .../billing/payment-instructions). The entry moves to pending-manual-approval and, when receiptFileName is supplied, gains a placeholder awaiting-review SWIFT/EFT receipt. A charge (an invoice charge or a commitment upfront — a debt) keeps its type; a payment row becomes wire-transfer-pending. A declared debt cannot be cancelled by the tenant (…/actions/cancel answers 409). This action settles nothing. No invoice is flipped PAID, no wallet grant is credited, no commitment is activated and no ledger entry is marked completed. The platform stores no cards and runs no payment processor, so there is no instant settlement to offer: a platform Finance operator verifies the transfer arrived against the bank feed (SLA: 1 business day) and settles it exactly once through the operator confirm action — or resolves it with the operator reject action. Settlement is the operator's, never the payer's. Validation: the addressed transaction must exist in the scope (else NOT_FOUND); an already-completed entry is CONFLICT; method must be wire — method=card is REFUSED with CONFLICT (it is retired PSP-era surface that used to settle self-service); a settling credit-applied or refund movement is not payable (CONFLICT) — the customer never wires those; caller needs contributor/admin/owner or billing role, otherwise FORBIDDEN. Guards are evaluated before any mutation, so a refusal leaves the ledger entry and its linked invoice unchanged. Async & state: Asynchronous — 202 returning the transaction in pending-manual-approval (type unchanged for a charge, wire-transfer-pending otherwise); terminal states completed (operator-confirmed against the bank feed) or failed (operator-rejected / tenant-cancelled), emitted as resource.updated SSE frames with kind billing.transaction patching status and the receipt verification field. Errors: 401 unauthenticated; 403 insufficient role; 404 unknown scope/transaction; 409 CONFLICT entry already settled, entry type not payable, or method=card; 422 invalid method; 429 repeated payment attempts rate-limited; 500 ledger failure. Idempotency/Concurrency: This operation does not use Idempotency-Key; it may be omitted and a sent one is ignored. The entry is locked FOR UPDATE so concurrent declarations serialize; re-declaring an entry already in pending-manual-approval replaces its placeholder receipt rather than posting a second entry.

Authorization

bearerToken
headerAuthorizationBearer <token>

An API token (pc_live_…) or a service-account key (sak_live_…), created in Identity & Access Management and sent as Authorization: Bearer <token>. Requests are rate limited per credential and per tenant: every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset, and a refused request gets 429 RATE_LIMITED with Retry-After.

Path Parameters

scopeId*string

Opaque ownership+authorization scope resolving to tenant + subscription + project.

transactionId*string

Transaction ledger entry identifier the payment action is anchored to.

Request Body

application/json
  1. body

Input for the "Pay now" flow — declare that a bank transfer was sent for an outstanding ledger entry, which a platform operator then reconciles. Settles nothing on its own.

invoiceId?stringDeprecated

Deprecated, optional and IGNORED — the entry settled is always the addressed transactionId, and the linked invoice (if any) is taken from that entry, never from this field. Omit it.

method*string

Must be wire: the entry becomes wire-transfer-pending / pending-manual-approval and waits for an operator to confirm the money arrived. card is REFUSED with CONFLICT — the platform stores no cards and runs no PSP, and a payer may not settle its own entry.

Value in"card""wire"
receiptFileName?|

Name of the uploaded SWIFT/EFT receipt file; a placeholder receipt is recorded when given.

Response Body

Deprecated success shape from the retired instant-settlement (card) path; no longer returned — a declared bank transfer answers 202.

application/json
  1. response

One entry in the billing transaction ledger. amount is in ten-thousandths of the currency unit, signed by effect on the outstanding balance: charges positive (you owe more), payments / credits / refunds negative.

id*string

Ledger entry identifier (e.g. tx-9f31ad).

type*TransactionType

Transaction ledger entry kind.

Value in"charge""payment""credit-applied""refund""wire-transfer-pending"
description*string

Human-readable ledger description (e.g. "Card payment — Visa •••• 4242").

amount*integer

Signed amount in ten-thousandths of the currency unit; charges positive, payments/credits/refunds negative.

method*string

Payment method label (e.g. "Visa •••• 4242", "Wire transfer — Barclays", "Invoice", "Credit balance").

status*TransactionStatus

Transaction settlement status.

Value in"completed""pending""pending-manual-approval""failed"
invoiceId*|

Linked invoice id, or null for account-level entries.

invoiceNumber*|

Linked invoice number (e.g. STC-2026-1039), or null.

reference*string

Processor/ledger reference (prefixed ch_, pay_, cr_, re_, wt_).

createdAt*string
Formatdate-time
failureReason*|

Processor decline reason for failed entries (e.g. "Issuer declined — insufficient funds (code 51)"); null otherwise.

commitmentId*|

The commitment whose upfront this charge pays, activated when the entry settles; null for every other entry, including a plain invoice charge. Present-as-null on purpose: an operator may confirm (or reject) a charge ONLY when it carries a commitment, so this is the field that tells a confirmable upfront apart from an invoice charge, for which those actions are always a conflict.

curl -X POST "https://example.com/scopes/string/billing/transactions/string/actions/pay" \  -H "Content-Type: application/json" \  -d '{    "method": "card"  }'
{  "id": "string",  "type": "charge",  "description": "string",  "amount": 0,  "method": "string",  "status": "completed",  "invoiceId": "string",  "invoiceNumber": "string",  "reference": "string",  "createdAt": "2019-08-24T14:15:22Z",  "failureReason": "string",  "commitmentId": "string"}