Contactlevel
Getting Started

Syncing your audiences

Send an existing ContactLevel audience to a connected destination, then check its progress. You can use the same connection ID to start the sync and check its status.

Before you start

You need:

  • Python 3.10+, Node.js 22+, or cURL, depending on your chosen example.
  • A ContactLevel API key with public API and audience sync access.
  • An audience containing the contacts you want to sync.
  • A destination connected and set up in ContactLevel, such as Facebook or Google Ads.

The API key, audience, and connection must belong to the same organization. The API gets your organization from the key; you do not send an organization ID.

1. Copy and paste the setup

Choose Python, JavaScript, or cURL using the tabs. Switching languages updates all examples on this page.

Check your tools. Python needs requests; JavaScript uses Node.js's built-in fetch.

python -m pip install requests

For Python, paste the setup into sync_audiences.py. For JavaScript, use sync_audiences.mjs. For cURL, paste it directly into a Bash or zsh terminal.

Replace YOUR_API_KEY and YOUR_AUDIENCE_ID. Use your API key starting with api_ and the audience's UUID. Keep your filled-in setup private.

"""Audience sync walkthrough. Install requests, then run python sync_audiences.py.""" import json from typing import Any import requests BASE_URL = "https://a.contactlevel.com" API_KEY = "YOUR_API_KEY" AUDIENCE_ID = "YOUR_AUDIENCE_ID" def _request( method: str, path: str, payload: dict[str, Any] | None = None ) -> dict[str, Any]: response = requests.request( method, f"{BASE_URL}{path}", headers={"X-Api-Key": f"Bearer {API_KEY}"}, json=payload, timeout=60, ) print(f"\n{method} {path} -> HTTP {response.status_code}") try: result = response.json() except ValueError: print(response.text) response.raise_for_status() raise print(json.dumps(result, indent=2)) response.raise_for_status() return result def find_audiences() -> dict[str, Any]: return _request("POST", "/api/audiences/search", {"page": 1, "page_size": 25}) def fetch_audience() -> dict[str, Any]: return _request("GET", f"/api/audiences/{AUDIENCE_ID}") def check_connections() -> dict[str, Any]: return _request("POST", "/api/connections/search", {}) def run_sync(connection_id: str) -> dict[str, Any]: return _request( "POST", "/api/audience-syncs/run", {"audience_id": AUDIENCE_ID, "connection_ids": [connection_id]}, ) def check_status( sync_id: str | None = None, *, connection_id: str | None = None ) -> dict[str, Any]: if sync_id and connection_id: raise ValueError("Use either sync_id or connection_id, not both") payload: dict[str, Any] = {"audience_id": AUDIENCE_ID} if sync_id: payload["sync_ids"] = [sync_id] if connection_id: payload["connection_ids"] = [connection_id] return _request("POST", "/api/audience-syncs/status", payload) if __name__ == "__main__": # Leave ONE call uncommented, then run this file again for each step. check_connections() # find_audiences() # fetch_audience() # run_sync(connection_id="YOUR_CONNECTION_ID") # check_status(connection_id="YOUR_CONNECTION_ID") # check_status(sync_id="YOUR_SYNC_ID") # check_status()

For Python and JavaScript, leave only the current step's call uncommented at the bottom of the file, then run the file again for each step. For cURL, paste each step's command into the same terminal.

python sync_audiences.py

Each example prints the HTTP status and response body.

2. Confirm your audience

If you need to find the audience ID, run:

find_audiences()

This calls POST /api/audiences/search and lists the first 25 audiences. Copy the relevant audiences[].id into AUDIENCE_ID. You can change page in the helper to see more results.

Then run:

fetch_audience()

This calls GET /api/audiences/{audience_id}. Check the returned audience.name to confirm you selected the right audience.

If you have not created and populated an audience yet, do that in ContactLevel first. You can also use the API reference to create an audience and add existing contacts. Creating an empty audience does not start a sync.

3. Choose a destination connection

check_connections()

This calls POST /api/connections/search. A connection looks like this:

JSONCode
{ "connections": [ { "connection_id": "00000000-0000-4000-8000-000000000002", "name": "Facebook Ads Sync", "platform": "facebook", "status": "healthy", "configured": true, "setup_complete": true, "ready": true, "blocking_reason": null } ] }

