The Monigo Go SDK (github.com/monigo-africa/go-monigo) is a thin, zero-dependency wrapper around the Monigo REST API. It follows the same resource-based structure as the REST API and uses functional options for configuration — familiar to anyone who has used the Stripe or Plaid Go SDKs.
Installation
go get github.com/monigo-africa/go-monigo
Requires Go 1.25 or later. No third-party dependencies — the SDK uses only the Go standard library.
Quick start
package main
import (
"context"
"fmt"
"log"
"os"
"time"
monigo "github.com/monigo-africa/go-monigo"
)
func main() {
client := monigo.New(os.Getenv("MONIGO_API_KEY"))
ctx := context.Background()
// Create a customer
customer, err := client.Customers.Create(ctx, monigo.CreateCustomerRequest{
ExternalID: "user_abc123",
Name: "Acme Corp",
Email: "billing@acme.com",
})
if err != nil {
log.Fatal(err)
}
// Ingest a usage event
_, err = client.Events.Ingest(ctx, monigo.IngestRequest{
Events: []monigo.IngestEvent{
{
EventName: "api_call",
CustomerID: customer.ID,
IdempotencyKey: "evt_unique_id_001",
Timestamp: time.Now(),
Properties: map[string]any{"endpoint": "/v1/data"},
},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Customer %s created and event ingested\n", customer.ID)
}
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
Default client
client := monigo.New(os.Getenv("MONIGO_API_KEY"))
The default client sends requests to https://api.monigo.co with a standard http.Client and no timeout. For production use, always set a timeout.
Custom HTTP client (recommended)
import "net/http"
httpClient := &http.Client{Timeout: 30 * time.Second}
client := monigo.New(
os.Getenv("MONIGO_API_KEY"),
monigo.WithHTTPClient(httpClient),
)
Custom base URL
For self-hosted deployments or a local development server:
client := monigo.New(
os.Getenv("MONIGO_API_KEY"),
monigo.WithBaseURL("https://api.yourcompany.com"),
)
Idempotency
Every POST, PUT, and PATCH request to the Monigo API must include an Idempotency-Key header. The SDK handles this automatically — a UUID v4 is generated for each request. To supply your own key (recommended for retryable operations), pass WithIdempotencyKey:
// Auto-generated key (default — safe for fire-and-forget)
customer, err := client.Customers.Create(ctx, monigo.CreateCustomerRequest{
ExternalID: "user_abc123",
Name: "Acme Corp",
Email: "billing@acme.com",
})
// Explicit key — use a stable ID from your own system so retries are safe
customer, err := client.Customers.Create(
ctx,
monigo.CreateCustomerRequest{
ExternalID: "user_abc123",
Name: "Acme Corp",
Email: "billing@acme.com",
},
monigo.WithIdempotencyKey("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 standard error. When the API responds with a 4xx or 5xx status, the error is an *APIError with a status code, message, and optional field-level details.
customer, err := client.Customers.Get(ctx, "cust_missing")
if err != nil {
var apiErr *monigo.APIError
if errors.As(err, &apiErr) {
fmt.Printf("API error %d: %s\n", apiErr.StatusCode, apiErr.Message)
}
return err
}
Sentinel helpers
Instead of inspecting status codes manually, use the provided helpers:
_, err := client.Subscriptions.Create(ctx, req)
switch {
case monigo.IsConflict(err):
// customer already has an active subscription of this plan type (collection or payout)
case monigo.IsNotFound(err):
// customer or plan not found
case monigo.IsRateLimited(err):
// back off and retry
case monigo.IsQuotaExceeded(err):
// org has exhausted its event quota
case err != nil:
// unexpected error
}
| Helper | HTTP Status | When it fires |
|---|
IsNotFound(err) | 404 | Resource does not exist |
IsUnauthorized(err) | 401 | Invalid or missing API key |
IsForbidden(err) | 403 | API key lacks the required scope |
IsConflict(err) | 409 | Duplicate resource (e.g. customer already has an active subscription of the same plan type) |
IsRateLimited(err) | 429 | Request rate limit exceeded |
IsQuotaExceeded(err) | 402 | Organisation event quota exhausted |
IsValidationError(err) | 400 | Request body failed validation |
Events
The client.Events service handles usage event ingestion and event replay.
Ingest events
resp, err := client.Events.Ingest(ctx, monigo.IngestRequest{
Events: []monigo.IngestEvent{
{
EventName: "api_call",
CustomerID: "cust_abc",
IdempotencyKey: "evt_20260201_001",
Timestamp: time.Now(),
Properties: map[string]any{
"endpoint": "/v1/predict",
"region": "us-east-1",
},
},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("ingested: %v, duplicates: %v\n", resp.Ingested, resp.Duplicates)
The server deduplicates events by idempotency_key. Sending the same key twice is safe — the second call will appear in resp.Duplicates and won’t be counted again.
A single Ingest call can contain up to 1,000 events.
Replay events
Replay reprocesses all events in a time window through the metering pipeline. This corrects rollups after an outage or after a metric definition change.
from := time.Now().Add(-24 * time.Hour)
to := time.Now()
eventName := "api_call" // pass nil to replay all event names
job, err := client.Events.StartReplay(ctx, from, to, &eventName)
if err != nil {
log.Fatal(err)
}
// Poll until complete
for job.Status == "pending" || job.Status == "processing" {
time.Sleep(5 * time.Second)
job, err = client.Events.GetReplay(ctx, job.ID)
if err != nil {
log.Fatal(err)
}
}
fmt.Printf("replayed %d / %d events\n", job.EventsReplayed, job.EventsTotal)
Customers
// Create
customer, err := client.Customers.Create(ctx, monigo.CreateCustomerRequest{
ExternalID: "user_123",
Name: "Acme Corp",
Email: "billing@acme.com",
})
// List
list, err := client.Customers.List(ctx)
fmt.Printf("%d customers\n", list.Count)
// Get
customer, err := client.Customers.Get(ctx, "cust_abc")
// Update
customer, err := client.Customers.Update(ctx, "cust_abc", monigo.UpdateCustomerRequest{
Name: "Acme Corp Ltd",
})
// Delete
err = client.Customers.Delete(ctx, "cust_abc")
Set ExternalID to your own system’s user ID. This makes it easy to look up a customer without storing Monigo’s UUID separately.
Metrics
A metric defines what gets counted (e.g. “API calls”, “GB stored”) and how the raw event values are aggregated.
metric, err := client.Metrics.Create(ctx, monigo.CreateMetricRequest{
Name: "API Calls",
EventName: "api_call",
Aggregation: monigo.AggregationCount,
})
// Sum a numeric property (e.g. bytes transferred)
metric, err := client.Metrics.Create(ctx, monigo.CreateMetricRequest{
Name: "Data Transferred",
EventName: "data_transfer",
Aggregation: monigo.AggregationSum,
AggregationProperty: "bytes",
})
| Constant | Value | Description |
|---|
AggregationCount | count | Count the number of matching events |
AggregationSum | sum | Sum a numeric property across events |
AggregationMax | max | Maximum value of a property |
AggregationMin | minimum | Minimum value of a property |
AggregationAverage | average | Average value of a property |
AggregationUnique | 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.
// Flat rate: $2.50 per API call
plan, err := client.Plans.Create(ctx, monigo.CreatePlanRequest{
Name: "Starter",
Currency: "USD",
BillingPeriod: monigo.BillingPeriodMonthly,
Prices: []monigo.CreatePriceRequest{
{
MetricID: "metric_api_calls",
Model: monigo.PricingModelFlat,
UnitPrice: "2.500000",
},
},
})
// Tiered pricing
plan, err := client.Plans.Create(ctx, monigo.CreatePlanRequest{
Name: "Growth",
Currency: "NGN",
BillingPeriod: monigo.BillingPeriodMonthly,
Prices: []monigo.CreatePriceRequest{
{
MetricID: "metric_api_calls",
Model: monigo.PricingModelTiered,
Tiers: []monigo.PriceTier{
{UpTo: ptr(int64(10_000)), UnitAmount: "5.00"},
{UpTo: ptr(int64(100_000)), UnitAmount: "3.00"},
{UpTo: nil, UnitAmount: "1.50"}, // nil = infinity
},
},
},
})
Pricing models
| Constant | Value | Description |
|---|
PricingModelFlat | flat | Fixed price per unit |
PricingModelTiered | tiered | Graduated tiers — each unit priced at its tier |
PricingModelVolume | volume | Whole volume priced at one tier |
PricingModelPackage | package | Price per block of N units |
PricingModelOverage | overage | Base allowance + per-unit above threshold |
PricingModelWeightedTiered | weighted_tiered | Average price across graduated tiers |
Plan types and billing periods
| Constant | Value |
|---|
PlanTypeCollection | collection — charge customers |
PlanTypePayout | payout — pay out to vendors |
BillingPeriodDaily | daily |
BillingPeriodWeekly | weekly |
BillingPeriodMonthly | monthly |
BillingPeriodQuarterly | quarterly |
BillingPeriodAnnually | annually |
Subscriptions
A subscription links a customer to a plan and defines the current billing period.
// Create
sub, err := client.Subscriptions.Create(ctx, monigo.CreateSubscriptionRequest{
CustomerID: "cust_abc",
PlanID: "plan_starter",
})
// List with filters
subs, err := client.Subscriptions.List(ctx, monigo.ListSubscriptionsParams{
CustomerID: "cust_abc",
Status: monigo.SubscriptionStatusActive,
})
// Get
sub, err := client.Subscriptions.Get(ctx, "sub_abc")
// Pause / resume / cancel
sub, err = client.Subscriptions.UpdateStatus(ctx, "sub_abc", monigo.SubscriptionStatusPaused)
sub, err = client.Subscriptions.UpdateStatus(ctx, "sub_abc", monigo.SubscriptionStatusActive)
// Cancel (soft-delete)
err = client.Subscriptions.Delete(ctx, "sub_abc")
| Status constant | Value | Meaning |
|---|
SubscriptionStatusActive | active | Billing is running |
SubscriptionStatusPaused | paused | Billing paused; events still accepted |
SubscriptionStatusCanceled | canceled | Subscription ended |
A customer can have at most one active subscription per plan type. A customer may hold one active collection subscription and one active payout subscription simultaneously, but attempting to create a second active subscription of the same type returns a 409 conflict — check with monigo.IsConflict(err).
Payout accounts
Payout accounts are bank or mobile-money accounts that a customer can receive payouts to. They are scoped to a customer.
// Add a bank account
acct, err := client.PayoutAccounts.Create(ctx, "cust_abc",
monigo.CreatePayoutAccountRequest{
AccountName: "John Driver",
PayoutMethod: monigo.PayoutMethodBankTransfer,
BankName: "First Bank Nigeria",
BankCode: "011",
AccountNumber: "3001234567",
Currency: "NGN",
IsDefault: true,
},
)
// Add a mobile money account
acct, err := client.PayoutAccounts.Create(ctx, "cust_abc",
monigo.CreatePayoutAccountRequest{
AccountName: "John Driver",
PayoutMethod: monigo.PayoutMethodMobileMoney,
MobileMoneyNumber: "+2348012345678",
Currency: "NGN",
},
)
// List all accounts for a customer
list, err := client.PayoutAccounts.List(ctx, "cust_abc")
// Get, update, delete
acct, err = client.PayoutAccounts.Get(ctx, "cust_abc", "acct_xyz")
acct, err = client.PayoutAccounts.Update(ctx, "cust_abc", "acct_xyz",
monigo.UpdatePayoutAccountRequest{AccountName: "Jane Driver"},
)
err = client.PayoutAccounts.Delete(ctx, "cust_abc", "acct_xyz")
| Method constant | Value |
|---|
PayoutMethodBankTransfer | bank_transfer |
PayoutMethodMobileMoney | mobile_money |
Invoices
Invoices are generated from subscriptions and contain line items derived from the customer’s usage in that billing period.
// Generate a draft invoice for a subscription
invoice, err := client.Invoices.Generate(ctx, "sub_abc")
fmt.Printf("Draft invoice %s — total: %s %s\n",
invoice.ID, invoice.Total, invoice.Currency)
for _, item := range invoice.LineItems {
fmt.Printf(" %s: qty %s × %s = %s\n",
item.Description, item.Quantity, item.UnitPrice, item.Amount)
}
// List invoices with filters
invoices, err := client.Invoices.List(ctx, monigo.ListInvoicesParams{
CustomerID: "cust_abc",
Status: monigo.InvoiceStatusDraft,
})
// Get a specific invoice
invoice, err = client.Invoices.Get(ctx, "inv_abc")
// Finalize — makes the invoice payable
invoice, err = client.Invoices.Finalize(ctx, "inv_abc")
// Void — cancels the invoice
invoice, err = client.Invoices.Void(ctx, "inv_abc")
| Status constant | Value | Meaning |
|---|
InvoiceStatusDraft | draft | Not yet finalized; amounts may change |
InvoiceStatusFinalized | finalized | Locked and ready for payment |
InvoiceStatusPaid | paid | Payment collected |
InvoiceStatusVoid | void | Cancelled |
All monetary amounts (Subtotal, VATAmount, Total, UnitPrice) 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
result, err := client.Usage.Query(ctx, monigo.UsageParams{})
// Filter by customer and metric
result, err := client.Usage.Query(ctx, monigo.UsageParams{
CustomerID: "cust_abc",
MetricID: "metric_api_calls",
})
// Custom time window
from := time.Now().AddDate(0, -1, 0)
to := time.Now()
result, err := client.Usage.Query(ctx, monigo.UsageParams{
CustomerID: "cust_abc",
From: &from,
To: &to,
})
for _, rollup := range result.Rollups {
fmt.Printf("customer %s — %s: %.2f (period: %s → %s)\n",
rollup.CustomerID, rollup.Aggregation, rollup.Value,
rollup.PeriodStart.Format("2006-01-02"),
rollup.PeriodEnd.Format("2006-01-02"),
)
}
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 PortalURL field of the returned token is what you share with your customer — embed it in an email, redirect the browser, or open it inside an iframe.
Create a portal link
tok, err := client.PortalTokens.Create(ctx, monigo.CreatePortalTokenRequest{
CustomerExternalID: "usr_abc123",
Label: "Invoice portal link",
})
if err != nil {
log.Fatal(err)
}
fmt.Println("Share this link:", tok.PortalURL)
Set ExpiresAt (RFC3339) for a time-limited link:
tok, err := client.PortalTokens.Create(ctx, monigo.CreatePortalTokenRequest{
CustomerExternalID: "usr_abc123",
Label: "30-day invoice link",
ExpiresAt: time.Now().Add(30 * 24 * time.Hour).Format(time.RFC3339),
})
List tokens for a customer
resp, err := client.PortalTokens.List(ctx, "usr_abc123")
if err != nil {
log.Fatal(err)
}
for _, tok := range resp.Tokens {
fmt.Printf("token %s — %s (expires: %v)\n", tok.ID, tok.Label, tok.ExpiresAt)
}
customerID accepts either a Monigo UUID or the customer’s ExternalID.
Revoke a token
if err := client.PortalTokens.Revoke(ctx, tok.ID); err != nil {
log.Fatal(err)
}
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.
Wallets
Customer wallets hold prepaid balances. They are the foundation of Prepaid Billing and can also be used for any credit/debit workflow. See the full Wallet Management guide for details.
// Get or create a wallet
wallet, err := client.Wallets.GetOrCreate(ctx, monigo.GetOrCreateWalletRequest{
CustomerID: "cust_abc",
Currency: "NGN",
})
// List all wallets (org inferred from API key)
resp, err := client.Wallets.List(ctx)
// List wallets filtered by customer
resp, err = client.Wallets.List(ctx, monigo.ListWalletsParams{CustomerID: "cust_abc"})
// Or use the dedicated customer endpoint
resp, err = client.Wallets.ListByCustomer(ctx, "cust_abc")
// Get a wallet (includes virtual accounts)
detail, err := client.Wallets.Get(ctx, "wallet_uuid")
fmt.Printf("Balance: %s, Virtual accounts: %d\n",
detail.Wallet.Balance, len(detail.VirtualAccounts))
// Credit (top up)
creditResp, err := client.Wallets.Credit(ctx, wallet.ID, monigo.CreditWalletRequest{
Amount: "5000.00",
Currency: "NGN",
Description: "Payment received",
EntryType: monigo.WalletEntryTypeDeposit,
ReferenceType: "payment",
ReferenceID: "pay_123",
IdempotencyKey: "topup_pay_123",
})
// Debit (charge)
debitResp, err := client.Wallets.Debit(ctx, wallet.ID, monigo.DebitWalletRequest{
Amount: "1200.00",
Currency: "NGN",
Description: "Usage charge",
EntryType: monigo.WalletEntryTypeUsage,
ReferenceType: "invoice",
ReferenceID: "inv_456",
IdempotencyKey: "debit_inv_456",
})
if monigo.IsQuotaExceeded(err) {
// 402 — insufficient balance
}
// Transaction history
txns, err := client.Wallets.ListTransactions(ctx, wallet.ID, monigo.ListTransactionsParams{
Limit: 25,
})
// Virtual accounts
va, err := client.Wallets.CreateVirtualAccount(ctx, wallet.ID, monigo.CreateVirtualAccountRequest{
Provider: monigo.VirtualAccountProviderPaystack,
Currency: "NGN",
})
vaList, err := client.Wallets.ListVirtualAccounts(ctx, wallet.ID)
Wallet constants
| Constant | Value | Description |
|---|
WalletEntryTypeDeposit | deposit | Customer top-up |
WalletEntryTypeWithdrawal | withdrawal | Customer withdrawal |
WalletEntryTypeUsage | usage | Metered usage charge |
WalletEntryTypeRefund | refund | Reversal of a previous charge |
WalletEntryTypeAdjustment | adjustment | Manual correction |
VirtualAccountProviderPaystack | paystack | Paystack virtual account |
VirtualAccountProviderFlutterwave | flutterwave | Flutterwave virtual account |
VirtualAccountProviderMonnify | monnify | Monnify virtual account |
All wallet amounts (Balance, ReservedBalance, ledger Amount) are decimal strings to preserve precision.
Example programs
The SDK ships with seven runnable example programs under examples/:
| Program | What it demonstrates |
|---|
examples/quickstart | End-to-end: customer → metric → plan → subscription → ingest |
examples/metering | High-volume batch ingest with idempotency and rate-limit retry |
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 |
examples/portal-tokens | Create, list, and revoke customer portal access links |
examples/wallets | Wallet lifecycle: create, credit, debit, transactions, virtual accounts |
Run any example by setting MONIGO_API_KEY and executing:
cd examples/quickstart
MONIGO_API_KEY=mk_test_... go run .
Testing
The SDK has 77 unit tests with zero external dependencies. Each test spins up an in-process mock HTTP server via net/http/httptest:
To run tests in your own project against a local Monigo server, point WithBaseURL at it:
client := monigo.New("sk_test_...",
monigo.WithBaseURL("http://localhost:8000"),
)