QANATIX
API Reference

Billing API

Stripe-powered subscription billing and usage-based overage.

Billing API

QANATIX uses Stripe for subscription billing. Free users never interact with Stripe. Paid plans (Pro/Scale) include a base subscription plus metered overage for searches and ingestions.

Billing endpoints are only available in cloud mode (DEPLOYMENT_MODE=cloud). Self-hosted deployments return 404 for all billing endpoints.

Plans

PlanPriceSearchesSearch overageIngestionsIngestion overageEntities
Free$01,000/moBlocked500/moBlocked1,000
Pro$199/mo50,000/mo$1.50/1K10,000/mo$6.00/1K100,000
Scale$399/mo500,000/mo$1.00/1K100,000/mo$4.00/1KUnlimited
EnterpriseCustomUnlimitedCustomUnlimitedCustomUnlimited

Free plan has hard caps — requests return HTTP 402 when limits are reached. Paid plans allow overages, billed at end of month via Stripe.

How billing works

  1. Sign up — user gets a free plan with no Stripe customer
  2. Upgrade — user clicks "Upgrade to Pro" in the dashboard, redirected to Stripe Checkout
  3. Payment — Stripe processes payment, webhook activates the plan
  4. Usage tracking — every search/ingestion increments a Redis counter (existing infrastructure)
  5. Meter events — every 5 minutes, counters are flushed to Stripe Billing Meters
  6. Invoicing — Stripe generates invoices with base fee + metered overage (tiered pricing handles included quotas)
  7. Self-service — user manages billing via Stripe Customer Portal (invoices, payment method, cancel)

Checkout

POST /portal/billing/checkout

Create a Stripe Checkout session for plan upgrade. Redirects the user to Stripe's hosted payment page.

Requires: JWT authentication (portal)

curl -X POST https://api.qanatix.com/api/v1/portal/billing/checkout \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "plan": "pro",
    "success_url": "https://qanatix.com/dashboard/settings?upgraded=1",
    "cancel_url": "https://qanatix.com/dashboard/settings"
  }'

Request body

FieldTypeRequiredDescription
planstringYesTarget plan: "pro" or "scale"
success_urlstringYesRedirect URL after successful payment
cancel_urlstringYesRedirect URL if user cancels

Response (200)

{
  "checkout_url": "https://checkout.stripe.com/c/pay/cs_test_..."
}

Redirect the user to checkout_url. After payment, Stripe redirects to success_url.

Customer Portal

POST /portal/billing/portal

Create a Stripe Customer Portal session for self-service billing management.

Requires: JWT authentication + existing Stripe customer (must be on a paid plan)

curl -X POST https://api.qanatix.com/api/v1/portal/billing/portal \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "return_url": "https://qanatix.com/dashboard/settings"
  }'

Response (200)

{
  "portal_url": "https://billing.stripe.com/p/session/..."
}

Open portal_url in a new tab. The portal lets users:

  • View invoice history
  • Update payment method
  • Cancel subscription
  • Change plan

Billing Status

GET /portal/billing/status

Get current billing status for the authenticated user's tenant.

curl https://api.qanatix.com/api/v1/portal/billing/status \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Response (200)

{
  "plan": "pro",
  "has_subscription": true,
  "billing_period_end": "2026-04-08T00:00:00Z",
  "is_cloud": true
}
FieldDescription
planCurrent plan: free, pro, scale, enterprise
has_subscriptionWhether a Stripe subscription exists
billing_period_endEnd of current billing period (null for free)
is_cloudWhether this is a cloud deployment (billing available)

Webhook

POST /billing/stripe/webhook

Stripe webhook receiver. Signature-verified, no JWT/API key auth required.

This endpoint is called by Stripe to notify us of subscription lifecycle events. It is not meant to be called by users.

Events handled

EventAction
checkout.session.completedActivate plan, save subscription ID
customer.subscription.updatedSync plan and billing period
customer.subscription.deletedRevert to free plan
invoice.paidAudit log
invoice.payment_failedWarning log (Stripe handles retry)

Usage metering

Usage is reported to Stripe Billing Meters automatically:

  • Two meters: qanatix_search (searches) and qanatix_ingestion (ingestions)
  • Frequency: every 5 minutes via SAQ cron job
  • Scope: only paid tenants with a Stripe customer ID
  • Tiered pricing: Stripe handles "first N included free" via tiered price configuration
  • Source of truth: Postgres usage_records table (Stripe meter events are fire-and-forget)

Self-hosted mode

When DEPLOYMENT_MODE=self_hosted:

  • All /billing/* endpoints return 404
  • All /portal/billing/* endpoints return 404
  • No Stripe API calls are ever made
  • Usage tracking to Redis + Postgres continues normally
  • Operators see usage via GET /portal/usage or GET /usage

Error responses

StatusMeaning
400Invalid request (bad plan, missing customer, invalid signature)
404Billing not available (self-hosted mode)
422Validation error (invalid plan name)

On this page