How to use Custom Segments via API
Custom Segments let you bring your own data into AdButler and target ads against it. This article walks through the whole flow through the API, from an empty segment to a placement that only serves to the members you choose. For what Custom Segments are and how they compare to User DB, see Custom Segment Targeting.
Custom Segments has to be enabled on your account before any of this works. If it is not, the calls below are rejected no matter how they are formed, so check with your AdButler contact first rather than debugging the requests.
Every call below uses your Administrator API key. See the API reference for authentication and for the full field list on each endpoint.
Content-Type: application/json on every JSON call in this article. Without it the request body is discarded before it is read, and the response names the first required field as missing rather than pointing at the header. A missing header therefore looks exactly like a missing field.How to create a custom segment
A segment is the container. Create it first, then describe the shape of your data.
POST https://api.adbutler.com/v2/custom-segments
{
"name": "Prescriber Demo"
}
{
"object": "custom_segment",
"self": "/v2/custom-segments/10002",
"id": 10002,
"name": "Prescriber Demo",
"num_memberships": 0,
"state": "SETUP",
"created_at": "2026-08-12T16:32:15-04:00",
"updated_at": null
}
A segment reports one of three states. SETUP means it has never completed an upload and cannot be targeted yet. IMPORT means an upload is being ingested right now. ACTIVE means no import is in progress, which is where a new segment lands once its first upload finishes.
How to define its fields
Each field maps one column of your CSV to a name and a type. Create one field per column you want to target on.
POST https://api.adbutler.com/v2/custom-segments/10002/fields
{
"name": "Member ID",
"field": "member_id",
"type": "IDENTIFIER"
}
Repeat for the rest of your columns:
POST https://api.adbutler.com/v2/custom-segments/10002/fields
{
"name": "Specialty",
"field": "specialty",
"type": "TEXT"
}
POST https://api.adbutler.com/v2/custom-segments/10002/fields
{
"name": "Years Active",
"field": "years_active",
"type": "NUMBER"
}
The field value must match the CSV column header exactly. The name is a label for your own reference.
There are four types, and the type decides which comparison operators a rule may use against it:
- IDENTIFIER: the key you pass on the ad call. A segment must have exactly one.
- NUMBER: a numeric value, supporting range comparisons.
- TEXT: a single string value.
- LIST: a comma-separated set of values inside one column. The whole field has to be quoted in the CSV so its commas are not read as column separators.
The operators each type allows are listed under Custom Segments / Definition Rules in the API reference. NUMBER is the only type that supports range comparisons; LIST has its own includes family and none of the equality operators.
Creating a second IDENTIFIER is rejected:
{
"object": "error",
"type": "invalid_request_parameters_error",
"http_status": 400,
"parameters": [
{
"field": "type",
"type": "invalid_value",
"message": "The field 'type' cannot be 'IDENTIFIER' when the segment already has an 'IDENTIFIER' field."
}
]
}
Fields cannot be deleted one at a time. Removing one would orphan any rule pointing at it and would silently change which files the segment accepts. Delete the whole segment instead.
How to upload members
Your CSV needs a header row whose column names match the field values you just created:
member_id,specialty,years_active
DOC-1001,Cardiology,12
DOC-1002,Oncology,4
DOC-1003,Cardiology,7
DOC-1004,Pediatrics,15
DOC-1005,Oncology,9
Requirements:
- The first line must be a header row.
- The file must not begin with a UTF-8 byte order mark (BOM).
- Every field defined on the segment must have a matching column, including the IDENTIFIER. Columns the segment does not define are ignored.
- Every row must have the same number of columns as the header.
- The file must contain at least one data row.
- A LIST value holds several entries in a single column, so quote it:
DOC-1006,"Cardiology,Oncology",8. Commas inside the quotes are part of the value and do not open a new column. Without the quotes the row is rejected for having too many columns.
This endpoint takes multipart/form-data rather than JSON, so it is shown here as a curl command:
curl -X POST https://api.adbutler.com/v2/custom-segments/10002/bulk-upload \
-H "Authorization: Basic YOUR_API_KEY" \
-F 'attributes={"operation":"REPLACE"}' \
-F "file=@members.csv"
file and attributes. Anything else, including operation sent as its own form field, is discarded and the request fails with "The field 'operation' is required". Put every setting inside the attributes JSON string.{
"object": "custom_segments_bulk_upload",
"url": "/v2/custom-segments/10002/bulk-upload",
"data": {
"status": "success",
"task_id": 10001,
"task_url": "/v2/scheduled-tasks/custom-segment-bulk-upload/10001"
}
}
A segment will not accept an upload while an import is already running. Wait for the current task to finish first.
How to check whether an upload succeeded
200 on the upload means the file was accepted and queued, not that the data landed. The segment's own state returns to ACTIVE whether the ingest succeeded or failed, so it cannot tell you which happened. The scheduled task is the only place the outcome is reported.Poll the task_url you were given:
GET https://api.adbutler.com/v2/scheduled-tasks/custom-segment-bulk-upload/10001
{
"object": "scheduled_task",
"url": "/v2/scheduled-tasks/custom-segment-bulk-upload/10001",
"data": {
"started_at": "2026-08-12T19:34:16-04:00",
"completed_at": "2026-08-12T19:35:01-04:00",
"estimated_end_at": "2026-08-12T19:35:01-04:00",
"percent_complete": 1,
"results": {
"status": "success",
"extra": {
"num_added": 5,
"num_updated": 0
}
}
}
}
results turning non-null is the only signal that the task is over. Two neighbouring fields look like progress and are not: started_at is stamped when the upload is queued rather than when the ingest begins, so it is already set on a task that has not run, and percent_complete is a fraction between 0 and 1 rather than a percentage, so a finished task reads 1.
Poll every few seconds. Small files finish almost immediately and large ones take proportionally longer, so treat a task whose results is still null long after that as stuck rather than waiting on it forever.
On success results carries the row counts above.
Two different kinds of failure exist, and they surface in different places.
Anything wrong with the shape of the CSV is caught at upload time and returned as a 400 on the file field, before any task is created. That covers all of the requirements listed above: a byte order mark, a missing identifier column, a row with the wrong number of columns, a file with no data rows. You will see these immediately, in the response to the upload itself.
A task-level failure is rarer and comes back through results, which carries a message instead of the row counts, for example "File could not be read." Handle both. The upload response tells you the file was rejected; the task tells you the ingest was.
How to create a definition and its rules
Members are in the segment now, but nothing can target them yet. A definition is the named, targetable subset. One segment can carry many definitions, so a single upload can feed several of them.
POST https://api.adbutler.com/v2/custom-segments/definitions
{
"name": "Cardiology Prescribers",
"custom_segment": 10002
}
{
"object": "custom_segment_definition",
"self": "/v2/custom-segments/definitions/10003",
"id": 10003,
"name": "Cardiology Prescribers",
"custom_segment": 10002,
"created_at": "2026-08-12T16:52:31-04:00",
"updated_at": null
}
Then add the rules that decide who belongs to it:
POST https://api.adbutler.com/v2/custom-segments/definitions/10003/rules
{
"field": "specialty",
"rule": "=",
"target": "Cardiology"
}
Rules combine with AND. A member belongs to the definition only when every rule on it matches. To express an OR, create a second definition.
A definition cannot be moved to another segment after it is created.
How to target a placement at a definition
The API calls this object a placement. The interface calls the same thing an assignment, so if your ad items were set up through the interface, the placement you want here is one of the assignments you already have. See Assignment overview for how they work.
This article assumes one already exists. List them to find its ID:
GET https://api.adbutler.com/v2/placements?limit=5
Then point it at the definition:
PUT https://api.adbutler.com/v2/placements/19540
{
"custom_segment_definition": 10003
}
Set it to null to turn custom segment targeting off again.
The same field can be sent in the POST that creates a placement, but creating one also needs a zone, an ad item and a schedule, which is beyond what this article covers.
A placement carries a single definition. A second rule set means a second definition on a second placement.
custom_segment_member parameter. That belongs to your ad tag rather than to this API flow, so it is not covered here.How to keep the segment in sync
The operation you send with each upload decides what happens to members already in the segment.
REPLACE leaves the segment containing exactly the members in the file. Anyone missing from the file is removed.
UPDATE adds and updates the rows in the file and removes nothing. Members already in the segment but absent from the file stay in it.
Here is that difference on a segment holding five members, uploading a file with one of them removed:
| operation | num_memberships after |
task results |
|---|---|---|
| UPDATE | 5, unchanged | num_added: 0, num_updated: 0 |
| REPLACE | 4 | num_added: 4, num_updated: 0 |
The UPDATE run reported success and changed nothing. REPLACE is what makes a removal take effect.