@monigo/sdk
v0.1.7
Published
Official Monigo JavaScript / TypeScript SDK — event ingestion, metering, billing, and usage for browser and Node.js
Maintainers
Readme
@monigo/sdk
The official JavaScript/TypeScript client library for the Monigo API. Zero external dependencies — works natively in Node.js (≥ 18), browsers, Cloudflare Workers, Bun, and Deno.
Installation
npm install @monigo/sdkOr with other package managers:
yarn add @monigo/sdk
pnpm add @monigo/sdk
bun add @monigo/sdkRequires Node.js 18 or later (for native fetch). For Node.js 16, see Polyfilling fetch.
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: '[email protected]',
})
// 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`)Tip: 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
Constructor options
const client = new MonigoClient({
apiKey: 'mk_live_...', // required — your Monigo API key
baseURL: 'https://api.monigo.co', // optional — defaults to production
timeout: 30_000, // optional — request timeout in ms (default: none)
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 does not include a built-in fetch. Pass a polyfill via the fetch option:
import fetch from 'node-fetch'
const client = new MonigoClient({
apiKey: process.env.MONIGO_API_KEY!,
fetch: fetch as unknown as typeof globalThis.fetch,
})Using in tests
The fetch option makes the client fully testable without a network:
const client = new MonigoClient({
apiKey: 'test_key',
fetch: (_url, _init) =>
Promise.resolve(new Response(JSON.stringify({ customer: mockCustomer }))),
})Authentication
Every request is authenticated with a Bearer token derived from your API key:
Authorization: Bearer mk_live_...API keys are scoped to an organisation and carry one of two prefixes:
| Prefix | Mode | Usage |
|--------|------|-------|
| mk_live_ | Live | Production traffic — charges and payouts are real |
| mk_test_ | Test | Development / CI — events, customers, and invoices are sandboxed |
Error handling
All methods return a Promise. On a 4xx or 5xx response the SDK throws a MonigoAPIError with a typed status code, message, and optional field-level details map.
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}`)
// { statusCode: 404, message: 'customer not found' }
}
}Static type-narrowing guards
Instead of catching and casting, use the static guards on MonigoAPIError:
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 if (err) {
throw err
}
}| Guard | HTTP Status | When it fires |
|-------|-------------|---------------|
| MonigoAPIError.isNotFound(err) | 404 | Resource does not exist |
| MonigoAPIError.isUnauthorized(err) | 401 | Invalid or missing API key |
| MonigoAPIError.isForbidden(err) | 403 | API key lacks required scope |
| MonigoAPIError.isConflict(err) | 409 | Duplicate resource (e.g. second active subscription of same plan type) |
| MonigoAPIError.isRateLimited(err) | 429 | Request rate limit exceeded |
| MonigoAPIError.isQuotaExceeded(err) | 402 | Organisation event quota exhausted |
| MonigoAPIError.isServerError(err) | 5xx | Unexpected server-side error |
Instance flags
MonigoAPIError also exposes boolean instance properties:
catch (err) {
if (err instanceof MonigoAPIError && err.isNotFound) {
// same as MonigoAPIError.isNotFound(err)
}
}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. Sending the same key twice is safe — the second call appears in response.duplicates and won't be counted again. A single ingest call can contain up to 1,000 events.
Tip: Always supply a stable
idempotency_key(e.g. a UUID or${customer_id}_${event_type}_${timestamp}) so retries after a network error don't double-count events.
Replay events
Replay reprocesses all events in a time window through the metering pipeline. Use this 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: '[email protected]',
})
// List
const { customers, count } = await client.customers.list()
console.log(`${count} customers`)
// Get by Monigo ID
const customer = await client.customers.get('cust_abc')
// Update
const updated = await client.customers.update('cust_abc', {
name: 'Acme Corp Ltd',
email: '[email protected]',
})
// Delete
await client.customers.delete('cust_abc')Tip: Set
external_idto your own system's user ID. This lets you look up a customer by your existing identifier without storing Monigo's UUID separately.
Metrics
A metric defines what gets counted (e.g. "API calls", "GB stored") 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 (e.g. tokens used)
const metric = await client.metrics.create({
name: 'Tokens Used',
event_name: 'completion',
aggregation: Aggregation.Sum,
aggregation_property: 'tokens',
})
// List / get / update / delete
const { metrics } = await client.metrics.list()
const metric = await client.metrics.get('metric_abc')
const updated = await client.metrics.update('metric_abc', { name: 'Renamed' })
await client.metrics.delete('metric_abc')Aggregation constants
| Constant | Value | Description |
|----------|-------|-------------|
| Aggregation.Count | count | Count the number of matching events |
| Aggregation.Sum | sum | Sum a numeric property across events |
| Aggregation.Max | max | Maximum value of a property |
| Aggregation.Min | minimum | Minimum value of a property |
| Aggregation.Average | average | Average value of a property |
| Aggregation.Unique | unique | Count 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, PlanType } 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
],
},
],
})
// List / get / update / delete
const { plans } = await client.plans.list()
const plan = await client.plans.get('plan_abc')
const updated = await client.plans.update('plan_abc', { name: 'Renamed' })
await client.plans.delete('plan_abc')Pricing model constants
| Constant | Value | Description |
|----------|-------|-------------|
| PricingModel.Flat | flat | Fixed price per unit |
| PricingModel.Tiered | tiered | Graduated tiers — each unit priced at its tier |
| PricingModel.Volume | volume | Whole volume priced at one tier |
| PricingModel.Package | package | Price per block of N units |
| PricingModel.Overage | overage | Base allowance + per-unit above threshold |
| PricingModel.WeightedTiered | weighted_tiered | Average price across graduated tiers |
Plan type and billing period constants
| Constant | Value | Meaning |
|----------|-------|---------|
| PlanType.Collection | collection | Charge customers (default) |
| PlanType.Payout | payout | Pay out to vendors |
| BillingPeriod.Daily | daily | Billed every day |
| BillingPeriod.Weekly | weekly | Billed every week |
| BillingPeriod.Monthly | monthly | Billed every month |
| BillingPeriod.Quarterly | quarterly | Billed every quarter |
| BillingPeriod.Annually | annually | Billed every year |
Subscriptions
A subscription links a customer to a plan and tracks 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 (soft-delete)
await client.subscriptions.delete('sub_abc')| Status constant | Value | Meaning |
|-----------------|-------|---------|
| SubscriptionStatus.Active | active | Billing is running |
| SubscriptionStatus.Paused | paused | Billing paused; events still accepted |
| SubscriptionStatus.Canceled | canceled | Subscription ended |
Warning: 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 that a customer can receive payouts to. All methods require a 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 all accounts for a customer
const { payout_accounts } = await client.payoutAccounts.list('cust_abc')
// Get / update / delete
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 constant | Value |
|-----------------|-------|
| PayoutMethod.BankTransfer | bank_transfer |
| PayoutMethod.MobileMoney | mobile_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 constant | Value | Meaning |
|-----------------|-------|---------|
| InvoiceStatus.Draft | draft | Not yet finalized; amounts may change |
| InvoiceStatus.Finalized | finalized | Locked and ready for payment |
| InvoiceStatus.Paid | paid | Payment collected |
| InvoiceStatus.Void | void | Cancelled |
Note: 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})`
)
}Example programs
The SDK ships five runnable example programs under examples/:
| Program | What it demonstrates |
|---------|----------------------|
| examples/quickstart | End-to-end: customer → metric → plan → subscription → ingest events |
| examples/metering | High-volume batch ingest with idempotency and rate-limit handling |
| examples/billing | Generate, inspect, and finalize an invoice |
| examples/payouts | Register payout accounts and trigger event replay |
| examples/usage-report | Query rollups and print a terminal usage table |
Run any example by setting MONIGO_API_KEY and executing from the examples/ directory:
cd js-sdk/examples
npm install
MONIGO_API_KEY=mk_test_... npm run quickstartTypeScript exports
All types and constants are exported from the root:
import {
MonigoClient,
MonigoAPIError,
// Constants
Aggregation,
PricingModel,
PlanType,
BillingPeriod,
SubscriptionStatus,
InvoiceStatus,
PayoutMethod,
// Types
type Customer,
type CreateCustomerRequest,
type UpdateCustomerRequest,
type ListCustomersResponse,
type Metric,
type CreateMetricRequest,
type Plan,
type CreatePlanRequest,
type CreatePriceRequest,
type PriceTier,
type Subscription,
type CreateSubscriptionRequest,
type ListSubscriptionsParams,
type PayoutAccount,
type CreatePayoutAccountRequest,
type Invoice,
type InvoiceLineItem,
type ListInvoicesParams,
type IngestEvent,
type IngestRequest,
type IngestResponse,
type EventReplayJob,
type UsageRollup,
type UsageQueryParams,
type UsageQueryResult,
} from '@monigo/sdk'Module format
The package ships dual-format bundles to work everywhere:
| Format | File | Used by |
|--------|------|---------|
| ESM | dist/monigo.js | Node.js ≥ 12 (with "type":"module"), bundlers, browsers |
| CJS | dist/monigo.cjs | Node.js CommonJS (require()) |
| Types | dist/index.d.ts | TypeScript consumers (both formats) |
The exports map in package.json routes each environment automatically — no manual import paths needed.
Browser / CDN usage
<script type="module">
import { MonigoClient } from 'https://esm.sh/@monigo/sdk'
const client = new MonigoClient({ apiKey: 'mk_test_...' })
const { customers } = await client.customers.list()
console.log(customers)
</script>Warning: Never embed a live API key in frontend code. For browser usage, proxy requests through your own backend or use a restricted publishable key if supported.
Development
Prerequisites
- Node.js ≥ 18
- npm
Setup
cd js-sdk
npm installBuild
npm run buildProduces dist/monigo.js (ESM), dist/monigo.cjs (CJS), and dist/index.d.ts.
Run tests
npm run test:run # run all 90 tests once
npm run test # watch mode
npm run test:coverage # with V8 coverage reportType check
npm run typecheckPublishing
The release workflow builds, tests, bumps the version, tags the commit, and publishes to npm in one command:
# Patch release (1.0.0 → 1.0.1) — bug fixes
npm run release:patch
# Minor release (1.0.0 → 1.1.0) — new features, backwards-compatible
npm run release:minor
# Major release (1.0.0 → 2.0.0) — breaking changes
npm run release:majorEach command runs:
npm run build— compile TypeScript and bundlenpm run test:run— run the full test suitenpm version <patch|minor|major>— bumppackage.jsonand create a git taggit push --follow-tags— push the commit and tag to the remotenpm publish --access public— publish to the npm registry
CI/CD
Add this GitHub Actions workflow to publish automatically on a tag push:
# .github/workflows/publish.yml
name: Publish to npm
on:
push:
tags:
- 'js-sdk/v*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
working-directory: js-sdk
- run: npm run build
working-directory: js-sdk
- run: npm run test:run
working-directory: js-sdk
- run: npm publish --access public
working-directory: js-sdk
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}Versioning
This SDK follows Semantic Versioning:
- Patch (
x.y.Z) — bug fixes, internal improvements, no API changes - Minor (
x.Y.0) — new methods or options, fully backwards-compatible - Major (
X.0.0) — breaking changes to the public API surface
Changelog
1.0.0 (2026-02-22)
- Initial release
- Full coverage of all 9 Monigo API resource groups: Events, Customers, Metrics, Plans, Subscriptions, Payout Accounts, Invoices, Usage
- Dual ESM + CJS build via Vite library mode
- 90 unit tests with zero external dependencies
- TypeScript types and constant objects for all enum values
License
MIT
