Documentation
SDK reference, cost tracking, credits, monetisation, and platform behaviour.
Nasca never estimates token counts. Every cost figure in your dashboard comes from the token usage that the AI provider returns in its response — not from counting characters, words, or approximations.
For non-streaming calls, the provider returns a usage object in the response body. Nasca reads this after the call completes.
For streaming calls, usage is emitted in the stream itself. For OpenAI, Nasca injects stream_options: { include_usage: true } so that a final synthetic chunk carries complete token counts. For Anthropic, usage is split across two events:
message_start carries input_tokensmessage_delta carries output_tokensThe SDK accumulates usage across all chunks, so both values are captured regardless of which event they arrive in.
Once Nasca has the token counts, it multiplies them by the per-token price for that model:
cost = (input_tokens / 1000) × input_price_per_1k
+ (output_tokens / 1000) × output_price_per_1kThe result is accumulated in a per-user Redis counter and written to your Supabase database for dashboard display.
Nasca looks up model pricing in two places, checked in order:
openai/gpt-4o or meta-llama/llama-3.1-70b.If a model is not found in either source, Nasca logs the event with a cost of $0.00 and sets an unrecognised_model flag. A banner in your dashboard will prompt you to add custom pricing for that model.
Models starting with gpt- are looked up in the OpenAI pricing table. Models starting with claude- are looked up in the Anthropic pricing table. No configuration is required for these providers.
OpenRouter model IDs typically look like openai/gpt-4o. These do not start with gpt- or claude-, so they are not automatically matched.
To enable cost tracking for OpenRouter models, add each model to Settings → Custom Model Pricing. The model name must match exactly the string you pass to the SDK.
Cost figures reflect the token counts reported by the provider and the prices configured in Nasca. They are not guaranteed to exactly match your provider invoice, which may include volume discounts or caching credits. Nasca costs are a close approximation suitable for per-user budgeting decisions.
Credits are dollar-denominated. When a user purchases a credit pack, Nasca adds an AI spend balance (in USD) to their account in Redis. Each AI call deducts the calculated cost from that balance.
End-users never see raw AI spend figures. Instead, Nasca shows them the amount they originally paid depleting proportionally. For example: a user who paid $10 for a pack that gives $6 in AI spend sees a “$10 balance” that depletes in proportion to AI usage, not the $6 internal figure.
Credit packs have a minimum price of $5.00. This ensures the 2% platform fee ($0.10 minimum) is always meaningful and that Stripe processing costs are well covered.
Nasca supports two types of credit pack:
Nasca selects the cheapest active fixed pack first when generating a checkout URL. If no fixed packs are active, it falls back to the cheapest flexible top-up.
Subscription plans let your end-users pay a recurring fee to unlock a higher monthly AI spend limit.
To configure one: create a recurring price in your Stripe dashboard (on your connected account), then paste the price ID into Settings → Monetisation → Subscription plans. Set the monthly AI spend limit that users on this plan will receive.
When a user subscribes via a Nasca-generated upgrade prompt, Nasca receives the Stripe webhook and immediately raises their monthly limit. No additional webhook configuration is needed. If a subscription lapses, the limit returns to the user's default tier.
Nasca uses Stripe Connect to route payments directly to your Stripe account. You are the merchant on record for all transactions; Nasca never holds your revenue.
To connect: go to Settings → Stripe Connect and click “Connect Stripe”. You will be redirected to Stripe to authorise the connection. Nasca requests read_write scope to create checkout sessions and receive webhooks on your behalf.
End-users pay through Stripe's standard hosted checkout page. Nasca creates the session on your connected account and passes a 2% application fee. Your bank account receives the remaining 98% when Stripe settles the payment.
The 2% platform fee applies to:
Recurring subscription renewals after the first month are not subject to the Nasca platform fee. Only conversions that originate from a Nasca-hosted checkout session count.
Nasca charges a 2% platform fee on the gross amount of each qualifying conversion. The fee is deducted from the checkout session as a Stripe application fee — Stripe transfers it to Nasca automatically. You receive 98% of the payment net of Stripe's own processing fee (~2.9% + $0.30).
For a $10 pack: user pays $10. Stripe takes ~$0.59. Nasca takes $0.20. You receive ~$9.21.
The 2% Nasca fee is always less than half of Stripe's processing fee. The $5.00 minimum pack price ensures the Nasca fee is always at least $0.10, making it viable to process.
The same 2% rate applies to both Free and Pro plans. There is no volume discount on the platform fee.
In addition to monthly limits, you can set optional daily and weekly AI spend limits per tier. Configure them in Settings → User tiers. Leave a field empty to apply no limit for that period.
All three limits are enforced independently. A user is blocked as soon as any active limit is reached, not only when all are exceeded. Credit pack purchases temporarily extend all limits by the purchased AI spend amount — the credit is shared across daily, weekly, and monthly counters.
nasca.getUsage(ctx) returns a snapshot of the current user's spend and credit state. Use it to build in-app usage displays.
const usage = await nasca.getUsage(ctx)
The return type:
interface UsageResponse {
// Monthly spend and limit
monthly_spend: number // USD spent this month
monthly_limit: number | null // null if no monthly limit set
monthly_percent: number | null
// Daily spend and limit (if daily limit configured)
daily_spend: number
daily_limit: number | null
daily_percent: number | null
// Weekly spend and limit (if weekly limit configured)
weekly_spend: number
weekly_limit: number | null
weekly_percent: number | null
// Credit balance
credit_balance_ai: number // remaining AI spend in USD
credit_balance_display: number | null // amount user paid, depleting proportionally
credit_pack_price: number | null // face value of the active credit pack
credit_percent: number | null // % of pack remaining (null for flexible packs)
is_blocked: boolean
resets_at: string // ISO timestamp — when the monthly counter resets
}Example output for a user on a $10 credit pack (0.6 ratio) with 40% remaining:
{
monthly_spend: 1.20,
monthly_limit: 5.00,
monthly_percent: 24,
daily_spend: 0.30,
daily_limit: 1.00,
daily_percent: 30,
weekly_spend: 0.80,
weekly_limit: null,
weekly_percent: null,
credit_balance_ai: 2.40,
credit_balance_display: 4.00,
credit_pack_price: 10.00,
credit_percent: 40,
is_blocked: false,
resets_at: "2026-07-01T00:00:00.000Z"
}When a user is blocked, the Nasca SDK throws a NascaBlockedError. The error carries an upgrade_url — a link to a Nasca-hosted upgrade prompt at nasca.dev/upgrade/{sessionId}. Return this URL to your frontend in a 402 response.
// Your API route (Node / Next.js / etc.)
import { NascaBlockedError } from '@nasca/sdk'
try {
const result = await callAI(params, ctx)
return res.json(result)
} catch (e) {
if (e instanceof NascaBlockedError) {
return res.status(402).json({
message: e.upgrade_message,
upgrade_url: e.checkout_url,
})
}
throw e
}In your frontend, when you receive a 402 with an upgrade_url, render it inside an iframe. The prompt is a fully hosted UI — it shows the user's usage, their available credit packs and subscription plans, and a Stripe checkout button. No additional UI work is needed on your side.
// Frontend — vanilla JS (adapt for React, Vue, etc.)
function showUpgradePrompt(upgradeUrl) {
const overlay = document.createElement('div')
overlay.style.cssText = `
position: fixed; inset: 0; z-index: 9999;
background: rgba(0,0,0,0.5);
display: flex; align-items: center; justify-content: center;
`
const iframe = document.createElement('iframe')
iframe.src = upgradeUrl
iframe.style.cssText = `
width: 400px; max-width: 95vw; height: 600px; max-height: 90vh;
border: none; border-radius: 16px; background: white;
`
overlay.appendChild(iframe)
document.body.appendChild(overlay)
overlay.addEventListener('click', (e) => {
if (e.target === overlay) overlay.remove()
})
window.addEventListener('message', (e) => {
if (e.data?.type === 'nasca:dismiss') overlay.remove()
}, { once: true })
}When the user clicks “Maybe later”, the prompt sends a { type: 'nasca:dismiss' } postMessage to the parent window. The listener above removes the overlay. If the user completes checkout, Stripe redirects to the success_url you configured when calling /intercept.
Customise the prompt appearance (app name and accent colour) in Settings → Upgrade Prompt Branding.
You can generate a Nasca checkout URL at any time — no block event required. Use this to add a “Buy credits” button on a pricing page or account settings page so users can top up before they hit a limit.
// Server-side (API route / server action)
const url = await nasca.getCheckoutUrl(ctx, {
type: 'credits', // 'credits' | 'subscription'
// packId: 'your-pack-id', // optional — pre-selects a credit pack
// planId: 'your-plan-id', // optional — pre-selects a subscription plan
successUrl: 'https://yourapp.com/billing?success=1',
cancelUrl: 'https://yourapp.com/billing',
})
// Returns the nasca.dev/upgrade/{sessionId} URL, or null if the worker is unreachable.
// Pass it to your frontend and show it in the same iframe overlay as Step 5.Passing packId or planId pre-selects that option in the upgrade prompt so the user can proceed directly to checkout without making a choice. Passing type without a specific ID filters the prompt to show only credit packs or only subscription plans.
If the Nasca worker is unreachable, returns an error, or times out, the SDK allows the AI call to proceed normally. Your end-users will never see an error caused by Nasca itself.
The trade-off is that if Nasca is unavailable, spend tracking and limit enforcement pause for the duration of the outage. Cost data for those calls will not be recorded, and users who are over their limit may get through. This is the right default — protecting your user experience matters more than perfect cost tracking.
Nasca releases SDK updates to support new providers, fix streaming edge cases, and improve cost calculation accuracy. You are responsible for keeping @nasca/sdk updated in your project. Running an outdated version may result in incorrect token extraction or missing provider support.
npm outdated @nasca/sdk npm install @nasca/sdk@latest
If you have questions about how a specific model or provider is handled, email hello@nasca.dev.