Skip to main content
The Monigo JavaScript SDK (@monigo/sdk) is a thin, zero-dependency wrapper around the Monigo REST API. It follows a Stripe-style resource pattern — each API resource group is a typed service object hanging off the client. It ships as dual ESM and CommonJS bundles and works natively in Node.js (≥ 18), browsers, Cloudflare Workers, Bun, and Deno.

Installation

npm install @monigo/sdk
Or with other package managers:
yarn add @monigo/sdk
pnpm add @monigo/sdk
bun add @monigo/sdk
Requires Node.js 18 or later for native fetch support. For Node.js 16, pass a fetch polyfill via the fetch option — see Client configuration below.

Quick start

import { MonigoClient, Aggregation, BillingPeriod, PricingModel } from '@monigo/sdk'

const client = new MonigoClient({ apiKey: process.env.MONIGO_API_KEY! })

// Create a customer
const customer = await client.customers.create({
  external_id: 'user_abc123',
  name: 'Acme Corp',
  email: 'billing@acme.com',
})

// Ingest a usage event
await client.events.ingest({
  events: [
    {
      event_name: 'api_call',
      customer_id: customer.id,
      idempotency_key: 'evt_20260201_001',
      timestamp: new Date(),
      properties: { endpoint: '/v1/data', region: 'us-east-1' },
    },
  ],
})

console.log(`Customer ${customer.id} created and event ingested`)
Use a test-mode API key (mk_test_...) during development. Test events are isolated from live data and won’t trigger real charges.

Client configuration

const client = new MonigoClient({
  apiKey: 'mk_live_...',              // required
  baseURL: 'https://api.monigo.co',   // optional — defaults to production
  timeout: 30_000,                     // optional — request timeout in ms
  fetch: customFetch,                  // optional — custom fetch implementation
})

Custom base URL

For self-hosted deployments or a local development server:
const client = new MonigoClient({
  apiKey: process.env.MONIGO_API_KEY!,
  baseURL: 'http://localhost:8000',
})

Polyfilling fetch (Node.js 16)

import fetch from 'node-fetch'

const client = new MonigoClient({
  apiKey: process.env.MONIGO_API_KEY!,
  fetch: fetch as unknown as typeof globalThis.fetch,
})

Testing

The fetch option makes the client fully testable without a real server:
const client = new MonigoClient({
  apiKey: 'test_key',
  fetch: (_url, _init) =>
    Promise.resolve(new Response(JSON.stringify({ customer: mockCustomer }))),
})

Idempotency

Every POST, PUT, and PATCH request to the Monigo API must include an Idempotency-Key header. The SDK handles this automatically — crypto.randomUUID() is called for each mutating request. To supply your own key (recommended for retryable operations), pass idempotencyKey in the optional options argument:
import { MonigoClient } from '@monigo/sdk'

// Auto-generated key (default — safe for fire-and-forget)
const customer = await client.customers.create({
  external_id: 'user_abc123',
  name: 'Acme Corp',
  email: 'billing@acme.com',
})

// Explicit key — use a stable ID from your own system so retries are safe
const customer = await client.customers.create(
  {
    external_id: 'user_abc123',
    name: 'Acme Corp',
    email: 'billing@acme.com',
  },
  { idempotencyKey: `req_${yourRequestId}` },
)
For critical mutations (subscription creation, invoice finalization) pass a key derived from your own request ID. Retrying with the same key is safe — the server will return the original response without applying the operation twice.

Error handling

All methods return a Promise. A 4xx or 5xx response throws a MonigoAPIError with a typed status code, message, and optional field-level details.
import { MonigoAPIError } from '@monigo/sdk'

try {
  const customer = await client.customers.get('cust_missing')
} catch (err) {
  if (err instanceof MonigoAPIError) {
    console.error(`API error ${err.statusCode}: ${err.message}`)
  }
}

Static type-narrowing guards

