TypeScript SDK
The PimaBill TypeScript SDK provides type-safe access to the entire PimaBill API. It works in Node.js (18+) and other runtimes with fetch. Keep your API key on the server: never ship it to a browser.
Installation
npm install pimabill
yarn add pimabill
pnpm add pimabill
Client Initialization
All operations go through a single PimaBillClient. Request and response types live under the PimaBill namespace.
import { PimaBillClient } from 'pimabill';
const client = new PimaBillClient({ token: process.env.PIMABILL_API_KEY! });
The client talks to the hosted API (https://api.pimabill.com) unless told otherwise:
import { PimaBillClient, PimaBillEnvironment } from 'pimabill';
const sandbox = new PimaBillClient({ token: 'sk_test_...', environment: PimaBillEnvironment.Sandbox }); // https://sandbox.pimabill.com
const local = new PimaBillClient({ token: 'sk_live_...', environment: PimaBillEnvironment.Local }); // http://localhost:4000
const selfHosted = new PimaBillClient({ token: 'sk_live_...', baseUrl: 'https://billing.example.com' });
Methods return the response body directly. IDs in the path are passed positionally, followed by the request body or query.
Customers
Customers represent the people or businesses you bill. Every subscription, invoice, and payment is linked to a customer. externalId is your own ID for the customer and must be unique.
Create a Customer
const customer = await client.customers.create({
externalId: 'usr_12345',
email: 'john@example.com',
currency: 'NGN',
name: 'John Doe',
metadata: {
plan: 'enterprise',
},
});
console.log('Created customer:', customer.id);
Get a Customer
const customer = await client.customers.get('cus_x1y2z3a4b5c6');
console.log('Name:', customer.name);
console.log('Email:', customer.email);
Update a Customer
const updated = await client.customers.update('cus_x1y2z3a4b5c6', {
name: 'John A. Doe',
metadata: {
plan: 'enterprise',
notes: 'VIP customer',
},
});
List Customers
const customers = await client.customers.list({
page: 1,
limit: 20,
search: 'john',
sortBy: 'createdAt',
sortOrder: 'desc',
});
console.log('Total customers:', customers.meta.total);
for (const customer of customers.data) {
console.log(`${customer.name} (${customer.email})`);
}
Delete a Customer
await client.customers.delete('cus_x1y2z3a4b5c6');
console.log('Customer deleted');
Get Customer Subscriptions
const subscriptions = await client.customers.getSubscriptions('cus_x1y2z3a4b5c6');
for (const sub of subscriptions) {
console.log(`${sub.id}: ${sub.status} - Plan: ${sub.planId}`);
}
Get Customer Invoices
const invoices = await client.customers.getInvoices('cus_x1y2z3a4b5c6');
for (const invoice of invoices) {
console.log(`${invoice.invoiceNumber}: ${invoice.status} - ${invoice.currency} ${invoice.amount}`);
}
Plans
Plans define what you sell: the product tiers, billing frequency, and included features. Each plan has a unique code and a price per currency. Deactivated plans take no new subscriptions; existing ones are unaffected.
Create a Plan
const plan = await client.plans.create({
name: 'Pro Plan',
code: 'pro_monthly',
billingInterval: 'MONTHLY',
description: 'Professional plan with advanced features',
features: ['Unlimited projects', 'Priority support', 'Advanced analytics', 'API access'],
prices: [
{ currency: 'NGN', amount: 15000 },
{ currency: 'USD', amount: 29 },
],
});
console.log('Created plan:', plan.id);
Add a Price
const price = await client.plans.addPrice('pln_d7e8f9g0h1i2', { currency: 'KES', amount: 3500 });
List Plans
const plans = await client.plans.list({ isActive: true });
for (const plan of plans) {
console.log(`${plan.name}: ${plan.code} (${plan.billingInterval})`);
}
Update a Plan
const updated = await client.plans.update('pln_d7e8f9g0h1i2', {
name: 'Pro Plan v2',
features: ['Unlimited projects', 'Priority support', 'Advanced analytics', 'API access', 'Dedicated account manager'],
});
Deactivate a Plan
const deactivated = await client.plans.update('pln_d7e8f9g0h1i2', {
isActive: false,
});
Subscriptions
Subscriptions connect customers to plans and run the recurring billing: the current period, the status (ACTIVE, TRIALING, PAUSED, PAST_DUE, CANCELED) and renewals.
Create a Subscription
const subscription = await client.subscriptions.create({
customerId: 'cus_x1y2z3a4b5c6',
planId: 'pln_d7e8f9g0h1i2',
currency: 'NGN',
trialDays: 14,
metadata: {
source: 'website',
},
});
console.log('Subscription status:', subscription.status);
console.log('Current period ends:', subscription.currentPeriodEnd);
Get a Subscription
const subscription = await client.subscriptions.get('sub_j3k4l5m6n7o8');
console.log('Status:', subscription.status);
console.log('Plan:', subscription.planId);
console.log('Period:', subscription.currentPeriodStart, '-', subscription.currentPeriodEnd);
Pause and Resume
await client.subscriptions.pause('sub_j3k4l5m6n7o8');
await client.subscriptions.resume('sub_j3k4l5m6n7o8');
Cancel a Subscription
const canceled = await client.subscriptions.cancel('sub_j3k4l5m6n7o8', {
cancelAt: 'period_end', // or 'now'
});
console.log('Subscription ends at:', canceled.cancelAt);
Change Plan
const updated = await client.subscriptions.changePlan('sub_j3k4l5m6n7o8', {
newPlanId: 'pln_newplan123',
prorate: true,
});
console.log('Now on plan:', updated.planId);
Invoices
Invoices track what customers owe. They are created from subscriptions automatically, or by you for one-off charges. Statuses: DRAFT, PENDING, PAID, FAILED, CANCELED.
Create an Invoice
const invoice = await client.invoices.create({
customerId: 'cus_x1y2z3a4b5c6',
currency: 'NGN',
items: [
{ description: 'Pro Plan - Monthly', quantity: 1, unitAmount: 15000 },
{ description: 'Additional API calls (500)', quantity: 1, unitAmount: 2500 },
],
dueDate: '2025-02-28T00:00:00Z',
});
console.log('Invoice:', invoice.invoiceNumber);
console.log('Total:', invoice.currency, invoice.amount);
Get an Invoice
const invoice = await client.invoices.get('inv_p1q2r3s4t5u6');
console.log('Status:', invoice.status);
console.log('Total:', invoice.currency, invoice.amount);
List Invoices
const invoices = await client.invoices.list({
customerId: 'cus_x1y2z3a4b5c6',
status: 'PENDING',
page: 1,
limit: 10,
});
for (const invoice of invoices.data) {
console.log(`${invoice.invoiceNumber}: ${invoice.status} - ${invoice.currency} ${invoice.amount}`);
}
Finalize, Void, or Mark as Paid
await client.invoices.finalize('inv_p1q2r3s4t5u6');
await client.invoices.void('inv_p1q2r3s4t5u6');
await client.invoices.markPaid('inv_p1q2r3s4t5u6');
Get Invoice PDF
import { writeFile } from 'node:fs/promises';
const pdf = await client.invoices.getPdf('inv_p1q2r3s4t5u6');
await writeFile('invoice.pdf', Buffer.from(await pdf.arrayBuffer()));
Hosted Checkout
const checkout = await client.invoices.createCheckout('inv_p1q2r3s4t5u6');
console.log('Send the customer to:', checkout.checkoutUrl);
Payments
Payments are created when invoices are paid through a payment provider. You can list, retrieve and refund them.
Get a Payment
const payment = await client.payments.get('pay_v1w2x3y4z5a6');
console.log('Amount:', payment.currency, payment.amount);
console.log('Status:', payment.status);
List Payments
const payments = await client.payments.list({
status: 'SUCCEEDED',
page: 1,
limit: 20,
});
for (const payment of payments.data) {
console.log(`${payment.id}: ${payment.currency} ${payment.amount} - ${payment.status}`);
}
Refund a Payment
const refunded = await client.payments.refund('pay_v1w2x3y4z5a6', {
amount: 15000, // leave out for a full refund
reason: 'Customer requested plan downgrade',
});
console.log('Status:', refunded.status);
Payment Providers
Payment providers process payments on your behalf. Configure one or more and set priorities to control which is tried first.
List Payment Providers
const providers = await client.paymentProviders.list();
for (const provider of providers) {
console.log(`${provider.providerName}: priority ${provider.priority}, ${provider.isActive ? 'active' : 'inactive'}`);
}
Configure a Payment Provider
const provider = await client.paymentProviders.configure({
providerName: 'stripe',
credentials: {
secretKey: 'sk_live_...',
webhookSecret: 'whsec_...',
},
priority: 1,
});
const test = await client.paymentProviders.testConnection(provider.id);
console.log(test.success ? 'Connected' : test.message);
Collections, Payouts and Transfers
One-time money movement, separate from invoices. Each returns a MoneyMovementResponse, whose status moves from pending or processing to succeeded or failed. A stuck movement is one the provider could not confirm: it is re-checked, never resent. Pass an Idempotency-Key so a retried request cannot move money twice.
Collect a Payment
const collection = await client.collections.create({
amount: '50000',
currency: 'UGX',
payer: { type: 'mobile_money', msisdn: '256771234567' },
description: 'Order #1042',
reference: 'order-1042',
'Idempotency-Key': 'order-1042',
});
if (collection.paymentUrl) {
console.log('Send the payer to:', collection.paymentUrl);
}
// Later, or from your collection.succeeded webhook handler:
const latest = await client.collections.refresh(collection.id);
console.log(latest.status);
Send a Payout
const payout = await client.payouts.create({
amount: '120000',
currency: 'UGX',
payee: { type: 'mobile_money', msisdn: '256771234567', name: 'Jane Doe' },
description: 'Commission for March',
'Idempotency-Key': 'commission-2025-03-jane',
});
if (payout.status === 'requires_approval') {
console.log('Waiting for approval in the dashboard');
}
Move Money Between Wallets
const transfer = await client.transfers.create({
fromWalletId: 'wal_a1b2c3',
toWalletId: 'wal_d4e5f6',
amount: '20000',
});
List Payouts
const payouts = await client.payouts.list({ status: 'stuck', page: 1, limit: 20 });
for (const payout of payouts.data) {
console.log(`${payout.id}: ${payout.currency} ${payout.amount} (${payout.providerStatus})`);
}
Ledger
Every invoice, payment, wallet movement and money movement is mirrored into a double-entry ledger.
const accounts = await client.ledger.listAccounts({ currency: 'UGX' });
for (const account of accounts) {
console.log(`${account.code}: ${account.balance}`);
}
const health = await client.ledger.getHealth();
console.log(health.balanced ? 'Balanced' : health.checks.filter((c) => !c.ok));
Webhooks
PimaBill sends events (such as invoice.paid or collection.succeeded) to one URL per organisation, signed with your webhook secret.
await client.tenants.updateMe({ webhookUrl: 'https://api.yourapp.com/webhooks/pimabill' });
const { webhookSecret } = await client.tenants.getMe();
const logs = await client.tenants.getWebhookLogs({ direction: 'outbound', limit: 20 });
for (const log of logs.data) {
console.log(`${log.event} → ${log.statusCode ?? 'no response'} (${log.attemptCount} attempts)`);
}
See the Webhooks guide for verifying signatures.
Error Handling
Failed requests throw a PimaBillError with the HTTP status and the response body. Statuses the API documents have their own classes under PimaBill.
import { PimaBill, PimaBillClient, PimaBillError, PimaBillTimeoutError } from 'pimabill';
const client = new PimaBillClient({ token: process.env.PIMABILL_API_KEY! });
try {
await client.customers.get('cus_nonexistent');
} catch (error) {
if (error instanceof PimaBill.NotFoundError) {
console.error('Customer not found');
} else if (error instanceof PimaBill.UnauthorizedError) {
console.error('Check your API key');
} else if (error instanceof PimaBillTimeoutError) {
console.error('Request timed out');
} else if (error instanceof PimaBillError) {
console.error('API error:', error.statusCode, error.body);
} else {
throw error;
}
}
| Error Class | HTTP Status | Description |
|---|---|---|
PimaBill.BadRequestError | 400 | Request body or parameters are invalid |
PimaBill.UnauthorizedError | 401 | Missing or invalid credentials |
PimaBill.NotFoundError | 404 | The resource does not exist |
PimaBill.ConflictError | 409 | Conflicts with the current state (e.g. duplicate email) |
PimaBillTimeoutError | - | Request exceeded the configured timeout |
PimaBillError | Any | Base class for all API errors |
Pagination
Paginated lists return data and meta (total, page, limit, totalPages).
let page = 1;
let totalPages = 1;
do {
const response = await client.customers.list({ page, limit: 100 });
for (const customer of response.data) {
console.log(customer.name);
}
totalPages = response.meta.totalPages;
page++;
} while (page <= totalPages);
TypeScript Types
Every request and response type is exported under the PimaBill namespace.
import { PimaBill } from 'pimabill';
function describe(customer: PimaBill.CustomerResponse): string {
return `${customer.name ?? customer.email} (${customer.id})`;
}
const input: PimaBill.CreateCustomerDto = {
externalId: 'usr_1',
email: 'jane@example.com',
currency: 'USD',
};
const interval: PimaBill.CreatePlanDto.BillingInterval = 'MONTHLY';
const status: PimaBill.SubscriptionResponse.Status = 'ACTIVE';
Configuration Options
const client = new PimaBillClient({
token: 'sk_live_...', // required
baseUrl: 'https://billing.example.com', // default: https://api.pimabill.com
timeoutInSeconds: 60, // default 60
maxRetries: 2, // default 2
});
// Per request:
await client.customers.list({}, { timeoutInSeconds: 10, maxRetries: 0 });
Next Steps
- Explore the Python SDK if you work with Python.
- Read the Guides for in-depth usage patterns.
- Check the API Reference for all available endpoints.