Logo

Audience

Manage people, companies, and lists using the Quotient SDK

Overview

The audience API lets you manage people, companies, and lists.

Both the client and server SDKs expose the audience API under audience.*. The client SDK includes audience.people only. The server SDK adds audience.companies and audience.lists.

EndpointAPI KeyServer SDKClient SDK
Upsert personpublic or privateaudience.people.upsert()audience.people.upsert()
Upsert companypublic or privateaudience.companies.upsert()
Upsert listprivate onlyaudience.lists.upsert()
List all listsprivate onlyaudience.lists.list()
Get a listprivate onlyaudience.lists.get()
List people in a listprivate onlyaudience.lists.listPeople()
Add people to a listprivate onlyaudience.lists.addPeople()
Remove people from a listprivate onlyaudience.lists.removePeople()

To manage list membership from the browser, use audience.people.upsert() with the lists parameter.

People

Upsert a Person

audience.people.upsert(params)

POST /api/v0/audience/people

Auth: public or private key · scope: AUDIENCE_WRITE

Creates or updates a person record based on email address.

ParamTypeRequiredDescription
emailAddressstringYesPrimary identifier
emailMarketingState"SUBSCRIBED" | "UNSUBSCRIBED"NoMarketing email opt-in state
firstNamestringNoFirst name
lastNamestringNoLast name
jobTitlestringNoJob title
leadScorenumberNoInteger lead score (default 0)
listsstring[]NoList slugs to add the person to
propertiesRecord<string, string | number | boolean | Date>NoCustom properties (must be pre-defined)
const { personId } = await client.audience.people.upsert({
  emailAddress: "user@example.com",
  firstName: "Jane",
  lastName: "Doe",
  emailMarketingState: "SUBSCRIBED",
  leadScore: 50,
  lists: ["newsletter", "customers"],
  properties: {
    plan: "pro",
    signupSource: "landing-page",
  },
});

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

Companies

Upsert a Company

audience.companies.upsert(params)

POST /api/v0/audience/companies

Auth: public or private key · scope: AUDIENCE_WRITE

Creates or updates a company record keyed on domain.

ParamTypeRequiredDescription
domainstringYesCompany domain (upsert key)
namestringNoCompany name
descriptionstringNoDescription
industriesstring[]NoIndustry tags
totalEmployeesnumberNoEmployee count
address1stringNoStreet address
citystringNoCity
regionCodestringNoState/region code
countrystringNoCountry code
zipstringNoPostal code
socialLinkLinkedInstringNoLinkedIn URL
propertiesRecord<string, string | number | boolean | Date>NoCustom properties (must be pre-defined)
const { companyId } = await client.audience.companies.upsert({
  domain: "acme.com",
  name: "Acme Corp",
  description: "Makes everything",
  industries: ["manufacturing"],
  totalEmployees: 500,
  properties: {
    arr: 1200000,
    fundingStage: "Series B",
  },
});

Returns:

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

Lists

The Lists API lets you create and manage audience lists programmatically. Lists are identified by a unique slug and can contain people identified by either their person ID or email address.

Most list operations require a private API key. The exceptions are addPeople and removePeople, which also accept public keys so you can manage list membership from your frontend.

import { QuotientServer } from "@quotientjs/server";

const client = new QuotientServer({
  privateKey: "sk_your_private_api_key",
});

Create or Update a List

audience.lists.upsert(options)

POST /api/v0/audience/lists

Auth: private key only · scope: AUDIENCE_WRITE

The slug is always the upsert key — you either provide it directly, or it gets derived from the name.

ParamTypeRequiredDescription
namestringOne of name or slugList name (slug auto-derived if slug omitted)
slugstringOne of name or slugExplicit slug to target
descriptionstringNoList description
// By name — slug is auto-generated ("newsletter-subscribers")
await client.audience.lists.upsert({
  name: "Newsletter Subscribers",
  description: "People who opted into our weekly newsletter",
});

// By slug — target an existing list directly
await client.audience.lists.upsert({
  slug: "newsletter-subscribers",
  description: "Updated description",
});

// By slug + name — target by slug, rename the list
await client.audience.lists.upsert({
  slug: "newsletter-subscribers",
  name: "Weekly Newsletter",
});

Upsert behavior:

InputLookup keyNot foundFound
{ name }generateSlug(name)Create with name + generated slugUpdate name
{ name, description }generateSlug(name)Create with bothUpdate name + description
{ slug }slugCreate (name defaults to slug)No-op
{ slug, name }slugCreate with name + slugUpdate name
{ slug, description }slugCreate (name defaults to slug)Update description
{ slug, name, description }slugCreate with all fieldsUpdate name + description

Returns:

{
  listId: string;
  name: string;
  slug: string;
}

List All Lists

audience.lists.list(options?)

GET /api/v0/audience/lists

Auth: private key only · scope: AUDIENCE_READ

ParamTypeRequiredDescription
searchstringNoFilter by name
pagenumberNoPage number (default 1)
limitnumberNoResults per page (default 20)
const { lists, pageData } = await client.audience.lists.list({
  search: "newsletter",
  page: 1,
  limit: 20,
});

Returns:

