Documentation

Nasca documentation

SDK reference, cost tracking, credits, monetisation, and platform behaviour.

Token counts come from API response metadata

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_tokens
  • message_delta carries output_tokens

The SDK accumulates usage across all chunks, so both values are captured regardless of which event they arrive in.

How costs are calculated

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_1k

The result is accumulated in a per-user Redis counter and written to your Supabase database for dashboard display.

Pricing sources

Nasca looks up model pricing in two places, checked in order:

  1. Your custom model pricing (Settings → Custom Model Pricing). This takes precedence and applies to any model string you configure, including OpenRouter model IDs like openai/gpt-4o or meta-llama/llama-3.1-70b.
  2. Nasca's built-in pricing table for OpenAI and Anthropic models. Nasca maintains this table and updates it when providers change their pricing.

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.

OpenAI and Anthropic — built-in support

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 and other 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.

Pricing accuracy

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.

How credits work

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.

Flexible top-ups

Nasca supports two types of credit pack:

  • Fixed packs have a set price and a set AI spend value. For example: $10 paid → $6 AI spend. The checkout URL points directly to a Stripe-hosted payment page.
  • Flexible top-ups let end-users enter any amount above a configured minimum. You set a minimum price (e.g. $5) and an AI spend ratio (e.g. 0.60). A user who pays $25 receives $15 in AI spend. The checkout URL points to a Nasca-hosted top-up page that collects the amount before redirecting to Stripe.

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

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.

Stripe Connect onboarding

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.

What counts as a Nasca monetisation event

The 2% platform fee applies to:

  • Credit pack purchases (fixed and flexible) processed through a Nasca-generated checkout URL.
  • The first month of a subscription plan upgrade triggered via a Nasca upgrade prompt.

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.

The 2% platform fee

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.

Daily and weekly limits

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.

getUsage() SDK method

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"
}

Showing the upgrade prompt

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.

Proactive checkout

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.

Nasca always fails open

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.

Keeping the SDK up to date

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

Questions

If you have questions about how a specific model or provider is handled, email hello@nasca.dev.