Endpoints
Complete Nozle API reference
Base URL: https://api.nozle.app/api/v1
All requests and responses use JSON unless otherwise noted. Pass your API key as a Bearer token in the Authorization header (see Authentication).
Authentication
These endpoints are public and do not require an API key.
POST /api/v1/auth/send-otp
Send a one-time password to a user's email for login.
Request body:
{
"email": "user@example.com"
}Response:
{
"message": "OTP sent"
}curl -X POST https://api.nozle.app/api/v1/auth/send-otp \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com"}'POST /api/v1/auth/verify-otp
Verify a one-time password and receive an access token.
Request body:
{
"email": "user@example.com",
"otp": "123456"
}Response:
{
"token": "eyJhbGciOi..."
}POST /api/v1/auth/signup
Create a new account.
Request body:
{
"email": "user@example.com",
"name": "Jane Doe"
}Response:
{
"message": "Account created"
}Health & Utility
GET /api/v1/ping
Authenticated health check. Verifies the engine is running and the database is reachable.
Auth: pk_ or sk_
curl https://api.nozle.app/api/v1/ping \
-H "Authorization: Bearer sk_live_your_key"Response:
{
"ok": true,
"engine": "ok"
}If the database is unreachable, engine will be "error" and ok will be false.
Customers
POST /api/v1/customers
Create or update a customer. Uses Lago's upsert semantics — if a customer with the given external_id exists, it updates their name/email; otherwise it creates a new one.
Auth: sk_ only
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
external_id | string | Yes | Your unique identifier for this customer |
name | string | No | Customer display name |
email | string | No | Customer email address |
{
"external_id": "cust_123",
"name": "Acme Corp",
"email": "billing@acme.com"
}Response:
{
"external_id": "cust_123",
"name": "Acme Corp",
"email": "billing@acme.com"
}curl -X POST https://api.nozle.app/api/v1/customers \
-H "Authorization: Bearer sk_live_your_secret_key" \
-H "Content-Type: application/json" \
-d '{"external_id": "cust_123", "name": "Acme Corp", "email": "billing@acme.com"}'Credits
POST /api/v1/check-and-deduct
Atomically check if a customer has sufficient credit balance and deduct in a single database transaction. Uses row-level locking to prevent race conditions under concurrent requests.
Auth: sk_ only
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
customer_id | string | Yes | External customer ID |
feature | string | Yes | Feature being consumed |
credits | number | Yes | Number of credits to deduct (must be positive) |
{
"customer_id": "cust_123",
"feature": "code_completion",
"credits": 5
}Response (sufficient balance):
{
"allowed": true,
"remaining": 95.0
}Response (insufficient balance):
{
"allowed": false,
"remaining": 3.0
}When allowed is false, no credits are deducted — the balance is returned as-is.
curl -X POST https://api.nozle.app/api/v1/check-and-deduct \
-H "Authorization: Bearer sk_live_your_secret_key" \
-H "Content-Type: application/json" \
-d '{"customer_id": "cust_123", "feature": "code_completion", "credits": 5}'Entitlements
GET /api/v1/can
Check whether a customer is entitled to use a feature under their current plan. This is the primary gating endpoint -- call it before executing any metered or limited operation.
Auth: pk_ or sk_
Query parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
customer_id | string | Yes | External customer ID |
feature | string | Yes | Feature key to check (matches a billable metric code) |
Response:
{
"allowed": true,
"reason": "within_limit",
"used": 1500,
"limit": 10000,
"remaining": 8500,
"cost_per_use_cents": 0.12,
"revenue_per_use_cents": 0.50,
"margin_per_use_cents": 0.38,
"min_margin_percent": 25.0
}| Field | Type | Description |
|---|---|---|
allowed | boolean | Whether the customer can use this feature |
reason | string | Why the request was allowed or denied (e.g. within_limit, limit_exceeded, no_subscription) |
used | number | Units consumed in the current billing period |
limit | number | Maximum units allowed by the plan |
remaining | number | Units still available |
cost_per_use_cents | number | Your cost per unit (from cost models) |
revenue_per_use_cents | number | What you charge per unit |
margin_per_use_cents | number | Profit per unit |
min_margin_percent | number | Minimum margin threshold configured for this feature, if any |
curl "https://api.nozle.app/api/v1/can?customer_id=cust_123&feature=api_calls" \
-H "Authorization: Bearer pk_live_your_key"When allowed is false, the reason field tells you why -- use it to show contextual upgrade prompts in your UI.
GET /api/v1/auth/centrifugo-token
Get a WebSocket authentication token for real-time entitlement updates.
Auth: pk_ or sk_
Response:
{
"token": "eyJhbGciOi..."
}Plans & Checkout
GET /api/v1/plans
List all available plans. Use this to build pricing tables.
Auth: pk_ or sk_
Response:
[
{
"code": "starter",
"name": "Starter",
"amount_cents": 2900,
"amount_currency": "USD",
"interval": "monthly"
},
{
"code": "pro",
"name": "Pro",
"amount_cents": 9900,
"amount_currency": "USD",
"interval": "monthly"
}
]curl https://api.nozle.app/api/v1/plans \
-H "Authorization: Bearer pk_live_your_key"POST /api/v1/checkout
Create a Stripe Checkout session. Returns a client_secret to embed Stripe's checkout UI on the client side.
Auth: pk_ or sk_
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
customer_id | string | Yes | External customer ID |
plan_code | string | Yes | Plan to subscribe to |
success_url | string | No | URL to redirect after successful payment |
{
"customer_id": "cust_123",
"plan_code": "pro",
"success_url": "https://app.example.com/dashboard"
}Response:
{
"client_secret": "cs_test_...",
"invoice_id": "inv_abc123",
"amount_cents": 9900,
"currency": "USD"
}curl -X POST https://api.nozle.app/api/v1/checkout \
-H "Authorization: Bearer pk_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "cust_123",
"plan_code": "pro",
"success_url": "https://app.example.com/dashboard"
}'POST /api/v1/subscribe
Create a subscription directly without going through Stripe Checkout. Use this for server-side subscription management when payment is already on file.
Auth: sk_ only
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
customer_id | string | Yes | External customer ID |
plan_code | string | Yes | Plan to subscribe to |
{
"customer_id": "cust_123",
"plan_code": "pro"
}Response:
{
"subscription_id": "sub_abc123",
"status": "active"
}curl -X POST https://api.nozle.app/api/v1/subscribe \
-H "Authorization: Bearer sk_live_your_secret_key" \
-H "Content-Type: application/json" \
-d '{"customer_id": "cust_123", "plan_code": "pro"}'Billing
GET /api/v1/billing/status
Get the current billing status for a customer, including their active subscription and plan details.
Auth: pk_ or sk_
Query parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
customer_id | string | Yes | External customer ID |
curl "https://api.nozle.app/api/v1/billing/status?customer_id=cust_123" \
-H "Authorization: Bearer pk_live_your_key"GET /api/v1/subscriptions/current
Alias for /api/v1/billing/status. Returns the same response.
Auth: pk_ or sk_
POST /api/v1/billing/upgrade
Upgrade a customer's plan. Handles proration automatically.
Auth: pk_ or sk_
Request body:
{
"customer_id": "cust_123",
"plan_code": "enterprise"
}curl -X POST https://api.nozle.app/api/v1/billing/upgrade \
-H "Authorization: Bearer pk_live_your_key" \
-H "Content-Type: application/json" \
-d '{"customer_id": "cust_123", "plan_code": "enterprise"}'POST /api/v1/subscriptions/change
Alias for /api/v1/billing/upgrade. Same request and response format.
Auth: pk_ or sk_
POST /api/v1/subscriptions/preview
Preview what a plan change would cost before committing. Returns proration details.
Auth: pk_ or sk_
Request body:
{
"customer_id": "cust_123",
"plan_code": "enterprise"
}DELETE /api/v1/subscriptions/{id}
Cancel a subscription. Takes effect at the end of the current billing period.
Auth: pk_ or sk_
Request body (optional):
{
"reason": "Switching to a competitor"
}curl -X DELETE https://api.nozle.app/api/v1/subscriptions/sub_abc123 \
-H "Authorization: Bearer pk_live_your_key" \
-H "Content-Type: application/json" \
-d '{"reason": "No longer needed"}'GET /api/v1/invoices
List invoices for a customer.
Auth: pk_ or sk_
Query parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
customer_id | string | Yes | External customer ID |
curl "https://api.nozle.app/api/v1/invoices?customer_id=cust_123" \
-H "Authorization: Bearer pk_live_your_key"GET /api/v1/credits/{id}/balance
Get the current credit balance for a customer.
Auth: pk_ or sk_
Path parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | External customer ID |
curl https://api.nozle.app/api/v1/credits/cust_123/balance \
-H "Authorization: Bearer pk_live_your_key"GET /api/v1/credits/{id}/transactions
Get credit transaction history for a customer.
Auth: pk_ or sk_
Path parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | External customer ID |
curl https://api.nozle.app/api/v1/credits/cust_123/transactions \
-H "Authorization: Bearer pk_live_your_key"Cost Models
Manage cost models that define your per-unit costs for margin calculations.
All cost model endpoints require a secret key (sk_). Publishable keys will receive a 403 Forbidden response.
POST /api/v1/cost-models
Create a new cost model.
Auth: sk_ only
curl -X POST https://api.nozle.app/api/v1/cost-models \
-H "Authorization: Bearer sk_live_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"name": "GPT-4 API Cost",
"metric_code": "api_calls",
"cost_per_unit_cents": 0.12,
"model": "gpt-4"
}'GET /api/v1/cost-models
List all cost models.
Auth: sk_ only
curl https://api.nozle.app/api/v1/cost-models \
-H "Authorization: Bearer sk_live_your_secret_key"GET /api/v1/cost-models/{id}
Get a specific cost model by ID.
Auth: sk_ only
DELETE /api/v1/cost-models/{id}
Delete a cost model.
Auth: sk_ only
curl -X DELETE https://api.nozle.app/api/v1/cost-models/cm_abc123 \
-H "Authorization: Bearer sk_live_your_secret_key"Margin Intelligence
Real-time margin analytics across customers, metrics, plans, and AI models.
All margin endpoints require a secret key (sk_). These endpoints expose cost data that should never be visible to your end users.
All margin endpoints accept optional date range query parameters:
| Parameter | Type | Format | Description |
|---|---|---|---|
from | string | YYYY-MM-DD | Start date (inclusive) |
to | string | YYYY-MM-DD | End date (inclusive) |
GET /api/v1/margin/summary
Get an aggregate margin summary across all customers and metrics.
Auth: sk_ only
curl "https://api.nozle.app/api/v1/margin/summary?from=2026-01-01&to=2026-01-31" \
-H "Authorization: Bearer sk_live_your_secret_key"GET /api/v1/margin/customers
Get margin breakdown by customer.
Auth: sk_ only
curl "https://api.nozle.app/api/v1/margin/customers?from=2026-01-01&to=2026-01-31" \
-H "Authorization: Bearer sk_live_your_secret_key"GET /api/v1/margin/metrics
Get margin breakdown by billable metric.
Auth: sk_ only
GET /api/v1/margin/plans
Get margin breakdown by plan.
Auth: sk_ only
GET /api/v1/margin/models
Get margin breakdown by AI model (e.g. GPT-4, Claude, Gemini).
Auth: sk_ only
GET /api/v1/margin/trend
Get margin trend over time.
Auth: sk_ only
Additional query parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
granularity | string | No | Time bucket size: hour, day, week, or month (default: day) |
curl "https://api.nozle.app/api/v1/margin/trend?from=2026-01-01&to=2026-01-31&granularity=week" \
-H "Authorization: Bearer sk_live_your_secret_key"POST /api/v1/margin/simulate
Simulate margin impact of pricing or cost changes before applying them.
Auth: sk_ only
curl -X POST "https://api.nozle.app/api/v1/margin/simulate" \
-H "Authorization: Bearer sk_live_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"plan_code": "pro",
"new_price_cents": 12900,
"cost_change_percent": 10
}'LLM Proxy
Reverse proxy routes that forward LLM requests to OpenAI or Anthropic and automatically track token usage. The proxy reads the response to extract { model, input_tokens, output_tokens, latency_ms } and fires an internal nozle.track() event -- no SDK wrapper needed on your side.
LLM proxy routes use X-Nozle-Key for Nozle authentication, not the Authorization header. The Authorization header is reserved for the customer's own LLM provider API key, which the proxy forwards untouched to the provider.
/api/v1/proxy/openai/v1/*
Proxies all requests to https://api.openai.com/v1/*. Any OpenAI-compatible endpoint works (chat completions, embeddings, etc.).
Auth: X-Nozle-Key header (sk_ only)
Required headers:
| Header | Description |
|---|---|
X-Nozle-Key | Your Nozle secret key (sk_live_...) |
X-Nozle-Customer | External customer ID to bill for this usage |
Authorization | Customer's OpenAI API key (Bearer sk-...) |
curl -X POST https://api.nozle.app/api/v1/proxy/openai/v1/chat/completions \
-H "X-Nozle-Key: sk_live_your_nozle_key" \
-H "X-Nozle-Customer: cust_123" \
-H "Authorization: Bearer sk-customer-openai-key" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o",
"messages": [{"role": "user", "content": "Hello"}]
}'/api/v1/proxy/anthropic/v1/*
Proxies all requests to https://api.anthropic.com/v1/*.
Auth: X-Nozle-Key header (sk_ only)
Required headers:
| Header | Description |
|---|---|
X-Nozle-Key | Your Nozle secret key (sk_live_...) |
X-Nozle-Customer | External customer ID to bill for this usage |
Authorization | Customer's Anthropic API key |
curl -X POST https://api.nozle.app/api/v1/proxy/anthropic/v1/messages \
-H "X-Nozle-Key: sk_live_your_nozle_key" \
-H "X-Nozle-Customer: cust_123" \
-H "x-api-key: customer-anthropic-key" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-20250514",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello"}]
}'The proxy returns the provider's response unchanged. Your customer's API key is forwarded but never stored. Token usage is extracted from the response and tracked automatically as a billing event on the llm_tokens metric.
Admin
POST /api/v1/admin/reload
Force-reload all server caches (plans, entitlements, cost models). Use after making changes directly in the dashboard.
Auth: sk_ only
curl -X POST https://api.nozle.app/api/v1/admin/reload \
-H "Authorization: Bearer sk_live_your_secret_key"Webhooks
POST /webhooks/stripe
Stripe webhook endpoint. Receives payment events from Stripe to keep subscription and invoice state in sync.
Auth: Validated via Stripe webhook signature (not API key). Configure your Stripe webhook secret in the Nozle dashboard.
See the Webhooks page for setup instructions and supported event types.
Utility
GET /health
Health check endpoint. Returns 200 if the API is running.
Auth: None
curl https://api.nozle.app/healthResponse:
{
"status": "ok"
}GET /metrics/queues
Asynq queue metrics in Prometheus format. Use this for monitoring background job processing.
Auth: None
curl https://api.nozle.app/metrics/queues