Sdks

TypeScript SDK

Complete guide to using the PimaBill TypeScript SDK for Node.js and browser environments.

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

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 ClassHTTP StatusDescription
PimaBill.BadRequestError400Request body or parameters are invalid
PimaBill.UnauthorizedError401Missing or invalid credentials
PimaBill.NotFoundError404The resource does not exist
PimaBill.ConflictError409Conflicts with the current state (e.g. duplicate email)
PimaBillTimeoutError-Request exceeded the configured timeout
PimaBillErrorAnyBase 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

Copyright © 2026