Finance

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.

Secret key stays on your backend
Customers never hold an API key. Your backend mounts the @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 marked paid immediately on capture.
  • Net-N issuances (sourceType: "net30") — issued at checkout time with a future dueAt timestamp (N days out). Status starts as open and transitions to paid once 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 carries periodStart and periodEnd so buyers can see exactly which billing period each charge covers.

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:

  1. Your backend authenticates the buyer (Clerk, NextAuth, session cookie, etc.) and resolves their internal user id.
  2. The proxy calls GET /api/v1/customers/by-external/:externalId with your secret key to resolve the Throttle customerId.
  3. All reads are forwarded as GET /api/v1/customers/:customerId/invoices so the buyer only ever sees their own invoices.
app/api/throttle/invoices/[...path]/route.ts
// 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.

Paginated response envelope
// 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; the amountRefunded field reflects the full invoice total.
  • Partial refund — status becomes partially_refunded; the amountRefunded field 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.

REST routes quick reference

List invoices
# 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"
Download PDF
# 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.

JSON response
{
  "data": {
    "url": "https://s3.us-east-1.amazonaws.com/…signed-url"
  }
}

Pages in this section

  • React Package @usethrottle/invoices hooks, provider, and server proxy reference.