Billing Configuration Files
Universal billing configuration schema for SaaS applications. Single source of truth for plans, pricing, and entitlements.
Billing Configuration Files
Raterunner defines a universal billing configuration format — a structured way to describe your entire pricing setup: plans, prices, entitlements, addons, and promotions.
File Format
The configuration can be written in YAML or JSON:
- YAML (
billing.yaml) — recommended for human editing, cleaner syntax - JSON (
billing.json) — useful for programmatic generation or tooling integration
Both formats are validated against the same JSON Schema, ensuring consistency regardless of which format you choose.
# billing.yaml (YAML format)
version: 1
plans:
- id: pro
name: Pro
prices:
monthly: { amount: 2900 }// billing.json (JSON format)
{
"version": 1,
"plans": [
{
"id": "pro",
"name": "Pro",
"prices": {
"monthly": { "amount": 2900 }
}
}
]
}Most examples in this documentation use YAML for readability.
Why a Configuration Schema?
SaaS billing configuration is typically scattered across multiple places:
| Location | What lives there |
|---|---|
| Payment provider dashboards | Products, prices, coupons |
| Backend code | Entitlement checks, feature flags |
| Marketing sites | Pricing pages, feature comparisons |
| Infrastructure configs | Environment-specific settings |
This leads to configuration drift, inconsistencies between environments, and provider lock-in.
How Other Approaches Fall Short
| Approach | Limitation |
|---|---|
| Provider dashboards | Lock-in, no version control, manual sync between environments |
| Terraform/Pulumi | Infrastructure-focused, provider-specific resources, no entitlement logic |
| Feature flag services | Pricing logic separate from access control, another vendor dependency |
| Custom code | Scattered across codebase, hard to audit, no single source of truth |
The Raterunner Solution
| Principle | How it works |
|---|---|
| Single source of truth | One billing.yaml defines all plans, prices, and entitlements |
| Provider-agnostic | Sync to Stripe, Paddle, or Chargebee from the same config |
| Environment separation | Provider IDs stored separately per sandbox/production |
| Version controlled | YAML in Git = full history, PR reviews, rollback |
| Backend owns entitlements | Your code checks limits, not the provider |
| Marketing included | Headlines, feature lists live with pricing data |
Architecture
The system works in three layers:
- Source of Truth — your
billing.yamlfile with plans, prices, and entitlements - Provider ID Files — auto-generated files (
stripe_sandbox.yaml,stripe_production.yaml) that map your config to provider-specific IDs - Payment Providers — Stripe, Paddle, Chargebee where the actual products and prices live
Your billing.yaml file is never modified by the CLI — it's read-only. Provider ID files are auto-generated and contain only ID mappings, no business logic.
File Structure
A typical Raterunner setup in your project:
your-project/
└── raterunner/
├── billing.yaml # Source of truth (you edit this)
├── stripe_sandbox.yaml # Auto-generated: Stripe IDs for test env
└── stripe_production.yaml # Auto-generated: Stripe IDs for live env| File | Purpose | Committed to Git? |
|---|---|---|
billing.yaml | Your billing configuration | Yes |
stripe_sandbox.yaml | Stripe IDs for test environment | Yes |
stripe_production.yaml | Stripe IDs for live environment | Yes |
All files should be committed to Git — this gives you full history of what plans existed at any point in time.
Schema Structure
version: 1
providers:
- stripe # Payment providers to sync with
settings:
currency: usd
trial_days: 14
grace_days: 7
entitlements: # Define available limits
projects:
type: int
unit: project
plans: # Your pricing tiers
- id: pro
name: Pro
headline: For growing teams
prices:
monthly: { amount: 1900 }
yearly: { amount: 19000 }
limits:
projects: 25
features:
- Advanced analytics
- Priority support
addons: # Optional add-ons
- id: extra_projects
name: Extra Projects
price: { amount: 1000 }
grants:
projects: "+10"
promotions: # Coupon codes
- code: LAUNCH50
discount: { percent: 50 }
duration: { months: 3 }Top-Level Properties
| Property | Type | Required | Description |
|---|---|---|---|
version | 1 | Yes | Schema version (always 1) |
providers | array | No | Payment providers to sync with (stripe, paddle, chargebee) |
settings | object | No | Default settings for all plans |
entitlements | object | No | Define available limit types |
plans | array | Yes | List of pricing plans (minimum 1) |
addons | array | No | Optional add-on products |
promotions | array | No | Promotional codes and discounts |
Settings
Default settings apply to all plans unless overridden.
settings:
currency: usd # Default currency (ISO 4217, lowercase)
trial_days: 14 # Default trial period
grace_days: 7 # Grace period after failed payment| Property | Type | Default | Description |
|---|---|---|---|
currency | string | usd | Three-letter currency code (multi-currency support is planned) |
trial_days | integer | 0 | Default trial period in days |
grace_days | integer | 7 | Days before cancellation on failed payment |
Entitlements
Entitlements define what limits and features your plans can include. Define them once, reference them in plans.
entitlements:
projects:
type: int
unit: project
description: Number of active projects
team_members:
type: int
unit: seat
description: Team members per workspace
api_requests:
type: rate
unit: request
description: API rate limit
sso:
type: bool
description: Single sign-on supportEntitlement Types
| Type | Description | Example value in plan |
|---|---|---|
int | Numeric limit | projects: 25 or projects: unlimited |
bool | Feature flag | sso: true |
rate | Rate limit | api_requests: { limit: 1000, per: minute } |
Rate Limits
For API rate limiting or similar time-based limits:
limits:
api_requests: { limit: 1000, per: minute }Available time periods: second, minute, hour, day.
Plans
Plans define your pricing tiers.
plans:
- id: pro # Unique ID (snake_case)
name: Pro # Display name
headline: For growing teams # Pricing page tagline
description: Everything you need to scale
type: team # personal, team, enterprise
public: true # Show on pricing page
default: false # Mark as default plan
trial_days: 14 # Override global setting
prices:
monthly: { amount: 2900 }
yearly: { amount: 29000 }
limits:
projects: 25
api_requests: { limit: 1000, per: minute }
sso: false
features: # Marketing bullets
- Up to 25 projects
- Priority support
upgrades_to: # Valid upgrade paths
- business
- enterprise
metadata: # Custom data
contact_sales: falsePlan Properties
| Property | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique identifier (^[a-z][a-z0-9_]*$) |
name | string | Yes | Display name |
headline | string | No | Short tagline for pricing page |
description | string | No | Longer description |
type | enum | No | personal, team, or enterprise |
billing_model | enum | No | subscription (default) or one_time |
version | integer | No | Plan version for grandfathering (see Plan Versioning) |
public | boolean | No | Show on pricing page (default: true) |
default | boolean | No | Mark as default plan (default: false) |
trial_days | integer | No | Trial period, overrides global setting |
prices | object | Yes | Pricing configuration |
limits | object | No | Entitlement values |
features | array | No | Marketing bullet points |
upgrades_to | array | No | Plan IDs this plan can upgrade to |
metadata | object | No | Custom key-value data |
Pricing Models
Raterunner supports multiple pricing models.
1. Flat Pricing
Simple fixed price per billing period.
prices:
monthly: { amount: 2900 } # $29/month
yearly: { amount: 29000 } # $290/yearThe amount is always in cents (smallest currency unit).
2. One-Time Payments
For lifetime access or one-time purchases, use billing_model: one_time:
plans:
- id: pro_lifetime
name: Pro Lifetime
billing_model: one_time
prices:
one_time: { amount: 7900 } # $79 onceKey points:
- Use
billing_model: one_timeto indicate a non-recurring plan - The price key must be
one_timeinstead ofmonthly/yearly - Creates a Stripe Payment Intent instead of Subscription
- No trial period support (trials only apply to subscriptions)
Use cases:
- Lifetime deals (pay once, access forever)
- One-time setup fees
- Digital product purchases
- Limited-time offers
3. Per-Unit Pricing
Price multiplied by quantity (seats, users, etc.).
prices:
monthly:
per_unit: 1200 # $12 per seat per month
unit: seat
min: 1 # Minimum 1 seat
max: 100 # Maximum 100 seats
included: 1 # First seat free| Property | Type | Description |
|---|---|---|
per_unit | integer | Price per unit in cents |
unit | string | Unit name (seat, user, etc.) |
min | integer | Minimum quantity |
max | integer | Maximum quantity |
included | integer | Units included in base price |
4. Tiered Pricing
Progressive pricing that changes at quantity thresholds.
prices:
monthly:
tiers:
- up_to: 10
amount: 2000 # $20/seat for 1-10
- up_to: 50
amount: 1500 # $15/seat for 11-50
- up_to: unlimited
amount: 1000 # $10/seat for 51+
mode: graduated # or: volumeModes:
graduated— Each tier's price applies only to units within that tier (default)volume— The tier's price applies to ALL units once you reach that tier
| Property | Type | Description |
|---|---|---|
up_to | integer or "unlimited" | Upper bound of this tier |
amount | integer | Price per unit in cents |
flat | integer | Flat fee for reaching this tier |
Billing Periods
Plans can have any combination of billing periods:
prices:
monthly: { amount: 2900 }
quarterly: { amount: 7900 }
yearly: { amount: 29000 }Addons
Addons are optional products that extend a plan's limits or add features.
addons:
- id: extra_projects
name: Extra Projects Pack
description: Add 10 additional projects to your plan
price:
amount: 1000 # $10/month
grants:
projects: "+10" # Add 10 to existing limit
- id: priority_support
name: Priority Support
description: 24/7 priority support with 1-hour response time
price:
amount: 9900
requires_plan: # Only available for these plans
- pro
- businessGrant Syntax
| Syntax | Meaning |
|---|---|
"+10" | Add 10 to existing limit |
"-5" | Subtract 5 from existing limit |
25 | Set to exactly 25 |
true | Enable boolean feature |
unlimited | Remove limit entirely |
Promotions
Promotional codes for discounts.
promotions:
- code: LAUNCH50 # Code customers enter (uppercase)
description: 50% off first 3 months
discount: { percent: 50 } # 50% discount
duration: { months: 3 } # For 3 months
new_customers_only: true
expires: "2026-06-30"
active: true
- code: ANNUAL20
description: 20% off annual plans
discount: { percent: 20 }
duration: forever # Applies forever
applies_to: # Only these plans
- pro
- business
max_uses: 1000 # Total redemptions allowedDiscount Types
# Percentage discount
discount: { percent: 50 } # 50% off
# Fixed amount discount
discount: { fixed: 1000 } # $10 offDuration Options
duration: once # First payment only
duration: forever # Every payment
duration: { months: 3 } # First 3 monthsPromotion Properties
| Property | Type | Description |
|---|---|---|
code | string | Promo code (^[A-Z0-9_]+$) |
description | string | Internal description |
discount | object | { percent: N } or { fixed: N } |
duration | string/object | once, forever, or { months: N } |
applies_to | array | Plan IDs (empty = all plans) |
new_customers_only | boolean | Only for new customers (default: true) |
max_uses | integer | Total redemption limit |
expires | string | Expiration date (YYYY-MM-DD) |
active | boolean | Enable/disable code |
Provider ID Files
When you sync to a payment provider, Raterunner creates a provider ID file:
# stripe_sandbox.yaml (auto-generated)
provider: stripe
environment: sandbox
synced_at: "2026-01-22T18:45:00Z"
plans:
pro:
product_id: prod_PwQ5lMnOpRsTuV
prices:
monthly: price_2OyBcDeFgHiJkLmN
yearly: price_3PzCdEfGhIjKlMnO
addons:
extra_projects:
product_id: prod_SzT8oPqRsTuVwXy
price_id: price_7TdGhIjKlMnOpQrSThese files are:
- Auto-generated — don't edit manually
- Environment-specific — separate files for sandbox/production
- ID mappings only — no business logic
- Committed to Git — for tracking and debugging
Provider Support
| Feature | Stripe | Paddle | Chargebee |
|---|---|---|---|
| Flat pricing | ✅ | ✅ | ✅ |
| Per-unit pricing | ✅ | ✅ | ✅ |
| Tiered graduated | ✅ Native | ❌ Backend | ✅ |
| Tiered volume | ✅ Native | ❌ Backend | ✅ |
| Metered billing | ✅ Native | ⚠️ Limited | ✅ |
| Subscription mgmt | ✅ | ✅ | ✅ |
| Tax handling | ✅ Stripe Tax | ✅ MoR | Via gateway |
Note: Currently only Stripe is fully supported. Paddle and Chargebee support is planned.
Validation
Validate your configuration before syncing:
raterunner validate raterunner/billing.yamlThe CLI validates against the JSON Schema and checks for logical errors like:
- Missing required fields
- Invalid entitlement references
- Duplicate IDs
- Invalid pricing configurations
Full Example
See a complete billing.yaml with all features:
version: 1
providers:
- stripe
settings:
currency: usd
trial_days: 14
grace_days: 7
entitlements:
projects:
type: int
unit: project
team_members:
type: int
unit: seat
api_requests:
type: rate
unit: request
sso:
type: bool
audit_logs:
type: bool
plans:
- id: free
name: Free
headline: Get started for free
public: true
default: true
prices:
monthly: { amount: 0 }
limits:
projects: 3
team_members: 1
api_requests: { limit: 100, per: minute }
sso: false
features:
- Up to 3 projects
- Basic analytics
- Community support
- id: pro
name: Pro
headline: For growing teams
trial_days: 14
prices:
monthly:
per_unit: 1900
unit: seat
min: 1
included: 1
yearly:
per_unit: 15900
unit: seat
min: 1
included: 1
limits:
projects: 25
team_members: 25
api_requests: { limit: 1000, per: minute }
sso: false
audit_logs: true
features:
- Up to 25 projects
- Advanced analytics
- Priority email support
upgrades_to:
- enterprise
- id: pro_lifetime
name: Pro Lifetime
headline: Pay once, use forever
billing_model: one_time
prices:
one_time: { amount: 7900 }
limits:
projects: unlimited
team_members: 10
api_requests: { limit: 5000, per: minute }
sso: false
audit_logs: true
features:
- Lifetime access
- All future updates
- Priority support
- id: enterprise
name: Enterprise
headline: Custom solutions
prices:
monthly: { amount: 0 }
limits:
projects: unlimited
team_members: unlimited
api_requests: { limit: 50000, per: minute }
sso: true
audit_logs: true
features:
- Unlimited everything
- Dedicated success manager
- SLA guarantee
metadata:
contact_sales: true
addons:
- id: extra_projects
name: Extra Projects Pack
price: { amount: 1000 }
grants:
projects: "+10"
promotions:
- code: LAUNCH50
description: 50% off first 3 months
discount: { percent: 50 }
duration: { months: 3 }
new_customers_only: true
expires: "2026-06-30"
active: true