try {
  await client.subscriptions.create({ customer_id: 'c', plan_id: 'p' })
} catch (err) {
  if (MonigoAPIError.isConflict(err)) {
    // customer already has an active subscription of this plan type
  } else if (MonigoAPIError.isNotFound(err)) {
    // customer or plan not found
  } else if (MonigoAPIError.isRateLimited(err)) {
    // back off and retry
  } else {
    throw err
  }
}
GuardHTTP StatusWhen it fires
MonigoAPIError.isNotFound(err)404Resource does not exist
MonigoAPIError.isUnauthorized(err)401Invalid or missing API key
MonigoAPIError.isForbidden(err)403API key lacks required scope
MonigoAPIError.isConflict(err)409Duplicate resource
MonigoAPIError.isRateLimited(err)429Request rate limit exceeded
MonigoAPIError.isQuotaExceeded(err)402Organisation event quota exhausted
MonigoAPIError.isServerError(err)5xxUnexpected server-side error

Events

The client.events service handles usage event ingestion and event replay.

Ingest events

const response = await client.events.ingest({
  events: [
    {
      event_name: 'api_call',
      customer_id: 'cust_abc',
      idempotency_key: 'evt_20260201_001',
      timestamp: new Date(),
      properties: {
        endpoint: '/v1/predict',
        region: 'us-east-1',
        tokens: 1500,
      },
    },
  ],
})

console.log('ingested:', response.ingested)
console.log('duplicates:', response.duplicates)
The server deduplicates events by idempotency_key. A single call can include up to 1,000 events.
Always supply a stable idempotency_key so retries after a network error don’t double-count events. A good key is ${customer_id}_${event_type}_${request_id}.

Replay events

Replay reprocesses all events in a time window to correct rollups after an outage or metric definition change.
let job = await client.events.startReplay({
  from: new Date('2026-01-01'),
  to: new Date('2026-01-31'),
  event_name: 'api_call', // omit to replay all event names
})

// Poll until complete
while (job.status === 'pending' || job.status === 'processing') {
  await new Promise(r => setTimeout(r, 5_000))
  job = await client.events.getReplay(job.id)
  console.log(`replayed ${job.events_replayed} / ${job.events_total}`)
}

Customers

// Create
const customer = await client.customers.create({
  external_id: 'user_123',
  name: 'Acme Corp',
  email: 'billing@acme.com',
})

// List
const { customers, count } = await client.customers.list()
console.log(`${count} customers`)

// Get
const customer = await client.customers.get('cust_abc')

// Update
const updated = await client.customers.update('cust_abc', {
  name: 'Acme Corp Ltd',
})

// Delete
await client.customers.delete('cust_abc')
Set external_id to your own system’s user ID. This lets you look up customers by your existing identifier without storing Monigo’s UUID separately.

Metrics

A metric defines what gets counted and how raw event values are aggregated.
import { Aggregation } from '@monigo/sdk'

// Count events
const metric = await client.metrics.create({
  name: 'API Calls',
  event_name: 'api_call',
  aggregation: Aggregation.Count,
})

// Sum a numeric property
const metric = await client.metrics.create({
  name: 'Tokens Used',
  event_name: 'completion',
  aggregation: Aggregation.Sum,
  aggregation_property: 'tokens',
})
ConstantValueDescription
Aggregation.CountcountCount the number of matching events
Aggregation.SumsumSum a numeric property across events
Aggregation.MaxmaxMaximum value of a property
Aggregation.MinminimumMinimum value of a property
Aggregation.AverageaverageAverage value of a property
Aggregation.UniqueuniqueCount distinct values of a property

Plans

A plan combines billing period, currency, and one or more prices. Each price links a metric to a pricing model.
import { BillingPeriod, PricingModel } from '@monigo/sdk'

// Flat rate: ₦50 per API call
const plan = await client.plans.create({
  name: 'Starter',
  currency: 'NGN',
  billing_period: BillingPeriod.Monthly,
  prices: [
    {
      metric_id: 'metric_api_calls',
      model: PricingModel.Flat,
      unit_price: '50.000000',
    },
  ],
})

