Understanding the Audiences API Workflows

Prev Next

Scope

The Audiences API enables programmatic creation and management of Audiences & Structures. Leverage your organizational directory in your third-party system to create and populate complete lists of audiences in Thrive directly through the API, using your own references for seamless mapping with your third-party system.

Note that a Structure is just an Audience which has no parent. Therefore the API manages both Structures and Audiences in the same way, with the main difference being the presence or absence of a parentId.

Authentication

All endpoints require authentication using either:

  • OAuth 2.0 client credentials flow (preferred) - using bearer tokens obtained from the token endpoint, sent via an Authorization: Bearer <access_token> header.
  • HTTP Basic authentication - your Tenant ID as the username and your API secret as the password.

Getting Started

Step 1: Create a Structure

Before creating an audience, you must first create the structure for it. As stated above, the API for both is the same; thus to start you must POST to the audiences API without a parent ID (or with an empty parentId), for example:

{
  "name": "Tech Team",
  "reference": "tech-team-123",
  "parentId": "",
  "category": "structure"
}

View the Related Swagger Documentation

Step 2: Create Child Audiences

Once the structure is created, you can create child audiences by making a POST request to the same audiences API endpoint, but now including the parent ID of the structure you created:

{
  "name": "Frontend Team",
  "reference": "frontend-team-456",
  "parentId": "673db100e5c0503ac23b0e2b",
  "category": "audience"
}

View the Related Swagger Documentation

Step 3: Managing Members

After creating your structure and audiences, you can add members to your audiences using the /rest/v1/audiences/{audienceId}/members endpoint. Members can be added individually or in batches of 1 to 100 identifiers per call:

POST /rest/v1/audiences/frontend-team-456/members

[
  "john.doe@example.com",
  "jane.smith@example.com",
  "dev123"
]

View the Related Swagger Documentation

Frequently Asked Questions

Can existing audiences created within Thrive (i.e. not via API) be managed via the API?

No - only audiences created by the API can be updated by the API. To ensure compatibility with third-party systems driving the audience creation, the API is designed to provide programmatic control over audiences that are specifically created and managed via the API.

Can audiences created via the API live within existing structures in Thrive?

No - API-created audiences can only exist within API-created structures. Audiences created via the API must reside within these API-driven structures to maintain full synchronization and programmatic control with the third-party system.

Can audiences created via the API be edited within Thrive (e.g. adding members, changing permissions)?

No - API-created audiences can only be managed via the API. This means that any updates, such as adding members or changing permissions, must be handled through the API rather than directly within the Thrive interface. By limiting management to the API, we ensure consistent, automated updates are fully aligned with your organizational data.

Can Smart and Manual audiences still be created and managed within the Thrive platform?

Yes! You can still create and manage Smart or Manual audiences as normal in Thrive. The Audience API is not replacing this, it is just a new method that we are introducing which allows for automated management of your entire audience lists.

What happens if an API-created audience structure is deleted?

Deletion does not cascade. The DELETE /audiences/{audienceId} endpoint will only remove an audience or structure if it has no child audiences. If you try to delete a structure that still contains audiences, the call will fail. You must delete all child audiences first, then delete the parent structure. Make sure to consider this order of operations when planning your deletion workflows.

Best Practices

  1. Plan your audience structure - Design your audience structure before implementation to avoid restructuring later.
  2. Use consistent references - Create a consistent naming convention for your references to make management easier.
  3. Batch operations - When adding multiple members, use the batch endpoints to minimize API calls. Each call accepts between 1 and 100 member identifiers.
  4. Error handling - Implement proper error handling for API responses, especially for batch operations that may return partial success (207) responses.
  5. Regular synchronization - Set up regular sync processes to keep your Thrive audiences in sync with your organization's directory.

Audience Hierarchy Management

Structure Design

When designing your audience structure, it's important to consider:

  • Organizational hierarchy (departments, teams, etc.)
  • Access control requirements
  • Content distribution needs
  • Reporting and analytics groupings

Start with a clear top-level structure and then create child audiences that directly map to your organizational units.

Audience Relationships

The relationship between structures and audiences is hierarchical:

Organization (Structure)
├── Department A (Audience)
├── Department B (Audience)
├── Division C (Audience)
└── Division D (Audience)

Each audience must belong to exactly one parent structure, and the structure itself has no parent. Audience levels can only be 1 deep - audiences can only be directly nested under structures, not under other audiences. When a member is added to an audience, they are automatically added to the parent structure as well.

Error Handling and Partial Success Responses

Understanding Partial Success (207)

When adding multiple members or managers to an audience, you may receive a 207 (Multi-Status) response if some operations succeeded while others failed.

The response will contain both successful and failed entries, allowing you to identify and handle problematic records.

