Skip to content

Architecture

BoxBilling is built as a FastAPI application using a layered architecture with clear separation of concerns. The system supports multi-tenancy through organization-based isolation and provides both synchronous API endpoints and asynchronous background job processing. The platform exposes 204 API endpoints across three API surfaces: the core V1 API, a customer self-service Portal API, and a Dashboard analytics API.

ComponentTechnology
Web frameworkFastAPI
ORMSQLAlchemy 2.0
DatabasePostgreSQL
Analytics storeClickHouse (optional, for high-volume events)
Task queueARQ + Redis
ValidationPydantic v2
MigrationsAlembic
Package managerUV
ContainerizationDocker
┌─────────────────────────────────────────┐
│ Routers (API) │ HTTP endpoints, request validation
├─────────────────────────────────────────┤
│ Schemas │ Pydantic models for request/response
├─────────────────────────────────────────┤
│ Services │ Business logic, orchestration
├─────────────────────────────────────────┤
│ Repositories │ Database queries, data access
├─────────────────────────────────────────┤
│ Models │ SQLAlchemy ORM definitions
├─────────────────────────────────────────┤
│ Core │ Config, auth, database, rate limiting
└─────────────────────────────────────────┘

28 router modules handle HTTP endpoints across three API surfaces:

  • V1 API (/v1/) — 28 resource routers: customers, plans, subscriptions, invoices, payments, payment_methods, payment_requests, wallets, events, billable_metrics, fees, credit_notes, coupons, add_ons, taxes, webhook_endpoints, organizations, dunning_campaigns, integrations, data_exports, audit_logs, billing_entities, features, entitlements, usage_alerts, usage_thresholds, commitments, notifications, and global search
  • Portal API (/portal/) — Customer self-service endpoints for subscriptions, invoices, wallets, payments, usage, coupons, add-ons, payment methods, and profile management
  • Dashboard API (/dashboard/) — Analytics endpoints for stats, revenue, activity, customer metrics, sparklines, and revenue analytics

Each V1 router is registered in app/main.py with a versioned prefix like /v1/customers.

249 Pydantic v2 schemas with from_attributes=True for ORM compatibility. Each resource has separate Create, Update, and Response schemas to enforce validation at API boundaries. The schema layer also includes specialized types for previews, simulations, bulk operations, and portal-specific responses.

Business logic is encapsulated in service classes. Key services include:

  • InvoiceGenerationService — Orchestrates charge calculation, tax application, coupon discounts, and wallet credit consumption
  • InvoicePreviewService — Generates invoice previews without persisting
  • ProgressiveBillingService — Handles mid-period billing with progressive credit tracking
  • SubscriptionLifecycleService — Manages subscription state machine (activate, pause, resume, upgrade, downgrade, cancel, terminate)
  • SubscriptionDatesService — Calculates next billing dates, trial periods, and billing boundaries
  • UsageAggregationService — Aggregates usage events into metric values
  • UsageQueryService — Queries and trends for usage data
  • WalletService — Priority-based prepaid credit management with transfers and depletion forecasting
  • PaymentProviderService — Abstraction layer over payment providers (Stripe, Adyen, GoCardless, UCP)
  • RefundService — Full and partial refund processing
  • CouponService — Coupon creation, application, analytics, and duplication
  • AddOnService — One-time add-on charge management
  • WebhookService — Event delivery with retry and exponential backoff
  • DunningCampaignService — Automated failed payment recovery workflows
  • DataExportService — Asynchronous CSV export generation

Data access layer using the repository pattern. Each model has a corresponding repository with standard CRUD methods (get_all, get_by_id, create, update, delete) plus specialized queries.

50+ SQLAlchemy models defining the database schema. All models use UUID primary keys (stored as String(36)) and include created_at/updated_at audit timestamps with timezone-aware DateTime. Monetary amounts use Numeric(12,4) for precision, with column names suffixed _cents. Models are organized into domains:

DomainModels
CustomersCustomer
Plans & PricingPlan, Charge, ChargeFilter, ChargeFilterValue, Commitment, Feature, Entitlement
SubscriptionsSubscription, SubscriptionFee
UsageBillableMetric, Event, UsageThreshold, AppliedUsageThreshold, UsageAlert, UsageAlertTrigger
InvoicingInvoice, Fee, CreditNote, CreditNoteItem
PaymentsPayment, PaymentMethod, PaymentRequest
WalletsWallet, WalletTransaction
Coupons & Add-onsCoupon, AppliedCoupon, AddOn, AppliedAddOn
TaxesTax, AppliedTax
WebhooksWebhookEndpoint, Webhook, WebhookDeliveryAttempt
IntegrationsIntegration, IntegrationCustomer, IntegrationMapping, IntegrationSyncHistory
PlatformOrganization, ApiKey, BillingEntity, AuditLog, DataExport, IdempotencyRecord, Notification, DunningCampaign, DunningCampaignThreshold