{
  lists: {
    id: string;
    name: string;
    slug: string;
    description: string | null;
    peopleCount: number;
    createdAt: string;
    updatedAt: string;
  }[];
  pageData: {
    page: number;
    limit: number;
    total: number;
    isNextPageAvailable: boolean;
  };
}

Get a Single List

audience.lists.get(options)

GET /api/v0/audience/lists/{slug}

Auth: private key only · scope: AUDIENCE_READ

ParamTypeRequiredDescription
slugstringYesList slug
const { list } = await client.audience.lists.get({
  slug: "newsletter-subscribers",
});

Returns:

{
  list: {
    id: string;
    name: string;
    slug: string;
    description: string | null;
    peopleCount: number;
    createdAt: string;
    updatedAt: string;
  };
}

List People in a List

audience.lists.listPeople(options)

GET /api/v0/audience/lists/{slug}/people

Auth: private key only · scope: AUDIENCE_READ

ParamTypeRequiredDescription
listSlugstringYesList slug
searchstringNoFilter by email
pagenumberNoPage number (default 1)
limitnumberNoResults per page (default 20)
const { people, pageData } = await client.audience.lists.listPeople({
  listSlug: "newsletter-subscribers",
  search: "jane",
  page: 1,
  limit: 20,
});

Returns:

{
  people: {
    personId: string;
    emailAddress: string;
    firstName: string | null;
    lastName: string | null;
  }[];
  pageData: {
    page: number;
    limit: number;
    total: number;
    isNextPageAvailable: boolean;
  };
}

Add People to a List

audience.lists.addPeople(options)

POST /api/v0/audience/lists/{slug}/people

Auth: private key only · scope: AUDIENCE_WRITE

Add up to 100 people per request. Each entry can reference an existing person by ID, or provide an email address to upsert a person and add them in one call.

ParamTypeRequiredDescription
listSlugstringYesList slug
peopleAddPersonEntry[]YesArray of people to add (max 100)

Each entry in people is one of:

ParamTypeRequiredDescription
personIdstringYes (if no email)Existing person ID
emailAddressstringYes (if no ID)Email to upsert
firstNamestringNoFirst name (with email only)
lastNamestringNoLast name (with email only)
jobTitlestringNoJob title (with email only)
leadScorenumberNoLead score (with email only)
propertiesRecord<string, ...>NoCustom properties (with email only)
await client.audience.lists.addPeople({
  listSlug: "newsletter-subscribers",
  people: [
    { personId: "clx..." },
    {
      emailAddress: "jane@example.com",
      firstName: "Jane",
      lastName: "Doe",
      properties: { plan: "pro" },
    },
  ],
});

Returns:

{
  added: number;
  listSlug: string;
  listId: string;
}

Remove People from a List

audience.lists.removePeople(options)

DELETE /api/v0/audience/lists/{slug}/people

Auth: private key only · scope: AUDIENCE_WRITE

Remove up to 100 people per request. Each entry can reference a person by ID or by email address. This operation is idempotent — removing a person who is not in the list (or referencing an email that doesn't exist) is a no-op.

ParamTypeRequiredDescription
listSlugstringYesList slug
peopleRemovePersonEntry[]YesArray of people to remove (max 100)

Each entry in people is one of:

ParamTypeDescription
personIdstringExisting person ID
emailAddressstringPerson's email address
await client.audience.lists.removePeople({
  listSlug: "newsletter-subscribers",
  people: [
    { personId: "clx..." },
    { emailAddress: "jane@example.com" },
  ],
});

Returns:

{
  removed: number;
  listSlug: string;
  listId: string;
}

Common Patterns

User Identification on Signup

async function identifyUser(user) {
  const { personId } = await client.audience.people.upsert({
    emailAddress: user.email,
    firstName: user.firstName,
    lastName: user.lastName,
    emailMarketingState: "SUBSCRIBED",
    leadScore: user.leadScore ?? 0,
    lists: ["customers", "newsletter"],
    properties: {
      plan: user.subscription,
      signupDate: new Date(),
      lastLogin: new Date(),
      totalPurchases: user.purchaseCount,
    },
  });

  console.log(`User identified: ${personId}`);
}

Contact Form Capture

async function handleContactForm(formData) {
  try {
    await client.audience.people.upsert({
      emailAddress: formData.email,
      firstName: formData.firstName,
      lastName: formData.lastName,
      lists: ["leads"],
      properties: {
        message: formData.message,
        source: "contact-form",
        submittedAt: new Date(),
      },
    });

    await client.analytics.event({
      eventType: "formSubmit",
      formName: "contact",
    });
  } catch (error) {
    console.error("Failed to save lead:", error);
  }
}

Syncing a List from an External Source

const client = new QuotientServer({ privateKey: "sk_..." });

// Ensure the list exists
await client.audience.lists.upsert({
  name: "Active Customers",
  description: "Synced from billing system",
});

// Add people in batches of 100
const customers = getActiveCustomers(); // your data source
for (let i = 0; i < customers.length; i += 100) {
  const batch = customers.slice(i, i + 100);
  await client.audience.lists.addPeople({
    listSlug: "active-customers",
    people: batch.map((c) => ({
      emailAddress: c.email,
      firstName: c.firstName,
      lastName: c.lastName,
      properties: {
        plan: c.plan,
        mrr: c.mrr,
      },
    })),
  });
}

Next Steps