Collect feedback from Node.js
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.
Publishable key pasted into the page that embeds the form
NORDVA_SECRET_KEY=nv_live_… 01Server: Node.js
feedback-submit.ts
export async function submitFeedback(input: Record<string, unknown>) {
const res = await fetch("https://api.nordva.dev/v1/feedback", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NORDVA_SECRET_KEY!}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ feedback_text: input.text, user_id: input.user_id, page_url: input.page_url }),
});
if (!res.ok) {
const { error } = await res.json();
throw new Error(`${error.code}: ${error.message}`);
}
const { data } = await res.json();
// data.category (bug | feature_request | praise | billing | support | uncategorised), data.sentiment
return data;
} 02Server: list open bugs, Node.js
feedback-list.ts
export async function listOpenBugs() {
const res = await fetch("https://api.nordva.dev/v1/feedback?category=bug&status=open&limit=20", {
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 is an array; page with the cursor in meta
return 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.