Every resource is scoped to an organization_id. API keys are hashed and associated with organizations. The get_current_organization FastAPI dependency extracts the organization context from the Authorization: Bearer <api_key> header on every request.

Two authentication flows are supported:

API key authentication (V1 API):

Request → Authorization: Bearer <api_key>
→ Hash API key
→ Look up in api_keys table
→ Verify not revoked/expired
→ Return organization_id

Portal token authentication (Portal API):

Request → POST /portal/auth with customer_external_id
→ Generate short-lived JWT token
→ Subsequent requests use Bearer <portal_token>
→ Verify token and extract customer context

API keys are never stored in plaintext — only the hash and a display prefix are persisted.

The ARQ task queue handles asynchronous processing via Redis:

ScheduleTask
Every 5 minRetry failed webhooks
HourlyProcess trial expirations
HourlyGenerate periodic invoices
HourlyEvaluate dunning campaign thresholds
Daily (00:00)Process pending downgrades
Daily (00:30)Aggregate daily usage
Daily (01:00)Check usage alert conditions

On-demand tasks are enqueued by API endpoints:

  • Usage threshold checks — triggered after event ingestion
  • Data export processing — triggered on export creation
  • Webhook delivery — triggered on billable events
  • Usage alert evaluation — triggered on alert creation or test

The Portal API provides a self-service interface for customers, supporting:

  • Subscription management — View subscriptions, change plans, cancel
  • Invoice access — List, view, and download invoices; pay now
  • Wallet operations — View balance, top up prepaid credits
  • Usage monitoring — Current usage, usage trends, projected usage, usage limits
  • Payment methods — List and manage saved payment methods
  • Coupons & Add-ons — Redeem coupons, purchase add-ons
  • Profile management — Update customer profile, view branding

Portal endpoints use a separate authentication flow via /portal/auth and return portal-specific response schemas with reduced fields.

The Dashboard API provides operational analytics:

  • Overview stats — Customer counts, subscription counts, MRR, revenue totals
  • Revenue analytics — Revenue by plan, by type breakdown, daily revenue trends
  • Activity feed — Recent events and changes across the platform
  • Customer metrics — Top customers by revenue, customer health
  • Sparklines — Compact trend data for dashboard widgets

For high-volume event ingestion, ClickHouse can be enabled as an optional analytics store. When configured via CLICKHOUSE_URL, events are written to both the primary database and a ReplacingMergeTree table in ClickHouse, with aggregation queries routed to ClickHouse for performance.

The /v1/search endpoint provides cross-entity search across customers, subscriptions, invoices, plans, and other resources, returning typed results grouped by entity type.

Event ingestion supports idempotency via the Idempotency-Key header. Keys are tracked in the IdempotencyRecord model to prevent duplicate event processing.

backend/
├── app/
│ ├── main.py # FastAPI app initialization
│ ├── worker.py # ARQ worker configuration
│ ├── tasks.py # Background task implementations
│ ├── core/
│ │ ├── auth.py # API key + portal token authentication
│ │ ├── config.py # Settings (Pydantic Settings)
│ │ ├── database.py # SQLAlchemy engine & sessions
│ │ ├── clickhouse.py # ClickHouse client
│ │ └── rate_limiter.py # In-memory sliding window
│ ├── models/ # 50+ SQLAlchemy models
│ ├── schemas/ # 249 Pydantic request/response schemas
│ ├── repositories/ # Data access layer
│ ├── routers/ # 28+ API router modules
│ ├── services/ # Business logic
│ │ ├── charge_models/ # 8 pricing calculators
│ │ ├── integrations/ # Accounting & CRM adapters
│ │ └── payment_providers/# Stripe, Adyen, GoCardless, UCP adapters
│ └── alembic/ # Database migrations
├── tests/ # Test suite (100% coverage required)
├── scripts/ # OpenAPI generation
├── pyproject.toml # Dependencies & config
├── Dockerfile # Container build
└── alembic.ini # Migration config

All configuration is managed through environment variables loaded via Pydantic Settings:

VariableDescriptionDefault
APP_DATABASE_DSNDatabase connection string
REDIS_URLRedis for task queueredis://localhost:6379
CORS_ORIGINSAllowed CORS originslocalhost:3000,5173
RATE_LIMIT_EVENTS_PER_MINUTEEvent ingestion rate limit1000
CLICKHOUSE_URLClickHouse connection (optional)
stripe_api_keyStripe API key
adyen_api_keyAdyen API key
gocardless_access_tokenGoCardless token
ucp_api_keyUCP (Universal Checkout Provider) key
PORTAL_JWT_SECRETSecret for portal token signing

See .env.example for the full list.