SDK Invoices
The InvoiceClient issues, tracks, and pays invoices for a Studio engagement. It is available at client.invoices on any authenticated StudioClient.
Two audiences, one client
Which methods you may call depends on the credential on the parent StudioClient:
| Method | Audience | Description |
|---|---|---|
| list(opts?) | both | List invoices, filter by org/project/status |
| get(id) | both | Fetch a single invoice |
| summary(orgId) | both | Outstanding / paid / overdue totals |
| create(data) | provider | Create a DRAFT invoice |
| update(id, patch) | provider | Edit a DRAFT invoice |
| send(id) | provider | Finalize & send via Stripe |
| cancel(id) | provider | Void a DRAFT or SENT invoice |
| payLink(id) | client | Stripe-hosted payment URL |
Provider methods require a ysk_prov_* key. Client methods accept an org member session token. Authorization is enforced server-side by SpiceDB.
Types
type StudioInvoiceStatus =
| 'DRAFT' | 'SENT' | 'PAID' | 'OVERDUE' | 'CANCELLED' | 'REFUNDED';
interface StudioLineItem {
description: string;
quantity: number;
unitPriceCents: number;
totalCents: number; // server-computed
}
interface StudioInvoice {
id: string;
number: string; // "STUDIO-2026-0042"
organizationId: string;
providerSlug: string; // "capswan-studio"
projectId?: string;
description: string;
lineItems: StudioLineItem[];
amountCents: number; // server-computed (sum of line totals)
currency: string; // "usd"
status: StudioInvoiceStatus;
hostedInvoiceUrl?: string; // Stripe pay page
pdfUrl?: string;
dueDate?: string;
issuedAt?: string;
paidAt?: string;
notes?: string;
createdAt: string;
}
interface InvoiceSummary {
currency: string;
outstandingCents: number; // SENT + OVERDUE
paidCents: number;
draftCents: number;
overdueCount: number;
nextDueDate?: string;
} Money is never computed on the client. On create/update you send only description, quantity, and unitPriceCents; totals are derived server-side and Stripe is the system of record.
Issue an invoice (provider)
import { StudioClient } from '@yapture/studio-sdk';
const studio = new StudioClient({
apiKey: process.env.YAPTURE_STUDIO_API_KEY, // ysk_prov_capswan-studio
});
const invoice = await studio.invoices.create({
organizationId: 'org_acme',
description: 'March retainer — product design & frontend',
lineItems: [
{ description: 'Design sprint', quantity: 1, unitPriceCents: 500000 },
{ description: 'Frontend dev (hours)', quantity: 40, unitPriceCents: 15000 },
],
dueDate: '2026-04-15',
});
// Finalize and email the client a Stripe-hosted pay page
const sent = await studio.invoices.send(invoice.id);
console.log(sent.number, sent.status); // STUDIO-2026-0042 SENT Show & pay invoices (client portal)
import { StudioClient } from '@yapture/studio-sdk';
// Constructed per-request with the signed-in org member's token
const studio = new StudioClient({ apiKey: session.accessToken });
const { data: invoices } = await studio.invoices.list({
organizationId: 'org_acme',
});
const summary = await studio.invoices.summary('org_acme');
console.log(`Outstanding: $${(summary.outstandingCents / 100).toFixed(2)}`);
// "Pay" — open the Stripe-hosted page; the portal never sees card data
const { hostedInvoiceUrl } = await studio.invoices.payLink(invoices[0].id);
window.open(hostedInvoiceUrl, '_blank'); Lifecycle
DRAFT ──update──▶ DRAFT
DRAFT ──send────▶ SENT ──(stripe paid)──▶ PAID
SENT ──(past dueDate)──▶ OVERDUE ──(paid)──▶ PAID
DRAFT|SENT ──cancel──▶ CANCELLED
PAID ──(admin refund)──▶ REFUNDED See the Invoicing guide for the full lifecycle and the Invoices API reference for raw endpoints.