Appearance
Syncing Audiences
The audience endpoints let your CRM, your customer data platform, or any tool that can send a web request keep a Bronco audience up to date. You create the audience once, then add and remove people as your list changes.
People are identified by email address or US phone number. Bronco hashes each value as it arrives and stores only the hashes.
The same key can also list every audience an advertiser has, upload a customer list as a file, and create lookalikes. See Other kinds of audience.
Before you start
You need an API key with the Audiences permission. An Admin creates one under Settings → API Keys: click Create Key and tick Audiences under Permissions. See the API quickstart for the full steps.
The examples below assume the key is in BRONCO_API_KEY.
1. Create an audience
An audience belongs to one advertiser. GET /api-keys/current lists the advertisers your key can use, with their ids.
bash
curl https://agentapi-prod-eastus-1.bronco.run/api/v2/audiences \
-H "Authorization: Bearer $BRONCO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"advertiserId": 42,
"name": "Current customers",
"externalId": "crm-list-8841"
}'json
{
"id": 1287,
"advertiserId": 42,
"advertiserName": "Acme Outdoors",
"name": "Current customers",
"externalId": "crm-list-8841",
"source": "api",
"status": "empty",
"people": 0,
"identifierCounts": { "email": 0, "phone": 0 },
"lastSyncedAt": null,
"lastDeliveredAt": null,
"removalsPending": 0,
"archived": false,
"createdAt": "2026-10-12T21:58:36.440Z",
"klaviyo": null,
"lookalike": null
}Keep the id: every other call uses it.
You can create the audience in Bronco instead of with a request. Go to Settings → Audiences, click Create Audience, then Sync through the API, and give it a name. Bronco then shows its Connection details: the Audience ID and the two addresses used in the next steps.
externalId is optional. It is your own id for the list, such as a CRM list id, and an advertiser can only have one audience with a given externalId. That makes creating safe to retry: a second attempt is refused with 409 and tells you the audience that already has it, in errors.audienceId. You can also look an audience up by it with GET /audiences?externalId=crm-list-8841.
2. Add people
bash
curl https://agentapi-prod-eastus-1.bronco.run/api/v2/audiences/1287/members \
-H "Authorization: Bearer $BRONCO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"members": [
{ "email": "jane@example.com", "phone": "+1 (310) 555-1234" },
{ "email": "sam@example.com" }
]
}'Each person needs at least one of these fields:
| Field | What to send |
|---|---|
email | The email address. Spaces around it and capital letters don't matter. |
emailSha256 | Instead of email, if you can't send the address itself: the SHA-256 hash of the trimmed, lowercased address, as 64 hexadecimal characters. jane@example.com hashes to 8c87b489ce35cf2e2f39f80e282cb2e804932a56a213983eeeb428407d43b52d. |
phone | A US phone number in any common format, with or without the country code. Send the number itself; there is no hashed form of this field. |
Other fields are ignored.
A request can carry up to 10,000 people. Adding someone the audience already has changes nothing, so sending the same request twice is safe.
Three ways to send the body
Use whichever your tool can produce. All three do the same thing.
json
{ "members": [{ "email": "jane@example.com" }, { "email": "sam@example.com" }] }json
[{ "email": "jane@example.com" }, { "email": "sam@example.com" }]json
{ "email": "jane@example.com", "phone": "3105551234" }The last form, one person as a flat object, is for tools that send one request per person, such as a workflow step that calls a URL or an automation that runs once per row. A form post with the same field names works too.
What comes back
json
{
"received": 3,
"accepted": 2,
"rejected": 1,
"added": { "email": 2, "phone": 1 },
"invalid": { "email": 0, "phone": 1 },
"problems": [{ "index": 2, "field": "phone", "reason": "invalidPhone" }],
"problemsTruncated": false,
"ignoredFields": [],
"identifierCounts": { "email": 128404, "phone": 96412 }
}- People with at least one usable field are accepted. The rest are skipped, and
problemssays which ones and why, by their position in the request. The values themselves are never repeated back. addedcounts what was new.identifierCountsis what the audience holds now.ignoredFieldslists field names you sent that Bronco doesn't use, which makes a misspelt field easy to spot.
If no one in the request is usable, the request fails with 400 and the code NO_USABLE_MEMBERS, and errors carries the same detail. This usually means a field is mapped to the wrong column.
3. Remove people
Send the same body to /members/remove, with the email or phone the person was added with.
bash
curl https://agentapi-prod-eastus-1.bronco.run/api/v2/audiences/1287/members/remove \
-H "Authorization: Bearer $BRONCO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "email": "jane@example.com", "phone": "3105551234" }'The response has removed in place of added. Removing someone who isn't in the audience changes nothing.
A removal is recorded straight away and the counts change. A person who has already reached ad delivery takes longer to leave it: removals are passed on once an hour, and it can then take up to 24 hours for the person to stop being reached. The audience's removalsPending counts the removals still on their way, and returns to 0 when all of them have been applied.
If you add someone back while their removal is still on its way, they stay in the audience. They may drop out of ad delivery for a short time first.
Check an audience
bash
curl https://agentapi-prod-eastus-1.bronco.run/api/v2/audiences/1287 \
-H "Authorization: Bearer $BRONCO_API_KEY"status | Meaning |
|---|---|
empty | No one has been added yet. |
processing | People have been added and are being matched for the first time. |
ready | The audience can be used. It stays ready as people are added and removed. |
notEnoughMatches | Too few people could be matched for the audience to be used. An audience needs at least 1,000 matches. |
failed | The audience could not be set up. |
New people are passed on to ad delivery every five minutes, and lastDeliveredAt is when that last happened. The first time, they also have to be matched: an audience becomes ready once at least 1,000 of its people are matched. If it isn't ready after 24 hours its status becomes notEnoughMatches, and it is checked again as more people are added. Once an audience is ready, later additions don't change its status.
lastSyncedAt is when the last add or remove request was accepted, whether or not it changed anything. people is the larger of the two identifierCounts, since one person can be sent with both an email and a phone.
Use the audience in a campaign
The audience appears in Bronco with your other custom audiences: under Settings → Audiences, where its Source is API, and in the Custom audiences section of a strategy, where you can include or exclude it. Audiences kept up to date through the API explains how it behaves there, including when a campaign that uses it can be published.
Rename or archive
PATCH /audiences/1287 with { "name": "Customers (US)" } renames an audience.
POST /audiences/1287/archive archives it. An archived audience stops accepting changes and the hashes stored for it are deleted. This can't be undone.
Both work on every kind of audience except web audiences, which Bronco builds and keeps for each advertiser.
Other kinds of audience
List every audience
GET /audiences lists every audience your key can see, newest first, whichever way it was made. Archived audiences are left out.
| Parameter | Narrows the list to |
|---|---|
advertiserId | One advertiser's audiences. |
source | Audiences from these sources. Separate several with commas, as in ?source=api,upload, or repeat the parameter. |
externalId | The audience created with this externalId. |
source says where an audience's people come from:
source | The audience |
|---|---|
api | Kept up to date through the steps above. The only kind whose people the API can add and remove. |
upload | Made from an uploaded customer list, in Bronco or through the API. It doesn't change after the upload. |
klaviyo | Imported from a Klaviyo list or segment and synced from it every day. klaviyo says how the last sync went. |
lookalike | New people who resemble another audience. lookalike says which one. |
web | Built by Bronco from what visitors do on the advertiser's site. Always ready. |
thirdParty | Made from data-marketplace segments. |
More sources may be added, so treat a value you don't recognise as another kind of audience rather than an error.
people is how many people an api, upload or klaviyo audience holds, and how many people a lookalike was built with. It is null for web and thirdParty audiences, and for a lookalike that isn't built yet.
Adding or removing people in an audience whose source isn't api is refused with 409 and the code NOT_AN_API_AUDIENCE. To keep a list up to date, create an api audience instead of uploading files.
Upload a customer list
Send a CSV file to /audiences/upload as a form upload, with the advertiser and a name for the new audience:
bash
curl https://agentapi-prod-eastus-1.bronco.run/api/v2/audiences/upload \
-H "Authorization: Bearer $BRONCO_API_KEY" \
-F advertiserId=42 \
-F name="Cart abandoners (30d)" \
-F file=@cart-abandoners.csvThe file follows the same rules as an upload in Bronco: a header row with an email column, a phone column, or both, and up to 500,000 rows. Other columns are ignored. A row is added when its email or its US phone number can be used, and skipped when neither can. The file isn't kept: each value is hashed as it is read.
The response is the new audience with an upload summary:
json
{
"id": 1291,
"source": "upload",
"status": "processing",
"people": 12803,
"upload": {
"rows": 12840,
"skipped": 37,
"skippedRows": [{ "line": 14, "reason": "invalidPhone" }],
"skippedRowsTruncated": true
}
}(The response above is shortened.) line counts the header row as line 1. The values in a skipped row are never repeated back.
A file with no usable row is refused with 400, and the audience isn't kept.
Create a lookalike
A lookalike reaches new people who resemble one of the advertiser's audiences. The source has to be ready, come from the API, an upload or Klaviyo, and hold email addresses.
bash
curl https://agentapi-prod-eastus-1.bronco.run/api/v2/audiences/lookalike \
-H "Authorization: Bearer $BRONCO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"advertiserId": 42,
"sourceAudienceId": 1287,
"similarity": 90,
"name": "Current customers lookalike 90%"
}'similarity is one of 90, 80, 70, 60 or 50: how closely the new people resemble the source, in percent. A higher value is more similar and reaches fewer people. People already in the source are left out.
A source with more than 1,000,000 email addresses is modeled on a sample of 1,000,000 of them.
The lookalike is built once, in the background, and its status is processing until it can be used. It doesn't follow later changes to the source. If its status becomes failed, POST /audiences/1293/retry starts the build again from where it stopped. Every other kind of audience is retried by Bronco on its own.
Limits
| Limit | Value |
|---|---|
| People in one add or remove request | 10,000 |
| Size of one add or remove request | 5 MB |
| Add and remove requests | 600 a minute per key |
| All other requests | 120 a minute per key |
| Emails and phone numbers in one audience | 25,000,000 in total |
| Emails and phone numbers in all of an advertiser's audiences | 50,000,000 in total, not counting archived audiences |
api audiences per advertiser | 200 |
| Rows in one uploaded file | 500,000 |
| Size of one uploaded file | 100 MB |
Add and remove requests are counted separately from everything else, so a busy sync doesn't use up the allowance for your other calls. Send several people per request where your tool allows it.
Errors
These are in addition to the ones in Authentication, limits, and errors.
| Status | code | When |
|---|---|---|
400 | NO_USABLE_MEMBERS | No one in the request has a usable email or phone number. |
400 | TOO_MANY_MEMBERS | The request has more than 10,000 people. |
400 | INVALID_MEMBERS_BODY | The body isn't one of the three accepted forms. |
400 | An upload has no file, isn't a CSV with an email or phone column, has more than 500,000 rows, or has no usable row. | |
400 | A lookalike's source is a lookalike, a web or data-marketplace audience, or holds no email address; or a retry is for an audience that isn't a lookalike. | |
404 | The audience or advertiser doesn't exist, or belongs to another account. | |
409 | EXTERNAL_ID_IN_USE | The advertiser already has an audience with this externalId. |
409 | TOO_MANY_AUDIENCES | The advertiser already has 200 api audiences. |
409 | AUDIENCE_ARCHIVED | The audience is archived. |
409 | AUDIENCE_FULL | The audience holds as many emails and phone numbers as it may. |
409 | ADVERTISER_AUDIENCES_FULL | The advertiser's audiences hold as many emails and phone numbers as they may between them. Remove people, or archive an audience you no longer use. |
409 | NOT_AN_API_AUDIENCE | People were added to or removed from an audience whose source isn't api. |
409 | AUDIENCE_BUILT_IN | A web audience was renamed or archived. |
409 | A lookalike's source isn't ready yet, or a retry is for a lookalike that hasn't failed. | |
429 | TOO_MANY_CONCURRENT_UPDATES | Too many requests are changing the same audience at once. Send them one after another, or retry. |
The API reference lists every field.