Report which sources pay in a SvelteKit app
A source that brings a thousand signups and no customers is worth less than one that brings twenty signups and five customers. Attribution joins where each waitlist signup came from to who is paying you in Stripe.
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.
PUBLIC_NORDVA_PUBLISHABLE_KEY=nv_pub_live_… # .env
NORDVA_SECRET_KEY=nv_live_… # .env, $env/static/private 01Server: endpoint
src/routes/api/attribution/+server.ts
import { NORDVA_SECRET_KEY } from "$env/static/private";
import { json } from "@sveltejs/kit";
export async function GET() {
const res = await fetch("https://api.nordva.dev/v1/attribution?group_by=referrer&period_days=90", {
method: "GET",
headers: {
Authorization: `Bearer ${NORDVA_SECRET_KEY}`,
},
});
if (!res.ok) {
const { error } = await res.json();
throw new Error(`${error.code}: ${error.message}`);
}
const { data } = await res.json();
// data.rows: [{ source, signups, confirmed, invited, paying, signup_to_paying, money: [{ currency, mrr_minor, revenue_minor }], spend?, views? }]
// group_by: referrer (Indie) | utm_source | utm_campaign | referral_code (Builder)
return json(data);
} Signups carry their first-touch source on every plan, so the report has history from the day you upgrade. Paying and money need a connected Stripe account. Record what a channel cost with POST /v1/attribution/costs and the rows gain cost per signup, cost per paying customer and return on spend.
02Verify from a terminal
curl -s "https://api.nordva.dev/v1/attribution?group_by=referrer" \
-H "Authorization: Bearer nv_live_…" | jq '.data.rows[] | {source, signups, paying}' Behaviour worth knowing
- Every response is
{ data, error, meta }. On failureerror.codeis a stable string such as VALIDATION_ERROR, PLAN_LIMIT_REACHED or RATE_LIMITED, with a remediation message. - POST and PATCH requests accept an
Idempotency-Keyheader; 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.