Copy the connection_id for the destination you want. For cURL, replace YOUR_CONNECTION_ID in export CONNECTION_ID="YOUR_CONNECTION_ID" and run that assignment again. Choose a connection with ready: true. If it is false, blocking_reason explains what needs fixing in ContactLevel before you can sync.

These are example IDs; use the IDs from your own responses.

4. Start the sync

Replace YOUR_CONNECTION_ID with the ID from step 3:

run_sync(connection_id="YOUR_CONNECTION_ID")

This sends:

Code
POST /api/audience-syncs/run
JSONCode
{ "audience_id": "00000000-0000-4000-8000-000000000001", "connection_ids": ["00000000-0000-4000-8000-000000000002"] }

This single request creates or reuses the destination sync, enables it if necessary, and starts it. No configuration step or sync ID is required. The /api/audience-syncs/configure endpoint is deprecated and remains available only for backward compatibility.

The request waits up to 25 seconds for startup, then returns HTTP 202. Example:

JSONCode
{ "audience_id": "00000000-0000-4000-8000-000000000001", "results": [ { "sync_id": "00000000-0000-4000-8000-000000000003", "connection_id": "00000000-0000-4000-8000-000000000002", "platform": "facebook", "disposition": "started", "sync_run_id": "00000000-0000-4000-8000-000000000004", "error_message": null } ] }

HTTP 202 does not mean the sync completed. Check each result's disposition:

DispositionWhat to do
startedThe run started or already finished. Check status next.
already_runningA run already exists and is active. Check its status.
pendingStartup is not confirmed yet. Check status before trying again.
failedRead error_message and resolve the problem before retrying.

Explicitly selected destinations are automatically enabled, including previously disabled ones. No separate configure call is needed. The connection must already be set up and ready; unresolved destination deletions still block a run.

Omit connection_ids or send [] to run existing enabled destinations only. This does not enable disabled destinations or add new ones.

5. Check progress

Use the same connection ID:

check_status(connection_id="YOUR_CONNECTION_ID")

This calls POST /api/audience-syncs/status with audience_id and connection_ids, and returns the latest sync for that connection in this audience.

Example response:

JSONCode
{ "audience_id": "00000000-0000-4000-8000-000000000001", "syncs": [ { "sync_id": "00000000-0000-4000-8000-000000000003", "platform": "facebook", "status": "IN_PROGRESS", "total_records": 597, "records_processed": 200, "records_successful": 190, "records_failed": 10, "records_skipped": 0, "error_message": null } ] }

Run the status check again after a few seconds. You do not need to call run_sync() again to refresh progress.

StatusMeaning
NOT_STARTED or not_readyA sync exists but has not started or is not ready.
PENDING, SCHEDULED, or DELAYEDThe run is waiting. Check again later.
IN_PROGRESSThe run is still working.
COMPLETEDThe run finished successfully.
COMPLETED_WITH_ERRORSThe run finished with some errors. Review the counts and any error message.
FAILEDThe run failed. Read error_message before retrying.

Counts describe the ContactLevel run, not the destination's matched audience size. Skipped records are reported separately from failed records. Before a run exists, status reports the saved startup state with zero counters.

Other ways to check status

Check a particular sync using sync_id from the run response. For cURL, first set SYNC_ID to that value:

check_status(sync_id="YOUR_SYNC_ID")

Check every destination configured for this audience:

check_status()

Use either connection_id or sync_id in the helper, not both. The API accepts their plural forms as lists: connection_ids or sync_ids. Omitting both lists, or sending both empty, returns all destinations.

Do not pass sync_run_id to a status filter. It identifies an individual execution; sync_id identifies the audience's sync configuration.

If a request fails

The examples print the response body when an HTTP request fails. Read its detail field. For HTTP 202, inspect results[].error_message instead.

ResultNext step
401Check your API key.
402Check the access or billing requirement in the response.
404 on a connection status checkConfirm the audience and connection IDs. A connection has no sync to check until one has been created for this audience.
409Read detail; the connection may be unavailable, incomplete, or the destination may be awaiting deletion.
422Use valid UUIDs without duplicates. For status, populate only one ID list.
A timeout or pending startupCheck status before resubmitting; the run may still start.
failed with an internal service authentication or scope errorContact support with the sync ID and error message.

See the API reference for the full request and response schemas.

Last modified on