// Tiered pricing
const plan = await client.plans.create({
  name: 'Growth',
  currency: 'NGN',
  billing_period: BillingPeriod.Monthly,
  prices: [
    {
      metric_id: 'metric_api_calls',
      model: PricingModel.Tiered,
      tiers: [
        { up_to: 10_000, unit_amount: '5.00' },
        { up_to: 100_000, unit_amount: '3.00' },
        { up_to: null, unit_amount: '1.50' }, // null = infinity
      ],
    },
  ],
})

Pricing models

ConstantValueDescription
PricingModel.FlatflatFixed price per unit
PricingModel.TieredtieredGraduated tiers — each unit priced at its tier
PricingModel.VolumevolumeWhole volume priced at one tier
PricingModel.PackagepackagePrice per block of N units
PricingModel.OverageoverageBase allowance + per-unit above threshold
PricingModel.WeightedTieredweighted_tieredAverage price across graduated tiers

Plan types and billing periods

ConstantValueMeaning
PlanType.CollectioncollectionCharge customers
PlanType.PayoutpayoutPay out to vendors
BillingPeriod.DailydailyBilled every day
BillingPeriod.WeeklyweeklyBilled every week
BillingPeriod.MonthlymonthlyBilled every month
BillingPeriod.QuarterlyquarterlyBilled every quarter
BillingPeriod.AnnuallyannuallyBilled every year

Subscriptions

A subscription links a customer to a plan and defines the current billing period.
import { SubscriptionStatus } from '@monigo/sdk'

// Create
const sub = await client.subscriptions.create({
  customer_id: 'cust_abc',
  plan_id: 'plan_starter',
})

// List with filters
const { subscriptions } = await client.subscriptions.list({
  customer_id: 'cust_abc',
  status: SubscriptionStatus.Active,
})

// Get
const sub = await client.subscriptions.get('sub_abc')

// Pause / resume
await client.subscriptions.updateStatus('sub_abc', SubscriptionStatus.Paused)
await client.subscriptions.updateStatus('sub_abc', SubscriptionStatus.Active)

// Cancel
await client.subscriptions.delete('sub_abc')
Status constantValueMeaning
SubscriptionStatus.ActiveactiveBilling is running
SubscriptionStatus.PausedpausedBilling paused; events still accepted
SubscriptionStatus.CanceledcanceledSubscription ended
A customer can hold at most one active subscription per plan type. A second active collection or payout subscription returns a 409 — catch it with MonigoAPIError.isConflict(err).

Payout accounts

Payout accounts are bank or mobile-money accounts scoped to a customer. All methods take customerId as the first argument.
import { PayoutMethod } from '@monigo/sdk'

// Bank transfer
const account = await client.payoutAccounts.create('cust_abc', {
  account_name: 'John Driver',
  payout_method: PayoutMethod.BankTransfer,
  bank_name: 'First Bank Nigeria',
  bank_code: '011',
  account_number: '3001234567',
  currency: 'NGN',
  is_default: true,
})

// Mobile money
const account = await client.payoutAccounts.create('cust_abc', {
  account_name: 'John Driver',
  payout_method: PayoutMethod.MobileMoney,
  mobile_money_number: '+2348012345678',
  currency: 'NGN',
})

// List / get / update / delete
const { payout_accounts } = await client.payoutAccounts.list('cust_abc')
const acct = await client.payoutAccounts.get('cust_abc', 'acct_xyz')
await client.payoutAccounts.update('cust_abc', 'acct_xyz', { account_name: 'Jane Driver' })
await client.payoutAccounts.delete('cust_abc', 'acct_xyz')
Method constantValue
PayoutMethod.BankTransferbank_transfer
PayoutMethod.MobileMoneymobile_money

Invoices

Invoices are generated from subscriptions and contain line items derived from the customer’s usage in the billing period.
import { InvoiceStatus } from '@monigo/sdk'

// Generate a draft invoice
const invoice = await client.invoices.generate('sub_abc')
console.log(`Draft invoice ${invoice.id} — total: ${invoice.total} ${invoice.currency}`)

