Official SDK for Echoes - Centralized feedback platform for developers managing multiple SaaS products.
# Using bun
bun add @echoessh/sdk
# Using npm
npm install @echoessh/sdk
# Using yarn
yarn add @echoessh/sdk
# Using pnpm
pnpm add @echoessh/sdkimport { Echoes } from "@echoessh/sdk";
const echoes = new Echoes({
apiKey: "ek_live_xxxxxxxxxxxxx",
});
// Send feedback
await echoes.send({
category: "bug",
message: "Button doesn't work on mobile",
userIdentifier: "user@example.com",
metadata: {
browser: "Chrome",
version: "120.0",
},
});
// Or use convenience methods
await echoes.bug("Button doesn't work on mobile");
await echoes.feature("Add dark mode support");
await echoes.question("How do I reset my password?");
await echoes.praise("Love the new design!");import { EchoesProvider, FeedbackWidget } from "@echoessh/sdk/react";
function App() {
return (
<EchoesProvider config={{ apiKey: "ek_live_xxxxxxxxxxxxx" }}>
<YourApp />
<FeedbackWidget
userIdentifier="user@example.com"
onSuccess={(id) => console.log("Feedback submitted:", id)}
/>
</EchoesProvider>
);
}new Echoes(config: EchoesConfig)| Option | Type | Required | Default | Description |
|---|---|---|---|---|
apiKey |
string |
Yes | - | Your Echoes API key |
baseUrl |
string |
No | https://echoes.sh |
API base URL |
defaultUserIdentifier |
string |
No | - | Default user identifier |
defaultMetadata |
object |
No | - | Default metadata for all feedback |
timeout |
number |
No | 10000 |
Request timeout in ms |
debug |
boolean |
No | false |
Enable debug logging |
Send feedback with full control over all parameters.
await echoes.send({
category: "bug", // "bug" | "feature" | "question" | "praise"
message: "Something went wrong",
userIdentifier: "user@example.com", // optional
metadata: { page: "/dashboard" }, // optional
});Convenience method for sending bug reports.
await echoes.bug("Login button not working", {
userIdentifier: "user@example.com",
});Convenience method for sending feature requests.
Convenience method for sending questions.
Convenience method for sending praise/positive feedback.
Create a new client instance with a default user identifier.
const userEchoes = echoes.withUser("user@example.com");
await userEchoes.bug("Something broke"); // Will include user identifierCreate a new client instance with additional default metadata.
const pageEchoes = echoes.withMetadata({ page: "/settings" });
await pageEchoes.bug("Settings not saving"); // Will include page metadataContext provider for React applications.
<EchoesProvider config={{ apiKey: "ek_live_xxx" }}>
{children}
</EchoesProvider>Pre-built feedback form component.
<FeedbackWidget
userIdentifier="user@example.com"
metadata={{ page: "/home" }}
onSuccess={(id) => console.log(id)}
onError={(err) => console.error(err)}
placeholder="Tell us what you think..."
submitText="Send Feedback"
successText="Thanks! π"
showCategories={true}
defaultCategory="bug"
theme="light" // "light" | "dark"
/>Access the Echoes client from context.
function MyComponent() {
const echoes = useEchoes();
const handleClick = async () => {
await echoes.bug("Something went wrong");
};
return <button onClick={handleClick}>Report Bug</button>;
}Hook with loading and error states for sending feedback.
function FeedbackForm() {
const { send, isLoading, isSuccess, error, reset } = useFeedback();
const [message, setMessage] = useState("");
const handleSubmit = async (e) => {
e.preventDefault();
await send({ category: "bug", message });
};
if (isSuccess) {
return (
<div>
<p>Thanks!</p>
<button onClick={reset}>Send more</button>
</div>
);
}
return (
<form onSubmit={handleSubmit}>
<textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<button disabled={isLoading}>
{isLoading ? "Sending..." : "Submit"}
</button>
{error && <p>{error.message}</p>}
</form>
);
}The SDK throws EchoesError for various error conditions:
import { EchoesError, ErrorCodes } from "@echoessh/sdk";
try {
await echoes.send({ category: "bug", message: "Test" });
} catch (error) {
if (error instanceof EchoesError) {
switch (error.code) {
case ErrorCodes.INVALID_API_KEY:
console.error("Check your API key");
break;
case ErrorCodes.RATE_LIMITED:
console.error("Too many requests, try again later");
break;
case ErrorCodes.NETWORK_ERROR:
console.error("Network error:", error.message);
break;
}
}
}The SDK is written in TypeScript and includes full type definitions:
import type {
EchoesConfig,
FeedbackCategory,
SendFeedbackParams,
FeedbackResponse,
} from "@echoessh/sdk";// app/providers.tsx
"use client";
import { EchoesProvider } from "@echoessh/sdk/react";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<EchoesProvider config={{ apiKey: process.env.NEXT_PUBLIC_ECHOES_API_KEY! }}>
{children}
</EchoesProvider>
);
}import { Echoes } from "@echoessh/sdk";
const echoes = new Echoes({
apiKey: process.env.ECHOES_API_KEY!,
});
// In your API route or server action
export async function submitFeedback(formData: FormData) {
await echoes.send({
category: formData.get("category") as any,
message: formData.get("message") as string,
userIdentifier: getCurrentUser()?.email,
});
}import { useEchoes } from "@echoessh/sdk/react";
import { useState } from "react";
function QuickFeedbackButton() {
const echoes = useEchoes();
const [isOpen, setIsOpen] = useState(false);
const handleQuickFeedback = async (type: "π" | "π") => {
await echoes.send({
category: type === "π" ? "praise" : "bug",
message: `Quick feedback: ${type}`,
metadata: { quick: true },
});
setIsOpen(false);
};
return (
<div>
<button onClick={() => setIsOpen(!isOpen)}>Feedback</button>
{isOpen && (
<div>
<button onClick={() => handleQuickFeedback("π")}>π</button>
<button onClick={() => handleQuickFeedback("π")}>π</button>
</div>
)}
</div>
);
}MIT