// Platform

Billing & Subscriptions

Two billing modes: pay-as-you-go credits for shared inference, monthly subscription for committed hardware. Stripe processes payments and surfaces invoices; rotation, prorations, and refunds all happen here.

Stripe processes every payment. Project-level billing is managed at /billing: subscription state, credit purchases, invoice history, and the Stripe customer portal.

Billing is tied to your project, not your individual user account. Project owners manage billing settings and subscriptions on behalf of their team.

For detailed usage tracking, per-request analytics, and uptime billing data, see Usage Tracking & Billing.

Owner Required: Only project owners can access billing settings. If you need to manage billing, ask a project owner to grant you the owner role. See Teams & User Management for details on roles.

Dashboard Surface, Not API: All /billing/* routes are mounted on the dashboard host and gated by ProjectMiddleware, which accepts the browser session cookie or a dashboard-issued JWT. Project API keys of the form xero_<project>_<...> are accepted only by the inference router (/v1/*) and will return 401 against billing endpoints. Two-factor authentication is also required: any owner without 2FA enrolled is rejected by TwoFactorMiddleware. See Authentication & CSRF below.

Redirect Responses: Every POST /billing/* handler returns a 302 redirect (typically to /dashboard/billing or to a Stripe-hosted URL), not a JSON body. Programmatic clients must disable automatic redirect-following and read the Location header. There is no JSON read endpoint for billing state today; GET /billing renders the dashboard HTML page.

Subscription Plans

Xerotier.ai offers tiered subscription plans that determine your project's capabilities and resource limits. Visit /billing/plans to compare available plans.

Each plan exposes two independent fields on the project record. Do not conflate them:

  • Account tier (accountTier): A free or premium flag flipped by the Stripe subscription webhook. This is a billing-state marker only; it gates dashboard features and surfaces a "Premium" badge.
  • Service tier (tierId): The routing slug consumed by the inference router (for example free, gpu_nvidia_shared, self_hosted). This drives compute quotas, concurrent-request limits, and per-token pricing. The Stripe webhook does not change tierId; service tier is managed separately.
  • Default storage allocation: ProjectQuota.storageQuotaGB defaults to 4 GB on free projects and 500 GB on premium projects at project-creation time. The Stripe webhook does not rewrite this row after subscription changes.
  • Credit balance (creditsBalance): A floating-point USD balance on the project used for per-token inference billing on shared agents.

See Service Tiers for a comparison of tierId values and what each controls at the routing layer.

Two billing enums: The data model also tracks a BillingMode (free or paid) distinct from accountTier. BillingMode controls whether per-request token charges accumulate against creditsBalance; accountTier controls subscription state. They are independent and can disagree.

Checkout Flow

To subscribe or upgrade your plan:

  1. Navigate to /billing/plans and select your desired plan
  2. Click the subscribe button to initiate a Stripe checkout session
  3. Complete payment in the Stripe-hosted checkout page
  4. You are redirected to the success page upon completion

Post-checkout, the actual order of operations is:

  1. Stripe redirects the browser to ${XEROTIER_BASE_URL}/dashboard/billing?success=true. At this instant the project is still accountTier = .free; the success page is informational.
  2. Stripe asynchronously delivers a checkout.session.completed event to POST /webhooks/stripe. Only when this webhook is processed does accountTier flip to .premium.
  3. The Stripe customer ID is persisted on the project record at checkout-creation time (before redirecting to Stripe), not at completion.

Quotas are not auto-rewritten: The webhook flips accountTier only. It does not update tierId, ProjectQuota.storageQuotaGB, maxConcurrentRequests, or any rate-limit row. If you need premium-tier compute routing or expanded storage limits, those must be set explicitly through project administration.

Checkout Cancellation: If you cancel the Stripe checkout before completing payment, no changes are made to your subscription. You can restart the checkout process at any time.

Managing Your Subscription

The billing overview page at /billing shows your current plan, credit balance, and billing status.

Stripe Customer Portal

For detailed billing management, access the Stripe customer portal via /billing/portal. The portal allows you to:

  • Update payment methods (credit card, bank account)
  • Change billing information (name, address, tax ID)
  • View and download past invoices
  • Cancel, upgrade, downgrade, or pause your subscription directly

Checking Your Subscription

The canonical surface is the rendered dashboard page at /billing, which displays the current plan, credit balance, and subscription status. There is no JSON read endpoint; GET /billing returns the HTML dashboard. To inspect state programmatically from the dashboard origin, read it from the rendered template variables or wait for a JSON read endpoint to be added.

Cancelling Your Subscription

Cancellation is performed from the dashboard "Manage subscription" UI, which submits a CSRF-protected form to POST /billing/cancel with the active session cookie. The endpoint responds with 302 Found and Location: /dashboard/billing; there is no JSON body.

If you must invoke the endpoint from a script that already holds a dashboard session cookie and CSRF token, model the request the same way the form does and follow the redirect:

curl
# Requires an active dashboard session cookie and the matching X-CSRF-Token # rendered into the dashboard template. Returns 302 to /dashboard/billing. curl -i -X POST https://xerotier.ai/billing/cancel \ -b "xerotier_session=<your-session-cookie>" \ -H "X-CSRF-Token: <csrf-token-from-dashboard-template>"

Billing Routes

Endpoint Method Description
/billing GET Billing overview page
/billing/plans GET Plan selection and comparison
/billing/invoices GET Invoice history
/billing/checkout POST Create a Stripe checkout session for a plan
/billing/checkout/success GET Stripe redirects here after successful checkout
/billing/checkout/cancel GET Stripe redirects here when checkout is cancelled
/billing/credits POST Initiate a Stripe checkout session to purchase additional credits
/billing/cancel POST Cancel subscription (takes effect at end of billing period)
/billing/reactivate POST Reactivate a cancelled subscription before the period ends
/billing/portal POST Create and redirect to the Stripe customer portal session

Credits

Credits are the currency used for per-token inference billing on shared agents. Your project's credit balance is shown on the billing overview page and the usage dashboard.

How Credits Work

  • Each inference request on a shared service tier deducts from your credit balance based on token count and the model's per-token price.
  • Credits are tracked at the project level as creditsBalance (USD-denominated Double).
  • Self-hosted tier (tierId = self_hosted) projects use hourly node billing rather than per-token credit consumption.
  • Requests submitted with service_tier: priority on the chat-completions API are billed at a 1.25x token multiplier against creditsBalance; see Service Tiers for the multiplier table.

Subscription Statuses

A subscription can be in one of the following states:

Status Description
active Subscription is current and payment is up to date.
trialing Subscription is in a trial period. Full access is granted.
past_due Payment failed but Stripe is retrying. Access may be restricted.
canceled Subscription has been cancelled and the billing period has ended.
unpaid All payment retries have been exhausted. Service is suspended.
incomplete Initial payment requires further action (e.g., 3D Secure).
incomplete_expired Initial payment was not completed in time. Subscription did not activate.

Purchasing Credits

Purchase additional credits via POST /billing/credits. The endpoint creates a Stripe Checkout session and returns 302 Found with Location pointing at the Stripe-hosted payment URL. Credits are added to the project balance after Stripe delivers checkout.session.completed to the webhook handler. The amount must be between 5 and 500 USD (inclusive).

The handler accepts both application/json and application/x-www-form-urlencoded bodies; in practice the form is submitted by the dashboard UI with the session cookie and CSRF token.

curl (JSON body)
# Returns 302 to a Stripe-hosted checkout URL. # Requires an active dashboard session cookie and matching CSRF token. curl -i -X POST https://xerotier.ai/billing/credits \ -b "xerotier_session=<your-session-cookie>" \ -H "Content-Type: application/json" \ -H "X-CSRF-Token: <csrf-token-from-dashboard-template>" \ -d '{"amount": 50}'
curl (form-encoded body)
curl -i -X POST https://xerotier.ai/billing/credits \ -b "xerotier_session=<your-session-cookie>" \ -H "Content-Type: application/x-www-form-urlencoded" \ -H "X-CSRF-Token: <csrf-token-from-dashboard-template>" \ --data-urlencode "amount=50"

Delinquent Projects

If a Stripe invoice fails to pay, the invoice.payment_failed webhook stamps the project with isDelinquent = true and delinquentAt = <timestamp>. Delinquent projects:

  • Carry the isDelinquent and delinquentAt fields on the project record.
  • May have routing decisions or credit-gated endpoints refuse requests, returning the quota_exceeded error family (see Error Codes for the exact response shape).
  • Are cleared automatically when the next invoice.paid event is delivered, or manually by resolving the payment failure in the Stripe customer portal.

Invoices

View your invoice history at /billing/invoices. Invoices are generated by Stripe and include:

  • Subscription charges
  • Credit purchases
  • Payment status and date

For detailed invoice management (downloading PDFs, updating billing details), use the Stripe customer portal.

Cancellation & Reactivation

Cancelling Your Subscription

Cancel your subscription via POST /billing/cancel (from the dashboard "Manage subscription" UI). The handler sets cancelAtPeriodEnd = true on the Stripe subscription. When you cancel:

  • Your subscription remains active until the end of the current billing period.
  • Existing creditsBalance is preserved.
  • After the period ends, the customer.subscription.deleted webhook flips accountTier back to free.
  • tierId, ProjectQuota.storageQuotaGB, and rate-limit rows are not automatically rolled back by the cancellation webhook; only the accountTier flag changes.

Reactivating Your Subscription

If you cancelled but the billing period has not yet ended, you can reactivate via POST /billing/reactivate.

Known issue: The current POST /billing/reactivate handler flips the local cancelAtPeriodEnd flag back to false optimistically, but the corresponding Stripe API call does not currently include cancel_at_period_end=false in its payload. Until this is resolved, Stripe-side cancellation continues to schedule at period end, and the next customer.subscription.updated webhook will re-flip the local flag. To reactivate reliably today, open the Stripe customer portal (POST /billing/portal) and reactivate from there. See defect CC#38.

After Period Ends: If the billing period has already ended after cancellation, you must start a new subscription through the checkout flow.

Authentication & CSRF

All /billing/* routes are mounted on the dashboard group and protected by three layers, in order:

  1. ProjectMiddleware: Accepts a dashboard session cookie or a dashboard-issued JWT in Authorization: Bearer <jwt>. Project API keys (xero_<project>_<...>) are not accepted here; those are scoped to the inference router at /v1/*.
  2. TwoFactorMiddleware: Returns 401 unless the authenticated user has 2FA enrolled and the current session is 2FA-verified.
  3. Owner check: The handler calls requireOwner() on the active project membership. Non-owners receive 403.

State-changing requests additionally require an X-CSRF-Token header (or hidden form field) matching the token rendered into the dashboard template by CSRFMiddleware. There is no public CSRF-token issuance endpoint; the token is bound to the active dashboard session and surfaced through the rendered HTML.

Note that handlers return 302 redirects to /dashboard/billing* after most state changes. A browser following the redirect today may land on an unmounted path; see the release notes for the current routing fix status.

Frequently Asked Questions

Who can manage billing?

Only project owners can access billing pages and make changes. Team members with the "member" role do not have access to billing settings. See Teams & User Management for details.

What payment methods are accepted?

Xerotier.ai uses Stripe for payments. Stripe supports credit cards, debit cards, and other payment methods depending on your region. Add, remove, or default-select payment methods through the Stripe customer portal.

Do credits expire?

No. Credits are stored as a single creditsBalance value on the project and are not subject to time-based expiration. They persist across subscription state changes (including cancellation).

What happens when my credits run out?

Shared service-tier inference requests start returning the quota_exceeded error family once creditsBalance is depleted. Self-hosted tier (tierId = self_hosted) projects are unaffected because they bill hourly rather than per token. Purchase additional credits or move workloads to a different tier to restore service.

Can I get a refund?

Refunds are handled through Stripe. Open the Stripe customer portal via POST /billing/portal from your dashboard, then file the refund request from the relevant invoice. No refund endpoint is exposed by the router itself.