Create an invoice
https://api.triport.io/v1/billing/invoicesOpen a single-rail payment invoice for the signed-in console user — one invoice maps to exactly one channel, one rail, and one deposit address.
Creates a new payment invoice in the Triport console billing flow. The billing
surface uses a per-rail model: each invoice is bound to exactly one
channel, one rail, and one freshly derived deposit address. To offer the
customer a choice of payment methods, create one invoice per method.
The invoice is created in pending status with an expiry timestamp. The
response includes the deposit address, a wallet-ready qr_uri, and the
running payments_received_micro total (0 at creation). Watch the invoice
through to settlement over the SSE stream at
GET /v1/billing/invoices/{id}/events, or pay it from the user's internal
balance with POST /v1/billing/invoices/{id}/pay-from-balance.
This endpoint requires an authenticated console session — anonymous invoices are
rejected, because a per-rail invoice drives subscription activation and must be
attributed to a user. The set of valid rail values and their
channel compatibility comes from GET /v1/billing/rails (unauthenticated);
build your rail selector from that list so you never submit an unsupported
combination.
Parameters
Send a JSON request body. No path or query parameters.
channelstringrequiredcrypto-onchain (customer sends funds to the deposit address) or crypto-inapp (in-app wallet transaction built/relayed via the /inapp/* endpoints).railstringrequiredsol-native, sol-spl-usdc, sol-spl-usdt, tron-usdt, tron-usdc, eth-usdt, eth-usdc, bsc-usdt, bsc-usdc, polygon-usdc, polygon-usdt. Must be compatible with channel.bill_actionobjectrequiredtypestringrequiredsubscription_purchase, subscription_renew, topup.planstringrequiredstarter, growth, scale.monthsintegerrequired1, 3, 6, 12.amount_microintegeroptional0 to let the backend resolve the price from bill_action via the pricing resolver.descriptionstringoptionalttl_secondsintegeroptional0.client_request_idstringoptionalfx_rate_micro_per_atomicintegeroptionalfx_rate_required is true in the rails list (e.g. sol-native); rejected for stablecoin rails that do not accept it.Response
201 Created:
idstringuser_idstringamount_microintegeramount_micro.amount_usdstringamount_micro formatted as a decimal USD string, e.g. "29.00".statusstringpending at creation. Later: paid, expired, cancelled.descriptionstringchannelstringrailstringbill_actionobjectbill_action.client_request_idstringfx_rate_micro_per_atomicintegerreference_pubkeystringcreated_atstring (ISO-8601)expires_atstring (ISO-8601)paid_atstring (ISO-8601)status is paid.addressobjectqr_uristringsolana: / EIP-681 link) encoding the address and amount; render it as a QR code.payments_received_microinteger0 at creation.Errors
Errors use the shared {"error": "<code>"} envelope.
| Code | Meaning | When it happens |
|---|---|---|
auth required | 401 | No valid session cookie on the request. |
bad json: … | 400 | The request body is not valid JSON. |
channel and rail required | 400 | channel or rail was empty/missing. |
bill_action required | 400 | bill_action was missing. |
invalid channel / invalid rail / unknown rail | 400 | The channel or rail value is not recognised. |
incompatible channel | 400 | The rail does not support the requested channel (unsupported rail/channel combination). |
unknown plan | 400 | bill_action.plan is not a known plan. |
amount too low | 400 | The resolved amount is below the rail minimum. |
fx rate required | 400 | The rail requires fx_rate_micro_per_atomic and it was missing. |
fx rate not applicable | 400 | An FX rate was supplied for a rail that does not accept one. |
max pending exceeded | 429 | The user already has too many open pending invoices. |
service disabled / session resolver not configured | 503 | Billing or session resolution is not currently wired on this deployment. |
See Errors for the shared error envelope and conventions.
Examples
JavaScript (fetch)
const res = await fetch("https://api.triport.io/v1/billing/invoices", {
method: "POST",
credentials: "include", // send the console session cookie
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
channel: "crypto-onchain",
rail: "sol-spl-usdc",
bill_action: { type: "subscription_purchase", plan: "starter", months: 1 },
description: "Starter plan — 1 month",
ttl_seconds: 1800,
client_request_id: crypto.randomUUID(),
}),
});
if (!res.ok) {
const { error } = await res.json();
throw new Error(error);
}
const invoice = await res.json();
console.log(invoice.address.address, invoice.qr_uri);TypeScript SDK (@triport/sdk)
import { Triport } from "@triport/sdk";
// Console client: authenticates with the browser session cookie.
const client = new Triport({ session: true });
const invoice = await client.billing.createInvoice({
channel: "crypto-onchain",
rail: "sol-spl-usdc",
billAction: { type: "subscription_purchase", plan: "starter", months: 1 },
description: "Starter plan — 1 month",
ttlSeconds: 1800,
clientRequestId: crypto.randomUUID(),
});
console.log(invoice.status, invoice.address?.address);Python (triport-sdk)
import os
import uuid
from triport import Triport
# Console client authenticates with the session cookie.
client = Triport(session_cookie=os.environ["TRIPORT_SESSION"])
invoice = client.billing.create_invoice(
channel="crypto-onchain",
rail="sol-spl-usdc",
bill_action={"type": "subscription_purchase", "plan": "starter", "months": 1},
description="Starter plan — 1 month",
ttl_seconds=1800,
client_request_id=str(uuid.uuid4()),
)
print(invoice["status"], invoice["address"]["address"])Notes
- One invoice = one rail. To present multiple payment options, create one
invoice per rail and let the customer pick. Discover valid rail/channel
combinations from
GET /v1/billing/rails(no auth required) and build your selector fromchannels/supports_inapp/fx_rate_required. - Let the backend price it. Omit
amount_micro(or send0) to have the pricing resolver compute the charge frombill_action. Pass an explicitamount_microonly when you must override. - Idempotency. Always set
client_request_id(a UUID v4) so a retried POST returns the same invoice instead of creating a duplicate. - FX rates. Stablecoin rails (
*-usdc,*-usdt) generally price 1:1 and do not take an FX rate; native-token rails such assol-nativerequirefx_rate_micro_per_atomic. The rails list'sfx_rate_requiredflag tells you which is which — supplying the wrong thing yields a400. - In-app channel. For
channel: "crypto-inapp", after creating the invoice build and relay the wallet transaction viaPOST /v1/billing/invoices/{id}/inapp/buildand.../inapp/submit. - Track settlement. Subscribe to
GET /v1/billing/invoices/{id}/events(SSE) forinvoice_paid/invoice_progress/invoice_expired_sweepupdates, or settle immediately from the user's balance withPOST /v1/billing/invoices/{id}/pay-from-balance.