Logo

Documentation

SDK Usage

Complete API reference and usage examples for the Quotient SDK

API Reference

Client Initialization

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 tracking
  • client.people - Customer profile management
  • client.auth - Authentication utilities
  • client.store - Client storage management

Analytics API

Track 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:

  • Timestamp
  • Device ID
  • Session ID
  • Browser fingerprint
  • Person ID (if identified)
  • Client context

People API

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 emails
  • UNSUBSCRIBED - Opted out of marketing emails
  • If not specified:
    • Existing people keep their current state
    • New people default based on double opt-in settings

Returns:

{
  personId: string; // Unique identifier (CUID format)
}

Auth API

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
}

Store API

We track the following identifiers in the browser:

  • Device ID - a uuidv4 that is persisted to localStorage
  • Session ID - a uuidv4 that is persisted to sessionStorage
  • Browser fingerprint - a proprietary hash of the browser's user agent and other device characteristics. See here for more information.
  • Person ID - if the client is identified by a call to 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.

Storage Methods

// 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:

  • Device ID: localStorage
  • Session ID: sessionStorage
  • Browser fingerprint: localStorage
  • Person ID: localStorage

Blog API

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;
  };
};

React Hooks

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>

Common Patterns

User Identification

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);
  }
}

Event Tracking

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;
}

Marketing Preferences

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>
  );
}

Performance Tips

1. Singleton Pattern

// 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;
}

2. Conditional Tracking

// Only track in production
const shouldTrack = process.env.NODE_ENV === "production";
 
if (shouldTrack) {
  await client.analytics.event({ eventType: "pageView" });
}

Next Steps