for (const item of invoice.line_items ?? []) {
  console.log(`  ${item.description}: qty ${item.quantity} × ${item.unit_price} = ${item.amount}`)
}

// List with filters
const { invoices } = await client.invoices.list({
  customer_id: 'cust_abc',
  status: InvoiceStatus.Draft,
})

// Get / finalize / void
const invoice = await client.invoices.get('inv_abc')
const finalized = await client.invoices.finalize('inv_abc')
const voided = await client.invoices.void('inv_abc')
Status constantValueMeaning
InvoiceStatus.DraftdraftNot yet finalized; amounts may change
InvoiceStatus.FinalizedfinalizedLocked and ready for payment
InvoiceStatus.PaidpaidPayment collected
InvoiceStatus.VoidvoidCancelled
All monetary amounts (subtotal, total, unit_price) are returned as decimal strings (e.g. "1500.00") to preserve precision across currencies.

Usage

Query aggregated usage rollups to see how much a customer has consumed in a period.
// All usage for the current period
const result = await client.usage.query()

// Filter by customer and metric
const result = await client.usage.query({
  customer_id: 'cust_abc',
  metric_id: 'metric_api_calls',
})

// Custom time window — accepts Date objects or ISO strings
const result = await client.usage.query({
  customer_id: 'cust_abc',
  from: new Date('2026-01-01'),
  to: new Date('2026-01-31'),
})

for (const rollup of result.rollups) {
  console.log(
    `customer ${rollup.customer_id}${rollup.aggregation}: ${rollup.value}`,
    `(period: ${rollup.period_start}${rollup.period_end})`
  )
}

Portal tokens

Portal tokens grant an end-customer read-only access to their invoices, payout slips, subscriptions, and payout accounts in the Monigo hosted portal. The portal_url on the returned token is what you share with your customer — embed it in an email, redirect the browser, or open it inside an iframe.
const token = await client.portalTokens.create({
  customer_external_id: 'usr_abc123',
  label: 'Invoice portal link',
})

// Share with your customer
sendEmail(customer.email, { link: token.portal_url })
Set expires_at (ISO 8601) for a time-limited link:
const expiry = new Date()
expiry.setDate(expiry.getDate() + 30)

const token = await client.portalTokens.create({
  customer_external_id: 'usr_abc123',
  label: '30-day invoice link',
  expires_at: expiry.toISOString(),
})

List tokens for a customer

const { tokens, count } = await client.portalTokens.list('usr_abc123')

for (const tok of tokens) {
  console.log(`${tok.id}${tok.label} (expires: ${tok.expires_at ?? 'never'})`)
}
customerId accepts either a Monigo UUID or the customer’s external_id.

Revoke a token

await client.portalTokens.revoke(token.id)
Revocation is immediate. Any customer holding the corresponding URL will receive a 401 on their next request.
Portal tokens are opaque 64-character hex strings stored in the database — not JWTs — so they can be instantly revoked without waiting for an expiry timestamp.

Example programs

The SDK ships with six runnable example programs under examples/:
ProgramWhat it demonstrates
quickstartEnd-to-end: customer → metric → plan → subscription → ingest
meteringHigh-volume batch ingest with idempotency and rate-limit retry
billingGenerate, inspect, and finalize an invoice
payoutsRegister payout accounts and trigger event replay
usage-reportQuery rollups and print a terminal usage table
portal-tokensCreate, list, and revoke customer portal access links
Run any example:
cd js-sdk/examples
npm install
MONIGO_API_KEY=mk_test_... npm run portal-tokens
Each example reads MONIGO_API_KEY from the environment and accepts an optional MONIGO_BASE_URL to point at a local server.

Testing

The SDK ships 98 unit tests with zero external dependencies. Each test uses a mock fetch implementation injected via the fetch option:
cd js-sdk
npm run test:run
To test against a local Monigo server:
const client = new MonigoClient({
  apiKey: 'mk_test_...',
  baseURL: 'http://localhost:8000',
})