@kyrofeedback/sdk
v0.1.0
Published
Typed server-side client for the KyroFeedback REST API
Readme
@kyrofeedback/sdk
A TypeScript-first, server-side client for the KyroFeedback v1 REST API. It uses the existing API and does not connect to a database or depend on Next.js.
The API currently supports feedback listing, retrieval, and creation; comment listing and creation; and vote state, vote, and unvote. Feedback editing/deletion and comment deletion are not available through v1 and are intentionally not exposed here.
Requirements and installation
- Node.js 20 or newer (native
fetchandAbortController) - A KyroFeedback API key for one project
When the package is published, install it with:
npm install @kyrofeedback/sdkThis repository does not publish automatically. Until release, build the package and install its packed tarball from a local path.
cd packages/sdk
npm install
npm test
npm pack
# From the consuming application:
npm install /path/to/kyrofeedback-sdk-0.1.0.tgzKeep keys on the server
The SDK can use either sk_live_ or pk_live_ keys because both are accepted by these API endpoints. Current keys have the prefix followed by 43 base64url characters. For a server integration, use a secret sk_live_ key and keep it in a server-only environment variable. In Next.js, use KYROFEEDBACK_API_KEY, never NEXT_PUBLIC_KYROFEEDBACK_API_KEY. The client rejects initialization in a browser runtime; do not import it into browser components or send a secret key to a browser. The browser widget continues to use its publishable key and widget.js.
Keys are project-scoped. The API enforces project settings, project isolation, and a current limit of 120 requests per minute per key. Feedback, comment, and vote creation can return 403 when the corresponding anonymous activity setting is disabled. The SDK does not bypass those checks.
Initialize
import { KyroFeedback } from "@kyrofeedback/sdk";
const kyro = new KyroFeedback({
apiKey: process.env.KYROFEEDBACK_API_KEY!,
});The default host is https://feedback.kyrocms.com; requests go to its /api/v1 API. For a local or self-hosted instance, set baseUrl to its origin (or an origin with a path prefix):
const kyro = new KyroFeedback({
apiKey: process.env.KYROFEEDBACK_API_KEY!,
baseUrl: "http://localhost:3000",
timeout: 5_000,
});baseUrl must be an absolute HTTP or HTTPS URL without credentials, query, or fragment. Plain HTTP is accepted only for localhost and loopback development URLs; remote hosts must use HTTPS. A trailing /api/v1 is normalized. The default timeout is 10 seconds. Pass a compatible Fetch API implementation with fetch when needed.
Feedback
List and paginate
The list endpoint returns newest feedback first and supports limit (API range 1–50, default 20), cursor, q, status, and type. The page object preserves meta.nextCursor; request subsequent pages only when needed.
const page = await kyro.feedback.list({
limit: 20,
status: "OPEN",
type: "FEATURE",
q: "dark mode",
});
for (const item of page.data) {
console.log(item.title, item.votesCount, item.commentsCount);
}
if (page.meta.nextCursor) {
const nextPage = await kyro.feedback.list({
limit: 20,
cursor: page.meta.nextCursor,
});
}Supported status values are OPEN, PLANNED, IN_PROGRESS, COMPLETED, and CLOSED. Supported type values are FEATURE, BUG, QUESTION, and INTEGRATION.
Get and create
const item = await kyro.feedback.get("feedback-id");
const created = await kyro.feedback.create({
title: "Add dark mode",
description: "A dark theme would make the dashboard easier to use at night.",
type: "FEATURE",
authorName: "Alex", // optional; 100 characters maximum in the API
});The API requires a title of 3–200 characters and a description of 3–10,000 characters. Feedback creation is anonymous and requires the project’s anonymous feedback setting to be enabled. The response fields are id, title, description, type, status, votesCount, commentsCount, createdAt, and updatedAt.
The v1 API does not support feedback updates or deletion. Those methods are not included.
Comments and replies
const comments = await kyro.comments.list("feedback-id");
const rootComment = await kyro.comments.create("feedback-id", {
content: "This would help our team too.",
authorName: "Alex",
});
await kyro.comments.create("feedback-id", {
content: "Thanks for the detail!",
parentId: rootComment.id,
});Comments are returned as a flat, creation-ordered array. Group parentId: null comments as roots and attach replies to their parent ID. The API accepts comment content up to 5,000 characters, requires anonymous comments to be enabled, and validates that a reply’s parent belongs to the same feedback item. Comment deletion is not supported by the v1 API.
Votes and visitor identity
const state = await kyro.votes.get("feedback-id");
const updated = state.voted
? await kyro.votes.unvote("feedback-id")
: await kyro.votes.vote("feedback-id");The API identifies votes with its kyro_anonymous_id cookie; it does not accept a user ID. The SDK never stores cookies between requests because a long-lived server client may be shared by many visitors. To preserve a particular visitor’s identity in a server route, forward that visitor’s Kyro cookie on each request:
const cookie = request.headers.get("cookie") ?? undefined;
const voteState = await kyro.votes.get("feedback-id", { cookie });
const responseHeaders = new Headers();
const updated = await kyro.votes.vote("feedback-id", {
cookie,
onResponse(response) {
// In a server route, relay Set-Cookie to that same visitor when present.
const setCookie = response.headers.get("set-cookie");
if (setCookie) responseHeaders.append("set-cookie", setCookie);
},
});
return Response.json(updated, { headers: responseHeaders });RequestOptions also accepts an AbortSignal. Vote state and counts come from the API response; the SDK does not synthesize them.
Request controls
All resource methods accept an optional final RequestOptions argument with signal, cookie, and onResponse. Cookies are opt-in and never persisted. Mutations are not automatically retried.
const controller = new AbortController();
const result = await kyro.feedback.list({ limit: 10 }, { signal: controller.signal });Errors
All API, transport, and malformed-response failures are KyroFeedbackError instances. status is populated for HTTP responses, and kind categorizes failures as authentication, permission, validation, not_found, rate_limit, server, http, network, timeout, or invalid_response.
The API currently returns errors as { error: { message, status } }; it does not return a stable error code or request ID, so the SDK does not fabricate either.
import { KyroFeedbackError } from "@kyrofeedback/sdk";
try {
await kyro.feedback.create({ title: "Add dark mode", description: "Please add a dark theme.", type: "FEATURE" });
} catch (error) {
if (error instanceof KyroFeedbackError) {
if (error.kind === "permission") console.error("Check anonymous feedback settings.");
else console.error(error.kind, error.status, error.message);
}
throw error;
}Next.js server-side usage
Keep SDK use in a route handler or server component. A client component should call your route, not import the SDK or receive the secret key.
// app/api/feedback/route.ts
import { KyroFeedback, KyroFeedbackError } from "@kyrofeedback/sdk";
const kyro = new KyroFeedback({ apiKey: process.env.KYROFEEDBACK_API_KEY! });
export async function GET() {
try {
const page = await kyro.feedback.list({ limit: 20 });
return Response.json(page);
} catch (error) {
if (error instanceof KyroFeedbackError) {
return Response.json({ error: error.message }, { status: error.status ?? 502 });
}
return Response.json({ error: "Unexpected error" }, { status: 500 });
}
}An end-to-end example is in examples/nextjs.
Node.js usage
import { KyroFeedback } from "@kyrofeedback/sdk";
const kyro = new KyroFeedback({ apiKey: process.env.KYROFEEDBACK_API_KEY });
const page = await kyro.feedback.list({ limit: 10, type: "BUG" });
console.log(page.data);Development, tests, and package verification
From this directory:
npm install
npm run typecheck
npm test
npm run pack:checknpm test compiles the package and runs mocked-fetch tests with Node’s built-in test runner. It does not contact a live service.
Live integration checks
npm run test:integration runs an opt-in suite against a dedicated local or test deployment. It creates feedback and comments and changes a vote; it cannot delete those records. Use two disposable projects with separate keys, enable anonymous feedback, comments, and voting on the first project, and use a third disposable key that you have revoked if you want the revoked-key case. Never use a production project, key, or user data.
Set these server-side variables without printing or committing them:
export KYROFEEDBACK_BASE_URL=http://localhost:3000
export KYROFEEDBACK_TEST_API_KEY=sk_live_REPLACE_WITH_FULL_TEST_PROJECT_ONE_KEY
export KYROFEEDBACK_SECOND_PROJECT_API_KEY=sk_live_REPLACE_WITH_FULL_TEST_PROJECT_TWO_KEY
export KYROFEEDBACK_REVOKED_API_KEY=sk_live_REPLACE_WITH_FULL_REVOKED_TEST_KEY
npm run test:integrationReplace each placeholder with the complete raw disposable key copied from the dashboard; these sample values are intentionally not valid keys.
For a remote dedicated test host, use HTTPS and explicitly set KYROFEEDBACK_ALLOW_REMOTE_TEST_INSTANCE=true after confirming the deployment is isolated. The script refuses feedback.kyrocms.com and refuses remote hosts without that opt-in. It checks authentication, list/create/get, cursor pagination, comments/replies, vote/unvote, a second-project 404, invalid credentials, and (when provided) revoked credentials. The test records are marked with a random ID and remain in the disposable project because v1 has no delete methods.
Build output and declaration files are generated under dist/. To publish after review, update the version, run the checks above, inspect npm pack --dry-run, then publish explicitly from this directory with npm publish --access public. No package has been published by this repository change.
