Billing endpoints
/billing/* and /platform/organizations/{id}/billing/* groups — plan lookup, usage/quota overview, checkout session generation and customer-portal access.
The implementation behind may be Stripe, the mock gateway (dev) or disabled — depends on NATIVE_WAZUH_BILLING_GATEWAY.
Endpoint table
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /billing/plans/public | Public | Lists plans visible to the public. |
| POST | /billing/webhooks/stripe | Stripe signature | Internal — not called by clients. |
| GET | /platform/organizations/{organization_id}/billing | Session | Subscription + usage overview. |
| POST | /platform/organizations/{organization_id}/billing/checkout | Session | Creates a checkout session (upgrade). |
| POST | /platform/organizations/{organization_id}/billing/portal | Session | Creates a URL for the customer portal. |
GET /billing/plans/public
Lists available plans. Public route — useful for a pricing page that doesn't require login.
Response 200: array of BillingPlanRecord:
[
{
"id": "plan_starter",
"code": "starter",
"name": "Starter",
"description": "For small teams starting with RuleForge.",
"currency": "usd",
"monthly_price_cents": 4900,
"annual_price_cents": 49000,
"minimum_seats": 1,
"maximum_seats": 10,
"status": "active",
"is_public": true,
"contact_sales": false,
"display_order": 1,
"features": {
"api_access": true,
"ai_assistant": false,
"sso": false,
"audit_log_retention_days": 30
},
"limits": {
"projects": 3,
"analysis_runs_per_month": 10000,
"api_requests_per_minute": 120,
"webhooks": 5
},
"metadata": {},
"created_at": "…",
"updated_at": "…"
},
{ "id": "plan_team", "code": "team", "...": "..." },
{ "id": "plan_enterprise", "code": "enterprise", "contact_sales": true, "...": "..." }
]
Plans with contact_sales=true have no public price — use portal or reach out to sales directly to activate.
GET /.../billing
Full billing overview for the organization.
Auth: user session.
Response 200 (OrganizationBillingOverviewResponse):
{
"organization_id": "org_...",
"organization_name": "Acme Security",
"subscription": {
"id": "sub_...",
"organization_id": "org_...",
"plan_id": "plan_team",
"status": "active",
"billing_interval": "month",
"seat_count": 5,
"gateway_provider": "stripe",
"gateway_subscription_id": "sub_Nz…",
"trial_ends_at": null,
"current_period_start": "2026-04-01T00:00:00Z",
"current_period_end": "2026-05-01T00:00:00Z",
"cancel_at": null,
"canceled_at": null,
"created_at": "…",
"updated_at": "…"
},
"plan": { "...": "BillingPlanRecord" },
"available_plans": [ "BillingPlanRecord[]" ],
"usage": [
{
"metric": "analysis_runs",
"period_start": "2026-04-01T00:00:00Z",
"period_end": "2026-05-01T00:00:00Z",
"consumed": 2741,
"limit": 10000
},
{
"metric": "projects",
"consumed": 4,
"limit": 10
}
],
"gateway": {
"provider": "stripe",
"status": "connected",
"publishable_key": "pk_live_..."
}
}
subscription.status∈ unpaid.usage[]— metrics consumed in the current period.limit=nullmeans unlimited.gateway.publishable_keyis only populated when the gateway is Stripe.
POST /.../billing/checkout
Creates a checkout session (typically Stripe Checkout) for the user to complete the subscription/upgrade.
Request body (BillingCheckoutRequest):
{
"plan_id": "plan_team",
"billing_interval": "month",
"seat_count": 5
}
plan_id— target plan ID.billing_interval—"month"or"year".seat_count— 1 to 100,000.
Response 200 (BillingCheckoutResponse):
{
"provider": "stripe",
"url": "https://checkout.stripe.com/c/pay/cs_...",
"session_id": "cs_..."
}
Typical client flow:
- Frontend calls this endpoint.
- Receives
urland redirects the user. - User completes payment on Stripe.
- Stripe calls back
/billing/webhooks/stripe(not documented here — internal webhook). - RuleForge updates
subscription.statustoactive. - The frontend receives
?checkout=success(configurable viaNATIVE_WAZUH_BILLING_SUCCESS_PATH).
Errors:
400 bad_request— plan doesn't exist or is not public.402 payment_required— gateway unconfigured or unavailable.403 forbidden— organization incanceledstate.
POST /.../billing/portal
Creates a URL for the customer portal (Stripe Customer Portal), where the user can:
- View invoice history.
- Update payment method.
- Cancel subscription.
- Downgrade.
Request body (BillingPortalRequest):
{ "return_path": "/settings/billing" }
return_path— absolute path (starts with/) to return to after leaving the portal.
Response 200 (BillingPortalResponse):
{
"provider": "stripe",
"url": "https://billing.stripe.com/p/session/bps_..."
}
Client redirects the user to url.
Stripe internal webhook
POST /billing/webhooks/stripe receives Stripe events (invoice.paid, customer.subscription.updated, etc.) to keep local state in sync. Authentication is via the Stripe-Signature signature verified with NATIVE_WAZUH_STRIPE_WEBHOOK_SECRET.
You don't need to call this endpoint — it's configured in the Stripe dashboard by the platform operator.
Shared structures
BillingPlanRecord
See the example above at GET /billing/plans/public.
OrganizationSubscriptionRecord
{
"id": "string",
"organization_id": "string",
"plan_id": "string",
"status": "trialing|active|past_due|canceled|incomplete|incomplete_expired|unpaid",
"billing_interval": "month|year",
"seat_count": 1,
"gateway_provider": "string",
"gateway_subscription_id": "string|null",
"trial_ends_at": "ISO 8601|null",
"current_period_start": "ISO 8601|null",
"current_period_end": "ISO 8601|null",
"cancel_at": "ISO 8601|null",
"canceled_at": "ISO 8601|null",
"created_at": "ISO 8601",
"updated_at": "ISO 8601"
}
BillingUsageMetric
{
"metric": "string",
"period_start": "ISO 8601",
"period_end": "ISO 8601",
"consumed": 0,
"limit": 0
}
Common metrics: analysis_runs, projects, api_requests, webhooks, members.
Related errors
code | When |
|---|---|
plan_quota_exceeded | Some operation hits the plan limit (analysis_runs, webhooks, etc.). |
billing_gateway_disabled | NATIVE_WAZUH_BILLING_GATEWAY=disabled. |
billing_subscription_required | Operation requires an active plan and the organization has no valid subscription. |
Related links
- Billing and plans — guide
- Limits and usage — request consumption.