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.
Example programs
The SDK ships with six 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 |
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"),
)