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:

LocationWhat lives there
Payment provider dashboardsProducts, prices, coupons
Backend codeEntitlement checks, feature flags
Marketing sitesPricing pages, feature comparisons
Infrastructure configsEnvironment-specific settings

This leads to configuration drift, inconsistencies between environments, and provider lock-in.

How Other Approaches Fall Short

ApproachLimitation
Provider dashboardsLock-in, no version control, manual sync between environments
Terraform/PulumiInfrastructure-focused, provider-specific resources, no entitlement logic
Feature flag servicesPricing logic separate from access control, another vendor dependency
Custom codeScattered across codebase, hard to audit, no single source of truth

The Raterunner Solution

PrincipleHow it works
Single source of truthOne billing.yaml defines all plans, prices, and entitlements
Provider-agnosticSync to Stripe, Paddle, or Chargebee from the same config
Environment separationProvider IDs stored separately per sandbox/production
Version controlledYAML in Git = full history, PR reviews, rollback
Backend owns entitlementsYour code checks limits, not the provider
Marketing includedHeadlines, feature lists live with pricing data

Architecture

The system works in three layers:

  1. Source of Truth — your billing.yaml file with plans, prices, and entitlements
  2. Provider ID Files — auto-generated files (stripe_sandbox.yaml, stripe_production.yaml) that map your config to provider-specific IDs
  3. 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
FilePurposeCommitted to Git?
billing.yamlYour billing configurationYes
stripe_sandbox.yamlStripe IDs for test environmentYes
stripe_production.yamlStripe IDs for live environmentYes

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

PropertyTypeRequiredDescription
version1YesSchema version (always 1)
providersarrayNoPayment providers to sync with (stripe, paddle, chargebee)
settingsobjectNoDefault settings for all plans
entitlementsobjectNoDefine available limit types
plansarrayYesList of pricing plans (minimum 1)
addonsarrayNoOptional add-on products
promotionsarrayNoPromotional 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
PropertyTypeDefaultDescription
currencystringusdThree-letter currency code (multi-currency support is planned)
trial_daysinteger0Default trial period in days
grace_daysinteger7Days 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 support

Entitlement Types

TypeDescriptionExample value in plan
intNumeric limitprojects: 25 or projects: unlimited
boolFeature flagsso: true
rateRate limitapi_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: false

Plan Properties

PropertyTypeRequiredDescription
idstringYesUnique identifier (^[a-z][a-z0-9_]*$)
namestringYesDisplay name
headlinestringNoShort tagline for pricing page
descriptionstringNoLonger description
typeenumNopersonal, team, or enterprise
billing_modelenumNosubscription (default) or one_time
versionintegerNoPlan version for grandfathering (see Plan Versioning)
publicbooleanNoShow on pricing page (default: true)
defaultbooleanNoMark as default plan (default: false)
trial_daysintegerNoTrial period, overrides global setting
pricesobjectYesPricing configuration
limitsobjectNoEntitlement values
featuresarrayNoMarketing bullet points
upgrades_toarrayNoPlan IDs this plan can upgrade to
metadataobjectNoCustom 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/year

The 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 once

Key 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
PropertyTypeDescription
per_unitintegerPrice per unit in cents
unitstringUnit name (seat, user, etc.)
minintegerMinimum quantity
maxintegerMaximum quantity
includedintegerUnits 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: volume

Modes:

  • 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
PropertyTypeDescription
up_tointeger or "unlimited"Upper bound of this tier
amountintegerPrice per unit in cents
flatintegerFlat 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
      - business

Grant Syntax

SyntaxMeaning
"+10"Add 10 to existing limit
"-5"Subtract 5 from existing limit
25Set to exactly 25
trueEnable boolean feature
unlimitedRemove 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 allowed

Discount Types

# Percentage discount
discount: { percent: 50 }     # 50% off

# Fixed amount discount
discount: { fixed: 1000 }     # $10 off

Duration Options

duration: once                # First payment only
duration: forever             # Every payment
duration: { months: 3 }       # First 3 months

Promotion Properties

PropertyTypeDescription
codestringPromo code (^[A-Z0-9_]+$)
descriptionstringInternal description
discountobject{ percent: N } or { fixed: N }
durationstring/objectonce, forever, or { months: N }
applies_toarrayPlan IDs (empty = all plans)
new_customers_onlybooleanOnly for new customers (default: true)
max_usesintegerTotal redemption limit
expiresstringExpiration date (YYYY-MM-DD)
activebooleanEnable/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_7TdGhIjKlMnOpQrS

These 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

FeatureStripePaddleChargebee
Flat pricing
Per-unit pricing
Tiered graduated✅ Native❌ Backend
Tiered volume✅ Native❌ Backend
Metered billing✅ Native⚠️ Limited
Subscription mgmt
Tax handling✅ Stripe Tax✅ MoRVia gateway

Note: Currently only Stripe is fully supported. Paddle and Chargebee support is planned.

Validation

Validate your configuration before syncing:

raterunner validate raterunner/billing.yaml

The 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

On this page