SeptemCore LogoSeptemCore
API ReferenceREST API

Billing REST API Reference

Billing / Subscriptions REST API reference. Plans CRUD, subscription lifecycle (trialing → active → past_due → suspended → terminated), usage and limits enforcement, payment methods, webhook receiver for Stripe/PayPal events, and escalation timeline with downgrade grace period.

The Billing Service manages subscriptions, licensing, and usage limits for all tenants. When Billing is not deployed (self-hosted), the Gateway skips all limit checks and all requests pass freely.

See the REST API Overview for authentication, error format, pagination, and rate limiting.

For layout brevity, the /api/v1 base path prefix is omitted from the endpoint tables below.


Subscription Status Machine

created-at-tenant-creation


      trialing  ──── trial end without payment method ──▶  past_due

 payment collected


       active  ◀─────────────────────── payment received (any stage)

   payment failed


      past_due   (0–7 days grace — tenant fully functional)

    7 days unpaid


     suspended  (8–37 days — read-only mode, all data accessible)

   30 days more


    terminated  (async soft-delete over 24–48 hours)

Zero-state guarantee: A trialing subscription is automatically created for every new tenant — there is never an ambiguous "no subscription" state.


Plans

EndpointAuthDescription
GET /billing/plansbilling.viewList all available plans
GET /billing/plans/:idbilling.viewPlan details (limits, pricing, features)

Plan Object

{
  "id":       "plan_growth_monthly",
  "name":     "Growth",
  "interval": "monthly",
  "price":    4900,
  "currency": "USD",
  "limits": {
    "users":          50,
    "records":        100000,
    "storageBytes":   10737418240,
    "eventsPerDay":   500000,
    "modules":        10
  },
  "features": ["custom_domains", "webhooks", "audit_export"]
}

Limit semantics: limit = 0 means unlimited (no enforcement). limit >= 1 is an enforced ceiling.


Subscriptions

EndpointAuthDescription
GET /billing/subscriptionsbilling.viewList subscriptions (filter by tenantId)
POST /billing/subscriptionsbilling.plan.changeCreate subscription (initial plan selection)
PATCH /billing/subscriptions/:idbilling.plan.changeChange plan. Requires optimistic locking.
DELETE /billing/subscriptions/:idbilling.plan.changeCancel subscription
POST /billing/subscriptions/:id/cancel-downgradebilling.plan.changeCancel pending downgrade

GET /api/v1/billing/subscriptions?tenant_id=...

{
  "id":       "01j9psub0000000000000001",
  "tenantId": "01j9pten0000000000000001",
  "planId":   "plan_growth_monthly",
  "status":   "active",
  "version":  3,
  "trialEndsAt":    null,
  "currentPeriodStart": "2026-04-01T00:00:00Z",
  "currentPeriodEnd":   "2026-05-01T00:00:00Z",
  "cancelAtPeriodEnd":  false,
  "pendingChange": null
}

PATCH /api/v1/billing/subscriptions/:id — Change Plan

Optimistic locking: supply the current version to prevent concurrent plan changes.

PATCH https://api.septemcore.com/v1/billing/subscriptions/01j9psub0000000000000001
Authorization: Bearer <access_token>
Content-Type: application/json

{
  "planId":  "plan_enterprise_monthly",
  "version": 3
}

Response 200 OK — updated subscription with version: 4.

ScenarioBehaviour
Wrong version409 Conflict (problems/optimistic-lock-conflict)
Plan change already in progress409 Conflict (problems/plan-change-in-progress) — mutex per tenant
Downgrade (smaller limits)Existing data is untouched. New resource creation blocked (402) immediately. After 30 days still over-limit → past_due → escalation
Trial end without payment methodStatus moves to past_due. Tenant is notified 3 days before trial end

POST /api/v1/billing/subscriptions/:id/cancel-downgrade

Cancels a downgrade that is in pending status (before the next billing cycle starts). After the billing cycle boundary — use PATCH to upgrade to a higher plan.

{ "reason": "user_changed_mind" }

Usage and Limits

EndpointAuthDescription
GET /billing/usage?tenant_id=...billing.viewCurrent usage counters for the tenant
GET /billing/usage?tenant_id=.../historybilling.viewUsage history (daily snapshots, paginated)

GET /api/v1/billing/usage?tenant_id=...?tenant_id=...

{
  "tenantId": "01j9pten0000000000000001",
  "period":   "2026-04",
  "usage": {
    "users":          23,
    "records":        48200,
    "storageBytes":   2147483648,
    "eventsPerDay":   12000,
    "modules":        4
  },
  "limits": {
    "users":          50,
    "records":        100000,
    "storageBytes":   10737418240,
    "eventsPerDay":   500000,
    "modules":        10
  }
}

