Skip to main content
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.
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
}
HelperHTTP StatusWhen it fires
IsNotFound(err)404Resource does not exist
IsUnauthorized(err)401Invalid or missing API key
IsForbidden(err)403API key lacks the required scope
IsConflict(err)409Duplicate resource (e.g. customer already has an active subscription of the same plan type)
IsRateLimited(err)429Request rate limit exceeded
IsQuotaExceeded(err)402Organisation event quota exhausted
IsValidationError(err)400Request 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",
})
ConstantValueDescription
AggregationCountcountCount the number of matching events
AggregationSumsumSum a numeric property across events
AggregationMaxmaxMaximum value of a property
AggregationMinminimumMinimum value of a property
AggregationAverageaverageAverage value of a property
AggregationUniqueuniqueCount 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

ConstantValueDescription
PricingModelFlatflatFixed price per unit
PricingModelTieredtieredGraduated tiers — each unit priced at its tier
PricingModelVolumevolumeWhole volume priced at one tier
PricingModelPackagepackagePrice per block of N units
PricingModelOverageoverageBase allowance + per-unit above threshold
PricingModelWeightedTieredweighted_tieredAverage price across graduated tiers

Plan types and billing periods

ConstantValue
PlanTypeCollectioncollection — charge customers
PlanTypePayoutpayout — pay out to vendors
BillingPeriodDailydaily
BillingPeriodWeeklyweekly
BillingPeriodMonthlymonthly
BillingPeriodQuarterlyquarterly
BillingPeriodAnnuallyannually

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 constantValueMeaning
SubscriptionStatusActiveactiveBilling is running
SubscriptionStatusPausedpausedBilling paused; events still accepted
SubscriptionStatusCanceledcanceledSubscription 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 constantValue
PayoutMethodBankTransferbank_transfer
PayoutMethodMobileMoneymobile_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 constantValueMeaning
InvoiceStatusDraftdraftNot yet finalized; amounts may change
InvoiceStatusFinalizedfinalizedLocked and ready for payment
InvoiceStatusPaidpaidPayment collected
InvoiceStatusVoidvoidCancelled
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.
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/:
ProgramWhat it demonstrates
examples/quickstartEnd-to-end: customer → metric → plan → subscription → ingest
examples/meteringHigh-volume batch ingest with idempotency and rate-limit retry
examples/billingGenerate, inspect, and finalize an invoice
examples/payoutsRegister payout accounts and trigger event replay
examples/usage-reportQuery rollups and print a terminal usage table
examples/portal-tokensCreate, 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:
cd gosdk
go test ./...
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"),
)