Collect feedback in a SvelteKit app
Accept feedback from inside your product, have each message classified, and forward it to wherever your team already works. No inbox to babysit.
Keys
Browser calls use a publishable key (nv_pub_live_…), which is safe to ship. Reading and admin calls use a secret key (nv_live_…) on your server only. Create both under Dashboard → API keys.
PUBLIC_NORDVA_PUBLISHABLE_KEY=nv_pub_live_… # .env
NORDVA_SECRET_KEY=nv_live_… # .env, $env/static/private 01Browser: feedback form (publishable key)
src/lib/FeedbackForm.svelte
<script lang="ts">
import { PUBLIC_NORDVA_PUBLISHABLE_KEY as KEY } from "$env/static/public";
let text = "";
let state: "idle" | "sent" | "error" = "idle";
async function submit() {
const res = await fetch("https://api.nordva.dev/v1/feedback", {
method: "POST",
headers: {
Authorization: `Bearer ${KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ feedback_text: text, user_id: "user_123", page_url: location.href }),
});
const { data, error } = await res.json();
state = error ? "error" : "sent";
}
</script>
{#if state === "sent"}
<p>Thanks, received.</p>
{:else}
<form on:submit|preventDefault={submit}>
<textarea bind:value={text} required minlength="5" maxlength="2000" />
<button type="submit">Send feedback</button>
{#if state === "error"}<p role="alert">Could not send. Try again.</p>{/if}
</form>
{/if} Feedback is an Indie and Builder feature; on Free the API answers PLAN_UPGRADE_REQUIRED. Register the page's origin under Project → Allowed origins.
02Server: list open bugs, endpoint
src/routes/api/feedback-list/+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/feedback?category=bug&status=open&limit=20", {
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 is an array; page with the cursor in meta
return json(data);
} 03Zero-code alternative: hosted widget
any HTML page
<script src="https://cdn.nordva.dev/v1/feedback.js"
data-key="nv_pub_live_…"
data-position="bottom-right"
data-accent="#A8552E"
data-user-id="user_123"></script> 04Verify from a terminal
curl -s "https://api.nordva.dev/v1/feedback?limit=5" \
-H "Authorization: Bearer nv_live_…" | jq '.data[] | {category, sentiment, feedback_text}' 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.