Nozle
API Reference

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
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
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:

FieldTypeRequiredDescription
external_idstringYesYour unique identifier for this customer
namestringNoCustomer display name
emailstringNoCustomer 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
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:

FieldTypeRequiredDescription
customer_idstringYesExternal customer ID
featurestringYesFeature being consumed
creditsnumberYesNumber 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
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:

ParameterTypeRequiredDescription
customer_idstringYesExternal customer ID
featurestringYesFeature 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
}
FieldTypeDescription
allowedbooleanWhether the customer can use this feature
reasonstringWhy the request was allowed or denied (e.g. within_limit, limit_exceeded, no_subscription)
usednumberUnits consumed in the current billing period
limitnumberMaximum units allowed by the plan
remainingnumberUnits still available
cost_per_use_centsnumberYour cost per unit (from cost models)
revenue_per_use_centsnumberWhat you charge per unit
margin_per_use_centsnumberProfit per unit
min_margin_percentnumberMinimum margin threshold configured for this feature, if any
cURL
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
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:

FieldTypeRequiredDescription
customer_idstringYesExternal customer ID
plan_codestringYesPlan to subscribe to
success_urlstringNoURL 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
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:

FieldTypeRequiredDescription
customer_idstringYesExternal customer ID
plan_codestringYesPlan to subscribe to
{
  "customer_id": "cust_123",
  "plan_code": "pro"
}

Response:

{
  "subscription_id": "sub_abc123",
  "status": "active"
}
cURL
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:

ParameterTypeRequiredDescription
customer_idstringYesExternal customer ID
cURL
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
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
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:

ParameterTypeRequiredDescription
customer_idstringYesExternal customer ID
cURL
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:

ParameterTypeDescription
idstringExternal customer ID
cURL
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:

ParameterTypeDescription
idstringExternal customer ID
cURL
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
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
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
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:

ParameterTypeFormatDescription
fromstringYYYY-MM-DDStart date (inclusive)
tostringYYYY-MM-DDEnd date (inclusive)

GET /api/v1/margin/summary

Get an aggregate margin summary across all customers and metrics.

Auth: sk_ only

cURL
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
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:

ParameterTypeRequiredDescription
granularitystringNoTime bucket size: hour, day, week, or month (default: day)
cURL
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
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:

HeaderDescription
X-Nozle-KeyYour Nozle secret key (sk_live_...)
X-Nozle-CustomerExternal customer ID to bill for this usage
AuthorizationCustomer's OpenAI API key (Bearer sk-...)
cURL
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:

HeaderDescription
X-Nozle-KeyYour Nozle secret key (sk_live_...)
X-Nozle-CustomerExternal customer ID to bill for this usage
AuthorizationCustomer's Anthropic API key
cURL
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
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
curl https://api.nozle.app/health

Response:

{
  "status": "ok"
}

GET /metrics/queues

Asynq queue metrics in Prometheus format. Use this for monitoring background job processing.

Auth: None

cURL
curl https://api.nozle.app/metrics/queues

On this page