Escrow Safe Trade is live: protect your next high-value deal Learn more
ProductsPayment links & checkoutM-Pesa, cards, invoices, split payoutsEscrow Safe TradeMilestone and release-based escrowWallets & P2PPersonal and business walletsChama, Lengo & DeniGroup savings, goals and IOUsBusiness toolkitAccounting, payroll, POS, ticketingBulk payments & payrollPay many people in one uploadSubscriptions & billingRecurring plans and renewalsRent, loans & donationsCollections and campaignsAll products
SolutionsMerchantsBuyersChama groupsBusiness solutionsIndustries
Pricing
DevelopersDeveloper overviewQuickstart, auth and sandboxREST APIsCheckout, invoices and requestsWebhooksSigned, real-time eventsWallet-as-a-ServiceVirtual sub-accountsDocumentationGuides and API reference
Security
CompanyAbout usCareersPartnersBlogHelp centreContact us
Log in Get started Talk to sales
Accept payments

Invoices & Recurring Billing

Issue itemised invoices with a hosted payment page, and switch on recurring renewals when a customer should be billed on a schedule.

Base URLhttps://api.paysasa.com/api/v1Authx-api-keyFormatJSON

Issue itemized invoices and share a hosted payment page. For recurring plans with customers, products, and lifecycle events, use the Subscriptions API. You can also enable recurring on a template invoice to generate a new invoice each billing cycle.

Hosted payment page: Every invoice includes payment_url (e.g. https://app.paysasa.com/pay/invoice/inv_…). Customers can pay with M-PESA without signing in, or with wallet after sign-in.

POST/invoices#

Create an invoice. Requires business_id when using API key auth. Amounts are in minor units (KES 500 = 50000).

ParameterTypeDescription
business_id *stringYour business ID (required with API key)
line_items *array[{ description, quantity, unit_price }] — unit_price in minor units
tostringPayer email, phone, or pay handle (optional)
due_datestringISO datetime when payment is due
statusstringdraft | pending (default pending)
metadataobjectYour subscription/plan IDs — returned on GET
external_referencestringe.g. sub_12345 from your billing system
recurringobjectSubscription template — see below
JavaScript
// Monthly recurring invoice
const res = await fetch("https://api.paysasa.com/api/v1/invoices", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-api-key": "apk_live_a1b2c3d4.7f9e0a...",
  },
  body: JSON.stringify({
    business_id: "biz_xxxxxxxxxxxxxxxx",
    to: "[email protected]",
    currency: "KES",
    status: "pending",
    due_date: new Date(Date.now() + 7 * 86400000).toISOString(),
    external_reference: "sub_plan_pro_001",
    metadata: { subscription_id: "sub_plan_pro_001", plan: "pro" },
    line_items: [
      {
        description: "Pro plan — monthly",
        quantity: 1,
        unit_price: 299900, // KES 2,999.00
      },
    ],
    recurring: {
      enabled: true,
      interval: "monthly",
      every: 1,
      end_at: null, // or ISO date to stop renewals
    },
    business_notes: "Thanks for subscribing to Pro.",
  }),
});
const { invoice, payment_url } = await res.json();
// Share payment_url with your customer or store it in your app
Response · JSON
{
  "invoice": {
    "id": "inv_01hx...",
    "invoice_number": "INV-ABC123-0001",
    "amount": 299900,
    "amount_paid": 0,
    "amount_remaining": 299900,
    "currency": "KES",
    "status": "pending",
    "payment_url": "https://app.paysasa.com/pay/invoice/inv_01hx...",
    "recurring": { "enabled": true, "interval": "monthly", "every": 1, "next_run": "..." },
    "metadata": { "subscription_id": "sub_plan_pro_001", "plan": "pro" }
  },
  "payment_url": "https://app.paysasa.com/pay/invoice/inv_01hx..."
}
GET/invoices#

List invoices for your business. With API key auth, pass business_id (required).

cURL
curl "https://api.paysasa.com/api/v1/invoices?business_id=biz_xxxxxxxxxxxxxxxx&status=pending" \
  -H "x-api-key: apk_live_a1b2c3d4.7f9e0a..."
GET/invoices/:id#

Retrieve invoice status (paid, pending, amount_remaining). Poll after customer pays on the hosted page.

POST/invoices/:id/send#

Email or SMS the invoice link to the customer (includes payment_url).

JavaScript
await fetch("https://api.paysasa.com/api/v1/invoices/inv_01hx.../send", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-api-key": "apk_live_a1b2c3d4.7f9e0a...",
  },
  body: JSON.stringify({
    email: "[email protected]",
    message: "Your subscription renewal is ready.",
  }),
});
GET/invoices/:id/public#

No authentication — used by the hosted payment page. Returns amount, line items, and status only.

POST/invoices/:id/checkout#

No authentication — start M-PESA payment from your own UI (returns hosted_url). The hosted page at /pay/invoice/:id uses this for the payer checkout experience.

JavaScript
const res = await fetch("https://api.paysasa.com/api/v1/invoices/inv_01hx.../checkout", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    amount: 299900,
    customer_phone: "254712345678",
  }),
});
const { hosted_url } = await res.json();
window.location.href = hosted_url;

Recurring invoices: Set recurring.enabled: true on a template invoice to generate a new invoice each cycle. For plan-based billing, use the Subscriptions API.