Customer Invoices
Every payment Throttle processes — a one-time card capture, a Net-N issuance, or a recurring subscription charge — lands in one unified, cursor-paginated invoice list. Your buyers see a single consistent view regardless of how they originally paid.
@usethrottle/invoices/server proxy which resolves the
authenticated buyer's identity and forwards only their own invoices. See React Package for the setup.
One list, three payment types
Throttle invoices are payment-source agnostic. The same GET /api/v1/invoices endpoint returns all three
payment types in a single paginated response, ordered by creation time (newest first by
default).
- Single-time card captures (
sourceType: "order") — generated when a buyer completes a one-time checkout session with a card. The invoice is markedpaidimmediately on capture. - Net-N issuances (
sourceType: "net30") — issued at checkout time with a futuredueAttimestamp (N days out). Status starts asopenand transitions topaidonce the merchant marks it paid or a payment is received. - Subscription charges (
sourceType: "subscription") — generated on the initial subscribe and on every renewal. The invoice carriesperiodStartandperiodEndso buyers can see exactly which billing period each charge covers.
A checkout that sells several subscriptions, or subscriptions and goods together, produces
one order invoice (sourceType: "order") listing
every line, each subscription line with its period label (for example "Oct 1 – Nov 1,
2026", or "Oct 1 – Nov 1, 2026 · Payment 1 of 3" on an installment plan), plus
shipping and tax; its
total equals the capture. No separate first-period subscription invoice is issued for it.
Renewals invoice as subscription charges. A single-plan checkout keeps its subscription
invoice.
Use the type query parameter to filter by source type
if your UI needs to show a subset.
Auth and trust model
The Throttle invoice API is a merchant-facing API: every call requires an sk_* secret key. Buyers cannot authenticate directly.
The recommended pattern is a thin backend proxy:
- Your backend authenticates the buyer (Clerk, NextAuth, session cookie, etc.) and resolves their internal user id.
-
The proxy calls
GET /api/v1/customers/by-external/:externalIdwith your secret key to resolve the ThrottlecustomerId. -
All reads are forwarded as
GET /api/v1/customers/:customerId/invoicesso the buyer only ever sees their own invoices.
// app/api/throttle/invoices/[...path]/route.ts
import { createInvoiceProxyHandler } from '@usethrottle/invoices/server';
import { auth } from '@/lib/auth';
const handler = createInvoiceProxyHandler({
apiKey: process.env.THROTTLE_SECRET_KEY!,
async getCustomerId(request) {
// Return your internal customer / user id.
// The proxy resolves it to a Throttle customerId via
// GET /api/v1/customers/by-external/:externalId.
const user = await auth();
return user?.id ?? null;
},
});
export { handler as GET };
The @usethrottle/invoices/server package ships this
proxy handler ready to mount in any framework that accepts a standard Request / Response interface (Next.js App Router, Hono, Remix,
etc.).
Pagination
Invoice list endpoints return a cursor-paginated envelope. Pass the returned meta.pagination.cursor as the cursor query parameter to fetch the next page.
When meta.pagination.hasMore is false there are no further pages.
// Cursor-paginated response envelope
{
"data": [
{
"id": "inv_abc123",
"number": "INV-sprinter-001",
"status": "paid",
"sourceType": "order",
"currency": "USD",
"subtotal": 4999,
"taxTotal": 400,
"shippingTotal": 699,
"discountTotal": 0,
"total": 6098,
"amountRefunded": 0,
"issuedAt": "2026-06-01T10:00:00Z",
"dueAt": null,
"periodStart": null,
"periodEnd": null,
"createdAt": "2026-06-01T10:00:00Z"
}
],
"meta": {
"pagination": {
"cursor": "eyJpZCI6Imlu...",
"hasMore": true
}
}
} Per-application branding
Every invoice PDF is rendered with the application's configured logo and brand color.
Set these on your application via the dashboard Settings → Branding panel or via PUT /api/v1/embed-config. The PDF renderer reads logoUrl and brandColor from application_env_settings at render time, so changes
apply to invoices downloaded after the update (not retroactively).
Refund behavior
When a payment is refunded via the dashboard or API, the corresponding invoice status transitions automatically:
- Full refund — status becomes
refunded; theamountRefundedfield reflects the full invoice total. - Partial refund — status becomes
partially_refunded; theamountRefundedfield shows the refunded portion.
The re-rendered PDF shows the refunded amount and updated status. Buyers who download after a refund automatically see the current state.
Net-N invoice status
An invoice raised on Net-N terms (sourceType: net30) starts open and follows the payment behind it:
- Paid — when the payment is captured (the merchant marks the invoice
paid), status becomes
paid. - Cancelled — when the payment is voided (the invoice is cancelled, the
order is cancelled or comped, or an edit leaves it owing nothing), status becomes
void.
The PDF re-renders on the next download, so it says Paid or Void instead of Due.
Amount due
A Net-N invoice's total is what the buyer owes on that
invoice — the amount of the Net-N payment behind it — not the order total. They
differ when part of the order was settled another way, such as a deposit taken by card at
checkout with the balance on Net-N terms. The invoice still lists every line, and subtotal, taxTotal, shippingTotal and discountTotal
are the order's. When the invoice bills less than the order, billingSnapshot.totals also carries orderTotal and the deductions that close the gap — amountPaid (already paid, e.g. the deposit) and compWaived (waived by a comp) — and the PDF prints them
under the order total, with the band reading Amount due.
Deposit receipts
The card receipt for a deposit (sourceType: order) on a
deposit + Net-N balance order has total equal to the deposit
that was paid, not the order total. The lines and the order's money rows stay itemised; billingSnapshot.totals carries orderTotal and balanceDue (the
balance billed on the Net-N invoice), and the PDF prints them under the order total with the
band reading Amount paid. Every other receipt is unchanged.
Invoice numbers
A Net-N invoice has one number: the invoice's number,
printed on its PDF and used as the PDF's filename. The Net-N payment carries the same
value in metadata.invoiceNumber, so the invoice emails, the
dashboard, receivables and the invoice.past_due webhook all
show it. It is formed from your Net-N invoice prefix (default INV), the application slug and the application's invoice
sequence, e.g. INV-acme-00042.
Invoices issued before 2026-10-04 were emailed under a separate payment number (e.g. INV-acme-workspace-4) while their PDF printed the invoice
number. Once such an invoice is brought onto the invoice number, its earlier number is kept as metadata.legacyInvoiceNumber on the payment (and the order),
sent as legacyInvoiceNumber on invoice.past_due, and printed as “Previous
reference” in the invoice emails and “Previous number” on the dashboard
invoice page, so either number can be matched. It is null or absent on every other invoice.
When tax is filed on invoice terms
For a card sale the tax document is filed when the money moves. For an invoice on net terms it is filed when the invoice is issued — the tax point for term billing is the moment the invoice is raised, not the moment it is paid.
So an invoice issued in January and paid in March belongs in January's filing. Throttle commits the document as soon as the invoice is authorized, while the payment is still unpaid, and you will see a committed tax document against an invoice that has not settled yet. That is correct, not a bug.
REST routes quick reference
# Merchant API: list all invoices (all source types) for the application
curl -G https://api.usethrottle.dev/api/v1/invoices \
-H "x-api-key: $THROTTLE_SECRET_KEY" \
--data-urlencode "limit=25" \
--data-urlencode "sort=desc"
# Merchant API: list invoices for a specific customer
curl https://api.usethrottle.dev/api/v1/customers/cus_xyz/invoices \
-H "x-api-key: $THROTTLE_SECRET_KEY" # Get a signed PDF download URL (JSON mode)
curl "https://api.usethrottle.dev/api/v1/invoices/inv_abc123/download?format=json" \
-H "x-api-key: $THROTTLE_SECRET_KEY"
# Redirect directly to PDF (default)
curl -L "https://api.usethrottle.dev/api/v1/invoices/inv_abc123/download" \
-H "x-api-key: $THROTTLE_SECRET_KEY" \
--output invoice.pdf
All routes require invoices:read scope on the API
key. The download route returns a 302 redirect to a
short-lived signed S3 URL by default; pass ?format=json to get the URL as a JSON response
instead.
{
"data": {
"url": "https://s3.us-east-1.amazonaws.com/…signed-url"
}
} Pages in this section
- React Package —
@usethrottle/invoiceshooks, provider, and server proxy reference.