Invoices
An invoice (inv_…) is a statement for one billing cycle. Most invoices are created
automatically by the billing engine on each renewal; you can also create one-off draft
invoices via the API.
Invoice status
Section titled “Invoice status”| Status | Meaning |
|---|---|
draft | Editable, not yet finalized. |
open | Finalized and awaiting payment from the wallet. |
paid | Settled — the wallet was debited. |
void | Canceled; never to be paid. |
The automatic path
Section titled “The automatic path”On each cycle the worker generates an open invoice and attempts to debit the
wallet:
- Wallet covers it → invoice
paid,invoice.paidevent/webhook fires. - Wallet short → invoice stays
open, the subscription goespast_due, andinvoice.payment_failedfires. Trile retries after a top-up.
You don’t call anything for this — just listen for the events.
Listing & retrieving
Section titled “Listing & retrieving”# List, filtered by status (cursor paginated)curl "$TRILE_API/v1/invoices?status=paid&limit=50" -H "x-api-key: $TRILE_KEY"
# Retrieve onecurl "$TRILE_API/v1/invoices/inv_01ARZ3..." -H "x-api-key: $TRILE_KEY"
# Timeline of what happened to an invoice (bare array, oldest first)curl "$TRILE_API/v1/invoices/inv_01ARZ3.../events" -H "x-api-key: $TRILE_KEY"import { Configuration, InvoicesApi } from '@trilehq/sdk'
const invoices = new InvoicesApi(new Configuration({ apiKey: process.env.TRILE_KEY }))
// List, filtered by status (cursor paginated)const { data: page } = await invoices.listInvoices({ status: 'paid', limit: '50' })// page.items, page.nextCursor
// Retrieve oneconst { data: invoice } = await invoices.getInvoice({ id: 'inv_01ARZ3...' })
// Timeline of what happened to an invoice (oldest first)const { data: timeline } = await invoices.listInvoiceEvents({ id: 'inv_01ARZ3...' })One-off / manual invoices
Section titled “One-off / manual invoices”You can create a draft invoice scoped to a subscription, then finalize and send it:
# 1. Create a draftcurl -X POST "$TRILE_API/v1/invoices" \ -H "x-api-key: $TRILE_KEY" -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "subscriptionId": "sub_01ARZ3...", "...": "line items" }'
# 2. Finalize & email it to the customer (draft → open)curl -X POST "$TRILE_API/v1/invoices/inv_01ARZ3.../send" -H "x-api-key: $TRILE_KEY"// 1. Create a draftconst { data: draft } = await invoices.createInvoice({ createInvoice: { subscriptionId: 'sub_01ARZ3...', priceId: 'price_01ARZ3...', amountPaisa: '49900', description: 'Extra seat — July', periodStart: new Date('2026-07-01T00:00:00Z'), periodEnd: new Date('2026-08-01T00:00:00Z'), }, idempotencyKey: crypto.randomUUID(),})
// 2. Finalize & email it to the customer (draft → open)await invoices.sendInvoice({ id: draft.id, idempotencyKey: crypto.randomUUID() })PATCH /v1/invoices/:id (invoices.updateInvoice) transitions status for the editable cases
(e.g. void). An invoice that’s already finalized returns invoice_not_editable for edits that
don’t apply.
curl -X PATCH "$TRILE_API/v1/invoices/inv_01ARZ3..." \ -H "x-api-key: $TRILE_KEY" -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "status": "void" }'await invoices.updateInvoice({ id: 'inv_01ARZ3...', updateInvoice: { status: 'void' }, idempotencyKey: crypto.randomUUID(),})