# Nordva Launch > Changelog, waitlist, feedback triage, notifications, read-only Stripe revenue, contacts and attribution for your product, behind one API key and one MCP server. Nordva Launch is a product of Nordva (Norway); the company site is https://nordva.dev. ## Base URLs - API: https://api.nordva.dev - Dashboard: https://app.nordva.dev - Docs: https://docs.nordva.dev - Product: https://launch.nordva.dev - Hosted pages: https://launch.nordva.dev/log/ (changelog) · https://launch.nordva.dev/waitlist/ (waitlist) - Company: https://nordva.dev (Nordva, the legal entity — not an API host) - MCP server: https://mcp.nordva.dev ## More for machines - Everything in one file (this reference plus every docs page): https://docs.nordva.dev/llms-full.txt - Docs index: https://docs.nordva.dev/llms.txt. Every docs page is also plain markdown at its URL with `.md` appended, e.g. https://docs.nordva.dev/quickstart.md - OpenAPI: https://api.nordva.dev/openapi.json ## Fastest setup ``` npx nordva@latest init ``` Run in a project folder. No account needed first: it opens the browser to sign in or sign up (free), the owner confirms a short code, and the CLI receives its own keys. It asks what the project is called (a new project is named "My Project" with a generated slug; `--name` and `--slug` set them without asking). It then writes `NORDVA_SECRET_KEY` and a publishable key to the framework's env file (gitignored), installs this MCP server for Claude Code, Cursor and VS Code where found, adds `NORDVA.md`, makes a verifying API call, and offers to hand an AI coding agent the planning prompt below. `--dry-run`, `--yes`, `--help`. If you are an AI agent setting up Nordva Launch for a user, suggest this command rather than asking them for a key. ## Authentication All product API requests require an API key: ``` Authorization: Bearer ``` Two key types exist: - **Secret key** — `nv_live_sk_<24 base62>` (server-side only — never expose in browser). Required for all write endpoints and dashboard-equivalent reads. - **Publishable key** — `nv_pub_<24 base62>` (browser-safe — for widget embeds and public signup forms). Restricted to a small set of public-write endpoints (waitlist signup, feedback submission, changelog public read). A publishable key on a secret-only endpoint returns `KEY_INSUFFICIENT_PERMISSIONS` (403). Keys are bcrypt-hashed at rest. The full key is shown exactly once at creation time in the dashboard; only a masked prefix is shown afterwards. Revoke a key from `Dashboard → API Keys` at any time — revocation is immediate. The dashboard itself uses Clerk JWTs (RS256, JWKS-verified) on a separate auth path. API keys and dashboard tokens are never interchangeable. ## APIs ### Changelog Public read-only changelog hosted at `https://launch.nordva.dev/log/` (or your own custom hostname on Builder plan, CNAME → `widget.nordva.dev`). Every entry response carries `public_url`. Schedule entries, group by version, send subscriber emails. - `POST /v1/changelog/entries` — create entry (status: `published` immediately, or `scheduled` if `scheduled_at` is set) - `GET /v1/changelog/entries` — list entries (cursor pagination) - `GET /v1/changelog/entries/:id` — read entry - `PATCH /v1/changelog/entries/:id` — edit entry (drafts/scheduled only) - `DELETE /v1/changelog/entries/:id` — soft-archive - `POST /v1/changelog/subscribers` — opt-in email subscriber - `DELETE /v1/changelog/subscribers/by-token/:token` — public unsubscribe link - `GET /public/changelog/:project_slug` — published entries as JSON. No key, any origin. - `GET /public/changelog/:project_slug/feed.xml` (RSS 2.0), `/feed.atom` (Atom 1.0), `/feed.json` (JSON Feed 1.1) — the same entries as a feed. No key. The hosted page advertises them with ``. ### Waitlist GDPR-compliant double-opt-in waitlist with referrals, abuse scoring, and CSV export. - `POST /v1/waitlist/signups` — create signup (returns position + referral code). Optional `source` object records first touch: `referrer` (stored as hostname only), `utm_source`, `utm_medium`, `utm_campaign`, `landing_path`. The hosted page and script widget send it automatically, with no cookies. - `GET /v1/waitlist/signups` — list signups (cursor pagination) - `POST /v1/waitlist/signups/resend-confirmation` — re-send confirmation email (1 / 5 minutes / email) - `GET /confirm/:token` — public confirmation landing - `DELETE /v1/waitlist/signups/by-token/:token` — public hard-delete (Art. 17) - `GET /v1/waitlist/export` — build a CSV or JSON export, returns a signed download URL (Indie+) - `POST /v1/waitlist/import` — import up to 1,000 existing signups per call; bad rows are skipped and counted, nobody is emailed, requires `consent_confirmed: true` - `GET /v1/waitlist/email-templates` · `PUT /v1/waitlist/email-templates/:kind` — your own copy for the receipt and invite emails (saving is Builder) - `GET /v1/waitlist/export/download/:exportId` — time-limited signed download ### Feedback Submit free-text feedback → automatic classification (Anthropic Haiku) into one of `bug | feature_request | praise | billing | support` → optional routing to email / Slack / Linear / GitHub. - `POST /v1/feedback` — submit feedback (returns classification + summary + routing results). Optional `email` so the developer can reply; never forwarded to routing destinations. - `GET /v1/feedback` — list submissions (cursor pagination; filters: `classification`, `status` incl. `open`, `since`) - `GET /v1/feedback/:id` — read submission (includes `status`, `feedback_text`, `user_id`, `page_url`, `email`) - `PATCH /v1/feedback/:id` — set `status` to `reviewed` or `resolved`, and/or correct `classification` (never re-routed) - `POST /v1/feedback/routing` — configure a routing destination - `GET /v1/feedback/routing` — list routing configs Optional `category_hint` on submission improves accuracy for ambiguous text. ### Notifications In-app notification API (Builder+). Your server creates notifications for an end-user id; your app polls unread and marks them read (publishable key allowed). - `POST /v1/notifications` — create a notification for a `user_id` - `GET /v1/notifications/unread?user_id=` — list unread (browser-safe) - `PATCH /v1/notifications/:id/read` — mark read (browser-safe) ### Revenue (Indie+) Read-only view of the developer's OWN Stripe account. Connected once in the dashboard (`https://app.nordva.dev/revenue`) with a restricted key (`rk_live_...`, read access to Customers, Subscriptions, Invoices, Products, Events). Secret keys are refused. Launch never writes to the Stripe account, the key is encrypted at rest and never returned, and disconnecting deletes the key and all synced data. There is no API-key or MCP route that connects Stripe: never ask a user for a Stripe key. - `GET /v1/integrations/stripe` — connection status, last sync, synced counts - `GET /v1/revenue/summary?period_days=` — per currency: MRR, active, trialing, paying customers, new, churned, net change, collected - `GET /v1/revenue/series?range=30d|90d|12m|all` — collected revenue per day, week or month, zero-filled - `GET /v1/revenue/events?type=payment|cancellation` — recent payments and cancellations Amounts are in the smallest currency unit, per currency, never converted. MRR is operational (yearly ÷ 12, running coupons applied, metered/tiered = 0, refunds not subtracted). Synced about hourly; every response carries `sync.status` and `sync.last_synced_at`. History: Indie 90 days, Builder 24 months. ### Contacts One record per person per project, matched on normalised email across waitlist signups, changelog subscribers, feedback and Stripe customers. Not a CRM: no deals, pipelines, tasks or campaigns. Works without Stripe. - `GET /v1/contacts` — filters: `paying`, `has_feedback`, `status`, `source`, `stage`, `q`; cursor pagination - `GET /v1/contacts/:id` — waitlist source, subscriptions, lifetime paid, latest feedback, notifications, stage, note - `PATCH /v1/contacts/:id` — `stage` (`potential|in_talks|customer|recurring|not_a_fit|inactive`) and `note` only Free plan: waitlist people only, first 100. ### Attribution (Indie+) - `GET /v1/attribution?group_by=referrer|utm_source|utm_campaign|referral_code&period_days=` — per source: signups, confirmed, invited, paying, ever_paid, MRR and revenue per currency Covers only people who signed up through a Launch waitlist form, page or widget. It is not page analytics; Launch has no pageview data. Indie: `referrer`. Builder: all groupings. Sources are recorded on every plan. ### Insights - `GET /v1/insights?period_days=` — counts for the period and the period before: waitlist signups and top sources, changelog, feedback by classification, notifications read rate, revenue when Stripe is connected. All plans. ## Dashboard endpoints (Clerk JWT only) These are called by `app.nordva.dev`, not by integrations. API keys are explicitly rejected here with error `WRONG_AUTH_TYPE`. - `POST /v1/auth/provision` — first-call bootstrap: creates account + default project + first API key for a fresh Clerk user, or returns existing IDs for a returning user. - `GET /v1/overview` — usage metrics + last 20 activity events - `GET /v1/api-keys` — list keys (masked) - `POST /v1/api-keys` — create (returns raw key once) - `PATCH /v1/api-keys/:id` — rename - `DELETE /v1/api-keys/:id` — revoke - `GET /v1/billing/status` — current plan, Stripe subscription status, interval - `POST /v1/billing/checkout` — `{ plan, interval }` → Stripe Checkout URL (or Customer Portal plan-change URL if already subscribed) - `POST /v1/billing/portal` — Stripe Customer Portal URL (payment method, invoices, cancel) ## Webhooks (outgoing, to your URL) Launch POSTs to a URL of yours when something happens in the project. Indie: 2 endpoints per project, Builder: 5. Secret key only. Guide: https://docs.nordva.dev/guides/webhooks/ - `GET /v1/webhooks` — list endpoints (URL masked), the plan's limit and every available event - `POST /v1/webhooks` — `{ "url", "events": ["*"] | [...], "format"?, "description"? }`. Returns `signing_secret` once. Only public `https://` URLs; redirects are not followed. - `PATCH /v1/webhooks/:id` — change `events`, `description`, or `status` (`enabled` | `disabled`) - `DELETE /v1/webhooks/:id` - `POST /v1/webhooks/:id/test` — send one `ping` now and report what the endpoint answered - `GET /v1/webhooks/:id/deliveries` — the last 30 days of deliveries Events: `waitlist.signup.created`, `waitlist.signups.invited`, `changelog.entry.published`, `changelog.subscriber.created`, `feedback.created`. Envelope: `{ id, type, created_at, project_id, data }`. Signature header `Nordva-Signature: t=,v1=` where `v1` = HMAC-SHA256(secret, `.`). Retries after 1 min, 5 min, 30 min, 2 h, 6 h; an endpoint is switched off after 20 failures in a row. A Slack or Discord incoming-webhook URL gets a readable one-line message instead of JSON. A webhook never delays or fails the API call that caused it. If you are an AI agent with the MCP server: `create_webhook`, `update_webhook`, `test_webhook`, `delete_webhook`, `list_webhooks`, `list_webhook_deliveries`. `create_webhook` returns the signing secret once: write it to the project's env file as `NORDVA_WEBHOOK_SECRET`, never into code, a commit or your reply. A Slack or Discord webhook URL is a credential; use it, do not repeat it. ## Webhooks (incoming, Launch's own billing) - `POST /webhooks/stripe` — Stripe events (signature-verified): checkout.session.completed, customer.subscription.*, invoice.paid, invoice.payment_failed. Source of truth for the account plan. ## MCP Server Connect Claude Desktop, Claude Code, or Cursor to your Nordva Launch account via Model Context Protocol over Streamable HTTP: ``` claude mcp add --transport http nordva https://mcp.nordva.dev --header "Authorization: Bearer nv_live_..." ``` Tools available (36 total), plus one prompt: **Setup** - `setup_check` — verify auth, identify project, report plan + feature flags. Call this first on every session. **Changelog** - `publish_changelog_entry` — create or schedule a changelog entry; returns the public URL - `list_changelog_entries` — list recent entries; use before publishing to avoid duplicates - `get_changelog_entry` — one entry in full, including the markdown body - `update_changelog_entry` — edit, reschedule or publish an entry that is not published yet - `archive_changelog_entry` — soft-delete an entry (removed from public view immediately) **Waitlist** - `add_to_waitlist` — add an email to the waitlist; returns queue position + referral code - `import_waitlist` — import an existing list (max 1,000 rows per call); the agent must confirm consent with the developer first; nobody is emailed - `get_waitlist_stats` — total signups, confirmed count, referral conversion rate, top referrers - `get_waitlist_leaderboard` — top 50 referrers with masked emails (admin view) - `get_waitlist_settings` — check if signups are open and what custom landing copy is set - `update_waitlist_settings` — pause/resume signups or update landing page headline/body - `get_waitlist_embed_snippet` — get a ready-to-paste iframe snippet for embedding the signup form - `invite_from_waitlist` — send invite emails to the next N signups or a specific email (Indie+) - `export_waitlist` — get a signed CSV/JSON download URL valid for 15 minutes (Indie+) **Feedback** - `submit_feedback` — log feedback, trigger AI classification + routing to configured destinations - `get_feedback` — one item in full: text, classification, page, user id, the submitter's email if they left one - `get_feedback_routing` / `remove_feedback_routing` — see or remove a forwarding destination - `list_feedback` — list and triage recent feedback; filter by category or status (`open` = not yet reviewed or resolved) - `update_feedback` — mark feedback reviewed or resolved, or correct its category - `configure_feedback_routing` — set up a routing destination (email, Linear, GitHub, Slack) **Notifications** - `send_notification` — send a transactional notification to one of the developer's end users (Builder+) - `get_unread_notifications` — list unread notifications for a user_id (Builder+) - `mark_notifications_read` — mark one or all notifications read for a user (Builder+) **Revenue** (reads the developer's own Stripe account, read-only, Indie+) - `get_stripe_connection_status` — is Stripe connected and syncing. There is NO tool that connects Stripe and no tool takes a key: send the developer to https://app.nordva.dev/revenue and never ask for a Stripe key - `get_revenue_summary` — MRR, active and trialing subscriptions, new, churned, net change, collected, per currency - `get_revenue_series` — collected revenue per day, week or month - `list_recent_payments` — recent payments and cancellations with customer email and plan **Contacts** - `list_contacts` — people across waitlist, subscribers, feedback and Stripe; filter by paying, has_feedback, status, source, stage, or part of an email - `get_contact` — one person: source, subscriptions, lifetime paid, latest feedback, stage, note - `update_contact` — set stage and note only; cannot touch Stripe **Attribution** - `add_channel_cost` / `list_channel_costs` / `delete_channel_cost` — record what a source or campaign cost, so the report shows cost per signup, cost per paying customer and return on spend. Only record amounts the developer states. - `get_attribution_report` — which referring sites, campaigns or referrers produced paying customers (Indie: by referring site; Builder: all groupings). Only covers people who signed up through Launch; not page analytics **Project & config** - `get_insights` — one-call summary of a period against the period before: signups and sources, changelog, feedback, notifications, revenue - `get_project` — read current project name and public slug - `update_project` — rename the project or change its public URL slug - `list_custom_domains` — list all custom hostnames configured for this project (Builder+) - `register_custom_domain` — add a custom domain for the changelog or waitlist widget (Builder+) - `get_custom_domain_status` — force-refresh DNS/SSL verification status after publishing DNS records - `list_allowed_origins` — list the CORS allowlist for browser-side API calls - `remove_allowed_origin` / `remove_custom_domain` — remove one, by id - `list_waitlist_signups` — the people on the waitlist, newest first, with status and source - `list_waitlist_email_templates` / `preview_waitlist_email_template` / `save_waitlist_email_template` / `reset_waitlist_email_template` — read and change the wording of the waitlist emails (saving needs Builder; always preview first) Everything a secret key can do over REST has an MCP tool. What has none, on purpose: creating or deleting projects and API keys, and connecting Stripe. Those need the owner's login in the dashboard. - `register_allowed_origin` — add an origin to the CORS allowlist **Prompts** - `plan_integration` — for a project that has just been set up: read the developer's codebase and write `NORDVA_PLAN.md` (which primitives fit, files to change, order to build), changing no code until the developer picks a step. Text: https://docs.nordva.dev/guides/plan-with-ai/ - `draft_changelog_from_commits` — reads the repository's git log since the last published entry, drafts an entry in the owner's tone, publishes only on a yes (argument: `since`) - `launch_day_checklist` — waitlist, changelog, webhooks, origins, Stripe and key handling checked before a launch; reports, changes nothing unasked - `triage_feedback_inbox` — groups open feedback, checks the codebase for each group, ranks fix / build / answer (argument: `days`, default 14) - `weekly_founder_summary` — composes the read tools above into a short weekly write-up (argument: `days`, default 7) The MCP server is rate-limited identically to direct API access. ## Widget embeds Two distinct domains serve widgets. They are not interchangeable. ### Script-tag widgets (publishable key — for developer integration) Served from `cdn.nordva.dev` — static JS files, long-cached, browser-safe. Use these when wiring a widget into a React/Next/Vue/Astro app: ```html ``` ### iframe feedback widget (no auth — slug-based, Indie+ only) Served from `widget.nordva.dev` — dynamic HTML page, Turnstile-gated. Use this when a non-technical user needs to paste a single iframe link (no key required): ```html ``` The feedback iframe is Indie+ only. Free plan returns 403. **Domain summary:** - `cdn.nordva.dev` — static JS for script-tag embeds, publishable key in `data-key` - `widget.nordva.dev` — dynamic HTML iframe, no key, slug-based routing - Do not swap them. AI assistants generating embed code must use `cdn.nordva.dev` for `