Skip to main content
20% off 3 months · code LAUNCH20 · until 31 October

Read your Stripe revenue in a Next.js app

Connect your own Stripe account or Lemon Squeezy store, read only, and ask for the numbers a solo founder checks: MRR, who started paying, who cancelled, and what was collected. It is deliberately small. It is also what lets Launch tell you which waitlist signups became customers.

Keys

Secret key (nv_live_…) on your server only. There is no browser part: revenue and contact data must not reach a page a visitor can read. Create both under Dashboard → API keys.

NEXT_PUBLIC_NORDVA_PUBLISHABLE_KEY=nv_pub_live_…  # .env.local
NORDVA_SECRET_KEY=nv_live_…  # .env.local, never NEXT_PUBLIC_

01Server: route handler

app/api/revenue/route.ts

export async function GET() {
  const res = await fetch("https://api.nordva.dev/v1/revenue/summary?period_days=30", {
    method: "GET",
    headers: {
      Authorization: `Bearer ${process.env.NORDVA_SECRET_KEY!}`,
    },
  });
  if (!res.ok) {
    const { error } = await res.json();
    throw new Error(`${error.code}: ${error.message}`);
  }
  const { data } = await res.json();
  // data.by_currency: [{ currency, mrr_minor, active_subscriptions, paying_customers, new_subscriptions, churned_subscriptions, revenue_minor }]
  // data.sync: { status, last_synced_at, livemode }; "backfilling" means the first import is still running
  return Response.json(data);
}

Amounts are in the smallest currency unit and never converted between currencies. Connect Stripe first, read-only, under Dashboard → Revenue; without a connection the API answers STRIPE_NOT_CONNECTED. Indie and up.

02Verify from a terminal

curl -s "https://api.nordva.dev/v1/revenue/summary?period_days=30" \
  -H "Authorization: Bearer nv_live_…" | jq '.data.by_currency[] | {currency, mrr_minor, paying_customers}'

Behaviour worth knowing

  • Every response is { data, error, meta }. On failure error.code is a stable string such as VALIDATION_ERROR, PLAN_LIMIT_REACHED or RATE_LIMITED, with a remediation message.
  • POST and PATCH requests accept an Idempotency-Key header; the same key with the same body returns the original response for 24 hours.
  • Rate limits per key: 30 requests a minute on Free, 120 on Indie, 500 on Builder. A 429 carries Retry-After.
  • Browser calls with a publishable key must come from an origin registered on the project, otherwise the API answers ORIGIN_NOT_ALLOWED.