Limit Enforcement Flow (Gateway)

Limit checking is the Gateway's responsibility, not the module's:

1. POST /api/v1/data (create new record)
2. Gateway extracts tenantId from JWT
3. Gateway → Valkey GET plan_info:{tenantId}
   ├── HIT  → plan limits + status resolved (O(1))
   └── MISS → gRPC to Billing → resolve → cache (TTL 15 min)
4. Status check:
   active / trialing             → proceed
   past_due                      → proceed (grace period)
   suspended (mutating requests) → 402 Payment Required
     EXCEPTION: money debit/transfer/hold/confirm/cancel → allowed
     (blocking user funds withdrawal = legal risk)
   terminated                    → 402 on all requests
5. Usage < limit                 → forward to Data Layer
   Usage >= limit                → 402 Payment Required (plan-limit-exceeded)
6. Invalidation: billing.plan.changed / billing.subscription.changed
   → Event Bus → Gateway invalidates Valkey cache

Payment Methods

The payment methods, invoices, billing info, and termination-status endpoints are planned for Phase 2+ and are not implemented in the current Go billing service release.

EndpointAuthDescription
GET /billing/payment-methodsbilling.payment.manageList saved payment methods
POST /billing/payment-methodsbilling.payment.manageAttach a payment method (SetupIntent)
DELETE /billing/payment-methods/:idbilling.payment.manageDetach payment method
POST /billing/payment-methods/:id/set-defaultbilling.payment.manageSet as default payment method

Invoices

EndpointAuthDescription
GET /billing/invoicesbilling.viewList invoices (cursor-paginated)
GET /billing/invoices/:idbilling.viewInvoice details
GET /billing/invoices/:id/pdfbilling.exportDownload invoice PDF

Billing Info

EndpointAuthDescription
GET /billing/infobilling.viewCurrent billing contact (email, address, VAT)
PATCH /billing/infobilling.info.updateUpdate billing email, company name, address

Webhook Receiver

The Billing Service receives webhook events from Stripe and PayPal to synchronise subscription and payment state.

EndpointAuthDescription
POST /billing/webhooks/{provider}NoneReceive webhook event from provider

Supported {provider} values: stripe, paypal.

Webhook Security

ParameterValue
Stripe signatureStripe-Signature header validated via stripe.ConstructEvent(). Invalid → 401 Unauthorized
IdempotencyBy provider event_id — duplicate webhook ignored
Response timeoutPlatform must respond 200 OK within 10 seconds
Stripe retriesUp to 3 days on non-2xx responses

Handled Events

Provider eventAction
invoice.paidUpdate subscription → active. Publish billing.subscription.changed. Gateway cache invalidated
invoice.payment_failedMove to past_due. Send notification to tenant
subscription.canceledUpdate subscription → canceled. Notify tenant
customer.subscription.updatedSync plan changes from Stripe (e.g. coupon applied)

Escalation Timeline (non-payment)

StageDurationBehaviour
Grace Period0–7 days after missed paymentStatus: past_due. Fully functional. Repeated email + in-app notifications
SuspendedDay 8–37Status: suspended. Read-only — all data accessible, mutations blocked (402)
TerminatedDay 38+Status: terminated. Async background soft-delete of all tenant data (24–48 hours). Progress: GET /admin/tenants/:id/termination-status
Payment received at any stageInstantpast_due / suspendedactive. Data untouched

Principle: A tenant never loses data without warning. At minimum 37 days and 5+ notifications before any soft-delete.

Termination Status Endpoint

GET https://api.septemcore.com/v1/admin/tenants/01j9pten0000000000000001/termination-status
Authorization: Bearer <access_token>
{
  "tenantId": "01j9pten0000000000000001",
  "status":   "terminated",
  "phase":    "deleting_files",
  "startedAt": "2026-04-20T00:00:00Z"
}

phase values: deleting_recordsdeleting_filescompleted. Recovery is possible through a Platform Owner until phase: completed.


Permissions Reference

PermissionDescription
billing.viewRead-only: invoices, usage, current plan
billing.plan.changeUpgrade / downgrade subscription (financial impact)
billing.payment.manageAdd / remove payment methods
billing.info.updateUpdate billing email, address
billing.exportExport invoice PDFs and billing data

Error Reference

Error typeStatusTrigger
problems/plan-limit-exceeded402Resource usage at plan ceiling
problems/subscription-suspended402Mutating request while suspended
problems/subscription-terminated402Any request while terminated
problems/optimistic-lock-conflict409Wrong version on PATCH subscription
problems/plan-change-in-progress409Second plan change while first is pending_change
problems/webhook-signature-invalid401Missing or invalid Stripe-Signature header

On this page