Billing API
The Billing API allows you to create Stripe checkout sessions, open the customer billing portal, and receive lifecycle events from Stripe. All billing actions are scoped to your tenant.
Billing endpoints are prefixed with /api/v1/billing. The webhook endpoint does not require an API key — it is authenticated by Stripe's signature header instead.
Authentication
All billing endpoints except POST /api/v1/billing/webhook require your API key:
X-API-Key: nat_pk_your_api_key_herePlans overview
| Plan | Scans / month | Features | Price |
|---|---|---|---|
| Free | 50 | Functional testing | $0 |
| Pro | 500 | Functional + security scanning | $49/mo |
| Team | 2,000 | All features + webhooks + priority support | $149/mo |
| Enterprise | Unlimited | All features + SSO/SAML + dedicated support | Custom |
When you exceed your monthly scan quota, subsequent scan requests return 402 Payment Required with the error code QUOTA_EXCEEDED. Upgrade your plan or wait for the quota reset date.
Endpoints
Create checkout session
Creates a Stripe-hosted checkout session for upgrading to a paid plan. The response contains a URL to redirect the user to.
POST /api/v1/billing/checkoutRequest body:
| Field | Type | Required | Description |
|---|---|---|---|
price_id | string | ✅ | Stripe Price ID (STRIPE_PRICE_ID_PRO or STRIPE_PRICE_ID_TEAM) |
success_url | string | ✅ | URL to redirect after successful payment |
cancel_url | string | ✅ | URL to redirect if checkout is cancelled |
curl -X POST https://api.nat-testing.io/api/v1/billing/checkout \
-H "X-API-Key: $NAT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"price_id": "price_pro_monthly",
"success_url": "https://app.nat-testing.io/dashboard?checkout=success",
"cancel_url": "https://app.nat-testing.io/pricing"
}'Response 200 OK:
{
"checkout_url": "https://checkout.stripe.com/c/pay/cs_test_..."
}Error responses:
| Status | Code | Description |
|---|---|---|
401 | UNAUTHORIZED | Missing or invalid API key |
400 | INVALID_PRICE_ID | The provided price_id does not exist |
Open billing portal
Creates a Stripe customer portal session so the tenant can manage their subscription, update payment methods, download invoices, or cancel their plan.
POST /api/v1/billing/portalRequest body:
| Field | Type | Required | Description |
|---|---|---|---|
return_url | string | ✅ | URL to return to after leaving the portal |
curl -X POST https://api.nat-testing.io/api/v1/billing/portal \
-H "X-API-Key: $NAT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"return_url": "https://app.nat-testing.io/dashboard"
}'Response 200 OK:
{
"portal_url": "https://billing.stripe.com/p/session/..."
}Get billing status
Returns the current billing plan and subscription status for your tenant.
GET /api/v1/billing/statuscurl https://api.nat-testing.io/api/v1/billing/status \
-H "X-API-Key: $NAT_API_KEY"Response 200 OK:
{
"plan": "pro",
"subscription_status": "active",
"current_period_end": "2025-02-01T00:00:00Z",
"cancel_at_period_end": false,
"stripe_customer_id": "cus_abc123xyz"
}Response fields:
| Field | Type | Description |
|---|---|---|
plan | string | Current plan: free, pro, team, enterprise |
subscription_status | string | Stripe subscription status: active, trialing, past_due, canceled |
current_period_end | string | ISO 8601 timestamp when the current billing period ends |
cancel_at_period_end | boolean | Whether the subscription cancels at the end of the current period |
stripe_customer_id | string | Stripe customer ID (useful for support requests) |
Error responses:
| Status | Code | Description |
|---|---|---|
401 | UNAUTHORIZED | Missing or invalid API key |
Stripe webhook receiver
This endpoint receives lifecycle events from Stripe. It is called automatically by Stripe — you do not need to call it directly.
POST /api/v1/billing/webhookThis endpoint does not use the X-API-Key header. It is authenticated using the Stripe-Signature header and the STRIPE_WEBHOOK_SECRET environment variable.
Handled event types:
| Stripe event | NAT action |
|---|---|
checkout.session.completed | Creates or updates tenant record, issues API key |
customer.subscription.updated | Updates tenant plan tier and quota limits |
customer.subscription.deleted | Downgrades tenant to Free plan |
invoice.payment_failed | Sends payment failure notification to tenant |
invoice.paid | Subscription renewed; quota reset for the new period |
charge.refunded | Refund processed; plan downgraded to Free if applicable |
Environment variables required:
| Variable | Description |
|---|---|
STRIPE_SECRET_KEY | Stripe secret API key |
STRIPE_WEBHOOK_SECRET | Stripe webhook signing secret (from Stripe dashboard) |
STRIPE_PRICE_ID_PRO | Stripe Price ID for the Pro plan |
STRIPE_PRICE_ID_TEAM | Stripe Price ID for the Team plan |
Sync tenant
Syncs a tenant record from the nat-app frontend into the NAT engine. This is called internally by nat-app after a user signs up or completes Stripe checkout, and is not typically called directly by end users.
POST /api/v1/billing/sync-tenantThis endpoint is used by the nat-app frontend to provision or update a tenant in the engine after signup or plan upgrade. End users accessing NAT via the SaaS dashboard do not need to call this endpoint directly — it is handled automatically.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
tenant_id | string | ✅ | Unique tenant identifier |
email | string | ✅ | Tenant email address |
plan | string | ✅ | Plan tier: free, pro, team, enterprise |
stripe_customer_id | string | Stripe customer ID | |
stripe_subscription_id | string | Stripe subscription ID |
Response 200 OK:
{
"tenant_id": "tenant_abc123",
"api_key": "nat_pk_...",
"plan": "pro",
"status": "active"
}Error responses:
| Status | Code | Description |
|---|---|---|
400 | INVALID_REQUEST | Missing required fields |
409 | TENANT_EXISTS | Tenant already exists with a different configuration |
See also
- Billing & Plans guide — quota enforcement, upgrades, and usage alerts
- Billing troubleshooting — API key issues, webhook failures, and more
- Usage & Quotas API — monitor current usage