Pay invoice from balance
https://api.triport.io/v1/billing/invoices/inv_8f2c1a/pay-from-balanceSettle a pending invoice instantly by debiting the signed-in user's prepaid balance.
Closes a pending invoice by drawing down the authenticated user's prepaid
balance instead of an on-chain or in-app crypto payment. The debit and the
invoice activation happen atomically inside a single database transaction, so
the call either fully settles the invoice or changes nothing.
On success the endpoint returns the new balance synchronously — there is no
need to poll the balance snapshot afterwards. The same
number is also pushed as a balance_debit event on
/v1/balance/events, so any other open UI tab updates
live.
The call is idempotent: POSTing again for an invoice that is already paid
returns 200 with the same response shape (the current state), not an error.
This lets the UI treat a double-click or a retried request exactly like the
first request.
Use this when the user has enough prepaid balance and wants to pay without broadcasting a blockchain transaction. If the balance is insufficient, locked, or the invoice is not yours / not pending, the call fails with a named error code (see Errors).
Parameters
Path parameters
idstringrequiredpending invoice owned by the authenticated user.Response
Response fields
| Field | Type | Description |
|---|---|---|
invoice_id | string | The invoice that was settled. |
status | string | Always "paid" on a successful (or idempotent) call. |
new_balance_micro | integer | The user's balance after the debit, in micro-units (1 unit = 1,000,000 micro). |
Errors
All errors use the shared error envelope { "error": "<code>" } — see
errors.md for the full contract. The error value is one of
the named codes below; surface it to the user verbatim.
| Code | HTTP | Meaning | When it happens |
|---|---|---|---|
auth required | 401 | Not authenticated | No valid session cookie on the request. |
invoice not found | 404 | Unknown invoice | No invoice exists for the given id. |
not_owner | 409 | Invoice belongs to another user | The invoice's owner is not the authenticated user. |
invoice_not_pending | 409 | Invoice not payable | Invoice is expired, cancelled, or lost a race to another settlement. |
invoice_is_topup | 409 | Wrong invoice kind | Top-up invoices credit the balance rather than debit it; they cannot be paid from balance. |
insufficient_balance | 409 | Balance too low | The balance is less than the invoice amount. |
balance_locked | 409 | Balance locked | The user's balance is temporarily locked and cannot be debited. |
already_debited | 409 | Concurrent debit | A parallel settlement already consumed this invoice's debit slot. |
A request for an invoice that is already
paidis not an error — it returns200with the standard response body (idempotent replay).
Examples
JavaScript (fetch)
const res = await fetch(
`https://api.triport.io/v1/billing/invoices/${invoiceId}/pay-from-balance`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include", // send the session cookie
},
);
if (!res.ok) {
const { error } = await res.json();
throw new Error(error); // e.g. "insufficient_balance"
}
const { invoice_id, status, new_balance_micro } = await res.json();
console.log(`${invoice_id} → ${status}, balance now ${new_balance_micro} micro`);TypeScript SDK (@triport/sdk)
import { Triport } from "@triport/sdk";
const triport = new Triport(); // uses the browser session cookie
const result = await triport.billing.payFromBalance(invoiceId);
// result: { invoice_id: string; status: "paid"; new_balance_micro: number }
console.log(result.new_balance_micro);Python (triport-sdk)
from triport import Triport
triport = Triport(session=session_cookie)
result = triport.billing.pay_from_balance(invoice_id)
# {"invoice_id": "inv_8f2c1a", "status": "paid", "new_balance_micro": 500000}
print(result["new_balance_micro"])Notes
- Live updates. On success a
balance_debitevent is broadcast on/v1/balance/eventswith the shape{ "kind": "balance_debit", "user_id", "delta_micro", "new_balance", "ref_invoice_id", "at" }, wheredelta_microis negative andref_invoice_idis the settled invoice. Aninvoice_paidevent is also emitted on that invoice's own event stream. - Idempotency. Retrying after a
paidresponse is safe — you receive the same200body. Build retries assuming at-most-once settlement even under concurrent requests; the duplicate is collapsed server-side. - Related endpoints. To pay on-chain or in-app instead, see the in-app transaction endpoints; to cancel an unpaid invoice, see cancel invoice; to inspect available funds, see the balance snapshot and ledger entries.