Docs / schema
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: 1plans: - 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: 1providers: - stripe # Payment providers to sync withsettings: currency: usd trial_days: 14 grace_days: 7entitlements: # Define available limits projects: type: int unit: projectplans: # 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 supportaddons: # 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_time to indicate a non-recurring plan - The price key must be one_time instead of monthly/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 discountdiscount: { percent: 50 } # 50% off# Fixed amount discountdiscount: { fixed: 1000 } # $10 offDuration Options
duration: once # First payment onlyduration: forever # Every paymentduration: { 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: stripeenvironment: sandboxsynced_at: "2026-01-22T18:45:00Z"plans: pro: product_id: prod_PwQ5lMnOpRsTuV prices: monthly: price_2OyBcDeFgHiJkLmN yearly: price_3PzCdEfGhIjKlMnOaddons: 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: 1providers: - stripesettings: currency: usd trial_days: 14 grace_days: 7entitlements: projects: type: int unit: project team_members: type: int unit: seat api_requests: type: rate unit: request sso: type: bool audit_logs: type: boolplans: - 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: trueaddons: - 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