TriportRPC

Create an invoice

POSThttps://api.triport.io/v1/billing/invoices

Open a single-rail payment invoice for the signed-in console user — one invoice maps to exactly one channel, one rail, and one deposit address.

— (rail-dependent: Solana, Ethereum, Polygon, BSC, or Tron)— (capped by per-user pending-invoice limit)

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.

channelstringrequired
Payment channel. One of crypto-onchain (customer sends funds to the deposit address) or crypto-inapp (in-app wallet transaction built/relayed via the /inapp/* endpoints).
railstringrequired
The payment rail. One of sol-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_actionobjectrequired
What the payment buys (see below).
typestringrequired
One of subscription_purchase, subscription_renew, topup.
planstringrequired
Plan id — e.g. starter, growth, scale.
monthsintegerrequired
Billing period in months — e.g. 1, 3, 6, 12.
amount_microintegeroptional
Charge amount in micro-USD (1 USD = 1,000,000). Omit or send 0 to let the backend resolve the price from bill_action via the pricing resolver.
descriptionstringoptional
Free-text label stored on the invoice.
ttl_secondsintegeroptional
Lifetime in seconds before the invoice expires. Backend default applies when omitted/0.
client_request_idstringoptional
Idempotency key. Reuse the same value to safely retry a create without producing duplicate invoices. Generate a UUID v4.
fx_rate_micro_per_atomicintegeroptional
FX rate (micro-USD per smallest token unit) for rails that price in a non-USD token. Required for rails where fx_rate_required is true in the rails list (e.g. sol-native); rejected for stablecoin rails that do not accept it.

Response

201 Created:

idstring
Invoice id. Use it for the detail, cancel, pay-from-balance, in-app, and SSE endpoints.
user_idstring
Owner of the invoice (resolved from the session).
amount_microinteger
Final charge amount in micro-USD. Resolved by the backend when the request omitted amount_micro.
amount_usdstring
amount_micro formatted as a decimal USD string, e.g. "29.00".
statusstring
Invoice status — pending at creation. Later: paid, expired, cancelled.
descriptionstring
The label you supplied (may be empty).
channelstring
The channel the invoice was created on.
railstring
The rail the invoice was created on.
bill_actionobject
Echo of the submitted bill_action.
client_request_idstring
Echo of the idempotency key, if supplied.
fx_rate_micro_per_atomicinteger
Echo of the FX rate, when applicable to the rail.
reference_pubkeystring
Reference key used to match an on-chain payment to this invoice, when applicable.
created_atstring (ISO-8601)
Creation timestamp.
expires_atstring (ISO-8601)
When the invoice expires if unpaid.
paid_atstring (ISO-8601)
Settlement timestamp; present once status is paid.
addressobject
The single deposit address for this invoice (see below).
qr_uristring
Wallet payment URI (e.g. a solana: / EIP-681 link) encoding the address and amount; render it as a QR code.
payments_received_microinteger
Total received against this invoice so far, in micro-USD. 0 at creation.

Errors

Errors use the shared {"error": "<code>"} envelope.

CodeMeaningWhen it happens
auth required401No valid session cookie on the request.
bad json: …400The request body is not valid JSON.
channel and rail required400channel or rail was empty/missing.
bill_action required400bill_action was missing.
invalid channel / invalid rail / unknown rail400The channel or rail value is not recognised.
incompatible channel400The rail does not support the requested channel (unsupported rail/channel combination).
unknown plan400bill_action.plan is not a known plan.
amount too low400The resolved amount is below the rail minimum.
fx rate required400The rail requires fx_rate_micro_per_atomic and it was missing.
fx rate not applicable400An FX rate was supplied for a rail that does not accept one.
max pending exceeded429The user already has too many open pending invoices.
service disabled / session resolver not configured503Billing 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 from channels / supports_inapp / fx_rate_required.
  • Let the backend price it. Omit amount_micro (or send 0) to have the pricing resolver compute the charge from bill_action. Pass an explicit amount_micro only 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 as sol-native require fx_rate_micro_per_atomic. The rails list's fx_rate_required flag tells you which is which — supplying the wrong thing yields a 400.
  • In-app channel. For channel: "crypto-inapp", after creating the invoice build and relay the wallet transaction via POST /v1/billing/invoices/{id}/inapp/build and .../inapp/submit.
  • Track settlement. Subscribe to GET /v1/billing/invoices/{id}/events (SSE) for invoice_paid / invoice_progress / invoice_expired_sweep updates, or settle immediately from the user's balance with POST /v1/billing/invoices/{id}/pay-from-balance.