Complete API reference and usage examples for the Quotient SDK
import { QuotientClient } from '@quotientjs/client';
const client = await QuotientClient.init({
apiKey: string, // Required: Your API key (pk_* or sk_*)
baseUrl?: string, // Optional: API endpoint (defaults to https://api.getquotient.ai)
});
The client provides access to four main APIs:
client.analytics
- Event trackingclient.people
- Customer profile managementclient.auth
- Authentication utilitiesclient.store
- Client storage managementTrack user interactions and behavior with the analytics API.
analytics.event(params)
Tracks an analytics event.
await client.analytics.event({
eventType: "pageView" | "search",
// Additional fields depend on eventType
});
Page View Events:
await client.analytics.event({
eventType: 'pageView',
pathname?: string, // Optional: defaults to window.location.pathname
pageUrl?: string, // Optional: defaults to window.location.href
});
Search Events:
await client.analytics.event({
eventType: 'search',
searchQuery: string, // Required: The search query
pathname?: string, // Optional: defaults to window.location.pathname
pageUrl?: string, // Optional: defaults to window.location.href
});
All events automatically include:
Manage customer profiles and preferences.
people.upsert(params)
Creates or updates a person record based on email address.
const { personId } = await client.people.upsert({
emailAddress: string, // Required: Primary identifier
// Optional:
// The desired email marketing state for the user.
// If not set, we will fall back on the following rules:
// - If a person with this email already exists, we will use their existing
// email marketing state.
// - If a person with this email does not exist, we will default to SUBSCRIBED
// if double opt in is not enabled for your business.
// - Otherwise, the user will be set as PENDING and will receive an email
// to confirm their subscription.
// If set, the person will be set to the desired email marketing state.
emailMarketingState?: 'SUBSCRIBED' | 'UNSUBSCRIBED',
firstName?: string,
lastName?: string,
jobTitle?: string,
// Optional:
// The lists to add the person to, designated by their slug.
// i.e. if you view such a list at getquotient.ai/s/<your-business-id>/lists/my-list-slug,
// then you would set lists to ['my-list-slug'].
// If not set, the person will not be added to any lists.
lists?: string[], // Array of list slugs
// Optional:
// Custom properties to add to the person.
// These properties must be pre-defined in your business
// at getquotient.ai/s/<your-business-id>/custom-properties
properties?: { // Custom properties (must be pre-defined)
[key: string]: string | number | boolean | Date
}
});
Email Marketing States:
SUBSCRIBED
- Opted in to marketing emailsUNSUBSCRIBED
- Opted out of marketing emailsReturns:
{
personId: string; // Unique identifier (CUID format)
}
Utilities for authentication and API key verification.
auth.whoami()
Returns information about the current API key and client context.
const info = await client.auth.whoami({});
Returns:
{
businessId: string,
keyType: 'public' | 'private',
scopes: string[],
// the origin of the request, if provided.
origin?: string,
// the ip address of the request, if provided.
// this is useful for debugging and fraud detection.
ipAddress?: string,
// the device id of calling client, if available.
deviceId?: string,
// the session id of calling client, if available.
sessionId?: string,
// the browser fingerprint of calling client, if available.
browserFingerprint?: string,
// the person id of calling client, if available.
personId?: string
}
We track the following identifiers in the browser:
people.upsert
, this will be set to the person id of the person created or updated.
This is useful for strongly associating a person with a device or session for tracking purposes and analytics.// Get stored identifiers
const deviceId = await client.store.deviceId(); // Persistent device ID
const sessionId = await client.store.sessionId(); // Session ID
const fingerprint = await client.store.browserFingerprint(); // Browser fingerprint
const personId = await client.store.personId(); // Person ID (if identified)
// Clear all stored data
await client.store.clear();
Storage Locations:
Utilities for retrieving and displaying blogs written in Quotient.
blog.get()
Retrieve an individual blog using a blog slug
.
const { blog } = client.blog.get({
slug: string // Required primary identifief
rawHtml?: boolean //Optional: Return the raw HTML of blog content in response
});
Returns
{
blog: {
id: string,
title: string,
slug: string,
content: JSON, // Raw JSON object that can be rendered with sdk-react
dominantImageUrl?: string | null,
publishDate: Date | null,
rawHtml?: string| null, // Raw HTML that can be returned with rawHtml param
authors: {
id: string,
name: string,
emailAddress?: string | null,
avatarUrl?: string | null
}[],
metaDescription: string | null,
tags: {
id: string,
name: string,
description?: string | null
}[]
}
}
blog.list()
Retrieves a list of blogs, paginated, with the ability to filter by status, author, or tags
const { blogs, pageData } = client.blog.list(
// List of author ids to filter blogs by
authorIds?: string[];
// List of tag ids to filter blogs by
tagIds?: string[];
// List of statuses to filter blogs by
statuses?: "PLANNING" | "DRAFT" | "APPROVED" | "ACTIVE"[];
search?: string; // string to search for in blog titles or descriptions
page?: number; // default to 1
limit?: number; // number of blogs per page, default to 50
);
Returns
{
blogs: {
id: string,
title: string,
slug: string,
content: JSON, // Raw JSON object that can be rendered with sdk-react
dominantImageUrl?: string | null,
publishDate: Date | null,
authors: {
id: string,
name: string,
emailAddress?: string | null,
avatarUrl?: string | null
}[],
metaDescription: string | null,
tags: {
id: string,
name: string,
description?: string | null
}[]
}[]
pageData: {
page: number,
limit: number,
total: number,
isNextPageAvailable: boolean,
}
}
blog.listAuthors()
List available authors with optional filtering and pagination.
const response = await client.blog.listAuthors({
search: "John", // Optional: Filter authors by name
page: 1, // Optional: Page number (default: 1)
limit: 10, // Optional: Items per page (default: 10)
});
Returns
{
authors: {
id: string;
name: string;
emailAddress?: string | null;
avatarUrl?: string | null;
}[];
pageData: {
page: number;
limit: number;
total: number;
isNextPageAvailable: boolean;
};
};
When using @quotientjs/react
, additional hooks are available:
useQuotient()
Access the Quotient client and context within React components.
import { useQuotient } from "@quotientjs/react";
function MyComponent() {
const {
client, // QuotientClient instance or null
isInitializing, // boolean: true while client is initializing
error, // Error or null if initialization failed
initialize, // () => Promise<void>: Manual initialization
reset, // () => Promise<void>: Clear storage and reinitialize
trackPageView, // () => Promise<void>: Manual page view tracking
} = useQuotient();
}
QuotientProvider
Context provider that initializes and manages the SDK.
<QuotientProvider
clientOptions={{
apiKey: string, // Required: Your public API key
baseUrl?: string, // Optional: API endpoint
}}
autoInitialize?: boolean, // Default: true - Initialize on mount
autoTrackPageViews?: boolean // Default: false - Auto-track page views
>
{children}
</QuotientProvider>
After Login/Signup:
async function identifyUser(user) {
const { personId } = await client.people.upsert({
emailAddress: user.email,
firstName: user.firstName,
lastName: user.lastName,
emailMarketingState: "SUBSCRIBED",
lists: ["customers", "newsletter"],
properties: {
plan: user.subscription,
signupDate: new Date(),
lastLogin: new Date(),
totalPurchases: user.purchaseCount,
},
});
console.log(`User identified: ${personId}`);
}
From a Contact Form:
async function handleContactForm(formData) {
try {
// Save to Quotient
await client.people.upsert({
emailAddress: formData.email,
firstName: formData.firstName,
lastName: formData.lastName,
lists: ["leads"],
properties: {
message: formData.message,
source: "contact-form",
submittedAt: new Date(),
},
});
// Track the submission
await client.analytics.event({
eventType: "formSubmit",
formName: "contact",
});
// Continue with your form handling...
} catch (error) {
console.error("Failed to save lead:", error);
}
}
Search Implementation:
// Debounced search tracking
import { debounce } from "lodash";
const trackSearch = debounce(async (query) => {
if (query.length > 2) {
// Only track meaningful searches
await client.analytics.event({
eventType: "search",
searchQuery: query,
});
}
}, 500);
// In your search component
function SearchBar() {
const handleSearch = (e) => {
const query = e.target.value;
setSearchQuery(query);
trackSearch(query);
performSearch(query);
};
return <input onChange={handleSearch} />;
}
Page View Tracking (SPA):
// For single-page applications without auto-tracking
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
function PageTracker() {
const location = useLocation();
const { client } = useQuotient();
useEffect(() => {
client?.analytics.event({
eventType: "pageView",
pathname: location.pathname,
pageUrl: window.location.href,
});
}, [location, client]);
return null;
}
Preference Center:
function EmailPreferences({ userEmail }) {
const [state, setState] = useState("SUBSCRIBED");
const [lists, setLists] = useState(["newsletter", "promotions"]);
const { client } = useQuotient();
const updatePreferences = async () => {
try {
await client.people.upsert({
emailAddress: userEmail,
emailMarketingState: state,
lists: state === "SUBSCRIBED" ? lists : [], // Remove from lists if unsubscribed
});
toast.success("Preferences updated");
} catch (error) {
toast.error("Failed to update preferences");
}
};
return (
<div>
<label>
<input
type="radio"
checked={state === "SUBSCRIBED"}
onChange={() => setState("SUBSCRIBED")}
/>
Subscribed - Receive marketing emails
</label>
<label>
<input
type="radio"
checked={state === "UNSUBSCRIBED"}
onChange={() => setState("UNSUBSCRIBED")}
/>
Unsubscribed - No marketing emails
</label>
{state === "SUBSCRIBED" && (
<div>
<h3>Email Lists:</h3>
<label>
<input
type="checkbox"
checked={lists.includes("newsletter")}
onChange={(e) => {
if (e.target.checked) {
setLists([...lists, "newsletter"]);
} else {
setLists(lists.filter((l) => l !== "newsletter"));
}
}}
/>
Weekly Newsletter
</label>
{/* More list options... */}
</div>
)}
<button onClick={updatePreferences}>Save Preferences</button>
</div>
);
}
// utils/quotient.js
let clientInstance = null;
export async function getQuotientClient() {
if (!clientInstance) {
clientInstance = await QuotientClient.init({
apiKey: process.env.NEXT_PUBLIC_QUOTIENT_API_KEY,
});
}
return clientInstance;
}
// Only track in production
const shouldTrack = process.env.NODE_ENV === "production";
if (shouldTrack) {
await client.analytics.event({ eventType: "pageView" });
}