// 207 Partial Success Response
{
  "success": {
    "count": 2,
    "entities": [
      { "reference": "john.doe@example.com" },
      { "reference": "jane.smith@example.com" }
    ]
  },
  "failure": {
    "count": 1,
    "entities": [
      {
        "reference": "unknown.user@example.com",
        "reason": "User does not exist."
      }
    ]
  }
}

Common Error Scenarios

Here are common error scenarios you might encounter and how to handle them:

Error Solution
409 - Reference already exists Use a unique reference for each audience
400 / 422 - Invalid audience data (including an invalid or missing parent reference) Validate all fields match required formats and confirm the parent structure exists before creating child audiences
409 - Attempting to delete a structure/audience that still has child audiences Delete all child audiences before deleting the parent structure
207 - Some users not found Pre-validate user existence or handle partial success

Implementation Patterns

Complete Workflow Example

Here's a complete workflow implementation example using JavaScript fetch API:

const TENANT_ID = 'TENANT_ID';       // Also used as the OAuth client_id
const CLIENT_SECRET = 'API_SECRET';  // Your API secret, used as the OAuth client_secret
const TOKEN_URL = `https://public.api.learn.link/oauth2/token/${TENANT_ID}`;
const API_BASE = 'https://public.api.learn.link/rest/v1';

// Simple in-memory token cache so we don't request a new token on every call
let cachedToken = null;
let tokenExpiresAt = 0;

// Step 0: Obtain (and cache) an OAuth 2.0 access token via the client credentials flow
async function getAccessToken() {
  if (cachedToken && Date.now() < tokenExpiresAt) {
    return cachedToken;
  }

  const response = await fetch(TOKEN_URL, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: TENANT_ID,
      client_secret: CLIENT_SECRET,
      scope: 'api/all'
    })
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Failed to obtain access token: ${error.message}`);
  }

  const data = await response.json();
  cachedToken = data.access_token;
  // Refresh a little before actual expiry to avoid edge-case failures
  tokenExpiresAt = Date.now() + (data.expires_in - 60) * 1000;

  return cachedToken;
}

// Step 1: Create the top-level structure
async function createStructure() {
  const token = await getAccessToken();

  const response = await fetch(`${API_BASE}/audiences`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer ' + token
    },
    body: JSON.stringify({
      name: "Engineering Department",
      reference: "eng-dept-001",
      parentId: "",
      category: "structure"
    })
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Failed to create structure: ${error.message}`);
  }

  return await response.json();
}

// Step 2: Create a child audience
async function createDepartment(parentRef) {
  const token = await getAccessToken();

  const response = await fetch(`${API_BASE}/audiences`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer ' + token
    },
    body: JSON.stringify({
      name: "Frontend Department",
      reference: "frontend-dept-002",
      parentId: parentRef,
      category: "audience"
    })
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Failed to create team: ${error.message}`);
  }

  return await response.json();
}

// Step 3: Add members to the audience
async function addMembers(audienceRef, members) {
  const token = await getAccessToken();

  const response = await fetch(`${API_BASE}/audiences/${audienceRef}/members`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer ' + token
    },
    body: JSON.stringify(members)
  });

  // Handle both success and partial success responses
  if (response.status === 200 || response.status === 207) {
    const result = await response.json();

    // Check for partial success
    if (response.status === 207 && result.failure && result.failure.count > 0) {
      console.warn('Some members could not be added:', result.failure);
    }

    return result;
  }

  const error = await response.json();
  throw new Error(`Failed to add members: ${error.message}`);
}

// Execute the complete workflow
async function setupAudienceStructure() {
  try {
    // Step 1: Create structure
    const structure = await createStructure();
    console.log('Structure created:', structure);

    // Step 2: Create department
    const department = await createDepartment(structure.reference);
    console.log('Department created:', department);

    // Step 3: Add members
    const members = [
      "john.doe@example.com",
      "jane.smith@example.com",
      "frontend-dev-123"
    ];
    const result = await addMembers(department.reference, members);
    console.log('Members added:', result);

    return {
      structure,
      department,
      membersResult: result
    };
  } catch (error) {
    console.error('Workflow failed:', error);
    throw error;
  }
}

Synchronization Strategies

Incremental Updates

For organizations with frequent, small changes to their directory:

  • Implement webhook listeners for directory changes
  • Process changes in real-time as they occur
  • Use individual API calls to update specific audiences

This approach minimizes API load and keeps Thrive audiences closely synchronized with your directory.

Full Reconciliation

For organizations with less frequent, larger batch updates:

  • Schedule regular full synchronizations (e.g., nightly)
  • Export complete audience data from your directory
  • Use PATCH endpoints to replace audience members
  • Track reconciliation status with comparison reports

This approach ensures complete accuracy but requires more API resources and is best for scheduled updates.