Entities
The core data types in Ledger — Plans, Subscriptions, Invoices, Usage Events, and more.
All Ledger entities use TypeID identifiers for type-safe, sortable IDs. This page provides an overview of every entity and how they relate.
Base entity
type Entity struct {
ID string `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}Every entity struct embeds base timestamp tracking and a TypeID identifier.
Entity overview
Plan
The top-level pricing entity. Defines features, pricing tiers, and billing configurations for customers.
type Plan struct {
Entity
ID PlanID `json:"id"` // pln_...
Name string `json:"name"`
Description string `json:"description"`
Status string `json:"status"` // draft, active, archived
// Billing configuration
BillingPeriod string `json:"billing_period"` // month, year, etc
Currency string `json:"currency"` // USD, EUR, etc
TrialPeriodDays int `json:"trial_period_days"`
// Features and pricing
Features []Feature `json:"features"`
PricingTiers []PricingTier `json:"pricing_tiers"`
// Metadata
Metadata map[string]any `json:"metadata"`
}See Plans for details.
Feature
A capability or resource included in a plan. Can be boolean (on/off) or metered (usage-based).
type Feature struct {
ID FeatureID `json:"id"` // fea_...
PlanID PlanID `json:"plan_id"`
Name string `json:"name"`
Key string `json:"key"` // unique identifier for code
Type string `json:"type"` // boolean, metered
// For metered features
MeterType string `json:"meter_type,omitempty"` // counter, gauge, timer
Unit string `json:"unit,omitempty"` // api_calls, gb, minutes
Aggregation string `json:"aggregation,omitempty"` // sum, max, unique_count
// Limits
Limit *int64 `json:"limit,omitempty"` // max allowed usage
Overage bool `json:"overage"` // allow usage above limit
Metadata map[string]any `json:"metadata"`
}PricingTier
Defines the pricing structure for a plan — flat fee, per-seat, usage-based, or hybrid.
type PricingTier struct {
ID TierID `json:"id"` // tier_...
PlanID PlanID `json:"plan_id"`
Name string `json:"name"`
Type string `json:"type"` // flat, per_unit, tiered, volume
// Flat pricing
FlatAmount Money `json:"flat_amount,omitempty"`
// Per-unit pricing
UnitAmount Money `json:"unit_amount,omitempty"`
FeatureKey string `json:"feature_key,omitempty"` // which feature to meter
// Tiered pricing brackets
Brackets []PricingBracket `json:"brackets,omitempty"`
Metadata map[string]any `json:"metadata"`
}
type PricingBracket struct {
StartQuantity int64 `json:"start_quantity"`
EndQuantity *int64 `json:"end_quantity,omitempty"` // nil = unlimited
UnitAmount Money `json:"unit_amount"`
}Subscription
A customer's active plan subscription with billing cycle and status tracking.
type Subscription struct {
Entity
ID SubscriptionID `json:"id"` // sub_...
CustomerID CustomerID `json:"customer_id"`
PlanID PlanID `json:"plan_id"`
Status string `json:"status"` // trialing, active, past_due, canceled, expired
// Billing cycle
CurrentPeriodStart time.Time `json:"current_period_start"`
CurrentPeriodEnd time.Time `json:"current_period_end"`
TrialEnd *time.Time `json:"trial_end,omitempty"`
// Cancellation
CanceledAt *time.Time `json:"canceled_at,omitempty"`
CancelAtPeriodEnd bool `json:"cancel_at_period_end"`
// Payment
PaymentMethodID string `json:"payment_method_id,omitempty"`
// Entitlements (cached)
Entitlements map[string]Entitlement `json:"entitlements"`
Metadata map[string]any `json:"metadata"`
}See Subscriptions for lifecycle details.
Customer
Represents a billable entity (person, organization, team).
type Customer struct {
Entity
ID CustomerID `json:"id"` // cus_...
Email string `json:"email"`
Name string `json:"name"`
// Organization (for B2B)
CompanyName string `json:"company_name,omitempty"`
TaxID string `json:"tax_id,omitempty"`
// Billing details
BillingAddress Address `json:"billing_address"`
Currency string `json:"currency"` // default: USD
// Payment provider
StripeCustomerID string `json:"stripe_customer_id,omitempty"`
PaddleCustomerID string `json:"paddle_customer_id,omitempty"`
Metadata map[string]any `json:"metadata"`
}Invoice
A billing document for a subscription period, including usage charges.
type Invoice struct {
Entity
ID InvoiceID `json:"id"` // inv_...
CustomerID CustomerID `json:"customer_id"`
SubscriptionID SubscriptionID `json:"subscription_id"`
Status string `json:"status"` // draft, open, paid, void, uncollectible
// Amounts (in cents)
Subtotal Money `json:"subtotal"`
Tax Money `json:"tax"`
Total Money `json:"total"`
AmountPaid Money `json:"amount_paid"`
AmountDue Money `json:"amount_due"`
Currency string `json:"currency"`
// Line items
Lines []InvoiceLine `json:"lines"`
// Dates
PeriodStart time.Time `json:"period_start"`
PeriodEnd time.Time `json:"period_end"`
DueDate time.Time `json:"due_date"`
PaidAt *time.Time `json:"paid_at,omitempty"`
// Payment
PaymentIntentID string `json:"payment_intent_id,omitempty"`
Metadata map[string]any `json:"metadata"`
}
type InvoiceLine struct {
ID string `json:"id"`
Description string `json:"description"`
Quantity int64 `json:"quantity"`
UnitAmount Money `json:"unit_amount"`
Amount Money `json:"amount"`
Type string `json:"type"` // subscription, usage, one_time
// For usage lines
FeatureKey string `json:"feature_key,omitempty"`
PeriodStart time.Time `json:"period_start,omitempty"`
PeriodEnd time.Time `json:"period_end,omitempty"`
}See Invoicing for generation and payment flow.
UsageEvent
A metered event submitted for billing calculation.
type UsageEvent struct {
Entity
ID EventID `json:"id"` // evt_...
CustomerID CustomerID `json:"customer_id"`
// Event identification
EventName string `json:"event_name"` // e.g., "api_call", "storage_gb"
EventTime time.Time `json:"event_time"`
IdempotencyKey string `json:"idempotency_key,omitempty"`
// Value
Value float64 `json:"value"` // e.g., 1 for count, 2.5 for GB
Unit string `json:"unit"` // api_calls, gb, minutes
// Context
Properties map[string]any `json:"properties,omitempty"`
// Processing
Processed bool `json:"processed"`
ProcessedAt *time.Time `json:"processed_at,omitempty"`
}See Metering for high-throughput event ingestion.
Entitlement
Cached access rights for sub-millisecond checks.
type Entitlement struct {
FeatureKey string `json:"feature_key"`
Enabled bool `json:"enabled"`
// For metered features
Limit *int64 `json:"limit,omitempty"`
Usage int64 `json:"usage"`
Remaining *int64 `json:"remaining,omitempty"`
HasOverage bool `json:"has_overage"`
}Entitlements are computed from subscription + plan + usage and cached for fast access checks.
Payment
Records a payment transaction.
type Payment struct {
Entity
ID PaymentID `json:"id"` // pay_...
InvoiceID InvoiceID `json:"invoice_id"`
CustomerID CustomerID `json:"customer_id"`
Amount Money `json:"amount"`
Currency string `json:"currency"`
Status string `json:"status"` // pending, succeeded, failed, refunded
// Provider details
ProviderID string `json:"provider_id"` // Stripe payment intent ID, etc
Provider string `json:"provider"` // stripe, paddle
PaymentMethod string `json:"payment_method"` // card, bank_transfer, etc
// Timestamps
ProcessedAt *time.Time `json:"processed_at,omitempty"`
FailedAt *time.Time `json:"failed_at,omitempty"`
FailureReason string `json:"failure_reason,omitempty"`
Metadata map[string]any `json:"metadata"`
}Entity relationship diagram
Customer
├── Subscription[] ──→ Plan
│ │ ├── Feature[]
│ │ └── PricingTier[]
│ │ └── PricingBracket[]
│ │
│ ├── Entitlements (cached from Plan + Usage)
│ └── Invoice[]
│ ├── InvoiceLine[] (computed from Plan + Usage)
│ └── Payment[]
│
└── UsageEvent[] ──→ aggregated → Invoice LinesTypeID prefixes
All entities use TypeIDs with consistent prefixes:
| Entity | Prefix | Example |
|---|---|---|
| Plan | pln_ | pln_01h2xcejqtf2nbrexx3vqjhp41 |
| Feature | fea_ | fea_01h2xcejqtf2nbrexx3vqjhp42 |
| Pricing Tier | tier_ | tier_01h2xcejqtf2nbrexx3vqjhp43 |
| Subscription | sub_ | sub_01h2xcejqtf2nbrexx3vqjhp44 |
| Customer | cus_ | cus_01h2xcejqtf2nbrexx3vqjhp45 |
| Invoice | inv_ | inv_01h2xcejqtf2nbrexx3vqjhp46 |
| Usage Event | evt_ | evt_01h2xcejqtf2nbrexx3vqjhp47 |
| Payment | pay_ | pay_01h2xcejqtf2nbrexx3vqjhp48 |
TypeIDs are:
- Type-safe — Prefix indicates entity type
- Sortable — Encodes timestamp for chronological ordering
- Globally unique — 128-bit UUID-like collision resistance
- URL-safe — Base32 encoding without special characters
Money type
All monetary values use the Money type (integer cents) to avoid floating-point precision errors:
type Money int64 // Amount in smallest currency unit (cents)
// Examples:
amount := Money(1000) // $10.00
amount := Money(99) // $0.99
amount := Money(12345) // $123.45Helper functions:
money.FromDollars(10.50) // → Money(1050)
money.ToDollars(Money(1050)) // → 10.50
money.Format(Money(1050), "USD") // → "$10.50"Store interfaces
Each entity type defines its own store interface. These are composed into a single composite store:
type Store interface {
PlanStore
FeatureStore
SubscriptionStore
CustomerStore
InvoiceStore
UsageEventStore
PaymentStore
Migrate(ctx context.Context) error
Ping(ctx context.Context) error
Close() error
}Example store methods:
type PlanStore interface {
CreatePlan(ctx context.Context, plan *Plan) error
GetPlan(ctx context.Context, id PlanID) (*Plan, error)
GetPlanByName(ctx context.Context, name string) (*Plan, error)
UpdatePlan(ctx context.Context, plan *Plan) error
DeletePlan(ctx context.Context, id PlanID) error
ListPlans(ctx context.Context, filters PlanFilters) ([]Plan, error)
}