Audiences
Cookbook:
Upload a CSV and match it to the voter file:
- Upload a CSV.
- Using the audience ID returned in step 1, retrieving that audience every 5 seconds or so until the uploadedAudienceCsv.status value in the audience response is READY_FOR_COLUMN_SELECTION.
- Match your uploaded CSV to the voter file.
- Using that same audience ID (which will be returned in step 3 as well), retrieve that audience every 5 seconds or so until the audience's status is ACTIVE.
Import a list from BigQuery and match it to the voter file:
- If you haven't already enabled our BigQuery integration, open the Matchbook app and configure it in your organization's settings.
- Import a BigQuery table from your dataset.
- Follow steps 2-4 from the instructions in the previous section ("Upload a CSV"),
Create a lookalike of a matched CSV audience:
- Follow steps 1-4 in upload a CSV and match it to the voter file above to match your CSV to the voter file.
- Using the audience ID of your matched audience as the baseAudienceId parameter, create a lookalike audience from your matched audience.
- Using the audience ID returned by step 2, retrieve that audience every 5 seconds or so until the audience's status is ACTIVE.
Export an audience:
To export an audience created via uploading a CSV and matching it to the voter file or creating a lookalike of a matched CSV audience:
- Create an export using the audience ID for the audience you'd like to export.
- Poll for updates to the audience until the export's status is no longer IN_PROGRESS.
- If the export's status is now REQUIRES_PAYMENT, purchase it by passing in the export ID returned in step 1.
- Once the export's status is ACTIVE, you can use its downloadUrl or externalBqTableName field to access the data.
If you need to download your export again in the future, you can always access it by retrieving the audience and then finding your export in the exports field on the audience.
Notes:
API Setup:
To get set up to use our API, reach out to request an API token. Once you have your token, you'll need to include it in the Authorization header of all requests: Authorization: Bearer <token>. The base URL for our API is https://app.indigo.engineering/api/v1.
Customer ID:
customerID is an arbitrary string that should identify each of your customers (this could be a hash or simply an integer ID or whatever else you want). The goal here is to separate data by user so that it's easy to tell which records are associated with which of your users. However, if you'd like to pass the same customerId for every request, that will work fine as well.
Note that customerId is enforced at the API level and is required for several endpoints, including all the endpoints that create audiences. For example, if an audience is created with a customer ID of 1 and then /audience is called to retrieve that audience but a customer ID of 2 is passed in, no audience will be returned — in other words, no audience with the requested ID exists for customer 2.
Errors:
To indicate an error, the API will return a JSON response containing an error key whose value will be a human-readable error message. In most cases, there will also be an errorType key containing a machine-readable error type. Error responses will typically have a 400 status code or similar (401, 429, 405, etc.). For example: { "error": "You've already made this purchase.", "errorType": "PurchasableEntityAlreadyPurchased" }
Endpoints:
POST /upload_csv
Asynchronously upload a CSV of people in order to match it to the voter file. Response is a stub audience.
Request parameters:
- customerId: Arbitrary string representing a unique customer ID. This will be associated with the created audience and will need to be passed whenever interacting with that audience.
- url: URL of the CSV.
Example request:
{
"customerId": "123",
"url": "https://storage.googleapis.com/your-public-bucket/your_csv.csv"
}Example response:
{
"id": "1",
"status": "PENDING",
"uploadedAudienceCsv": {
"id": "12",
"status": "READY_FOR_PARSING"
},
}POST /import_bq_table
Asynchronously import a BigQuery table in order to match it to the voter file. Response is a stub audience. (The "uploaded audience CSV" attached to the audience is a bit of a misnomer: it's just Matchbook's internal representation of your imported BigQuery table.)
Request parameters:
- customerId: Arbitrary string representing a unique customer ID. This will be associated with the created audience and will need to be passed whenever interacting with that audience.
- tableName: The name of the table in the import dataset that you specified when you configured your BigQuery integration.
Example request:
{
"customerId": "123",
"tableName": "my_bq_table"
}Example response:
{
"id": "1",
"status": "PENDING",
"uploadedAudienceCsv": {
"id": "6",
"status": "READY_FOR_PARSING"
}
}POST /match_uploaded_csv
Asynchronously fill in the audience by matching an uploaded CSV to our voter file using our schema and specify which fields are required or optional for our matching algorithm. Response is the newly created audience.
Note that your audience's uploadedAudienceCsv.status must be READY_FOR_COLUMN_SELECTION before calling this endpoint.
Request parameters:
- audienceID: The audience ID returned from /upload_csv
- customerId: Arbitrary string representing a unique customer ID. Must be the same as the customer ID associated with the audience created in /upload_csv.
- audienceName: Name for audience. Must be unique by customerId.
- columnSelections: A JSON dictionary in which each entry is of the form [CANONICAL_FIELD]: { column_name: [COLUMN_NAME], is_required: [true or false] }. In each case, CANONICAL_FIELD should be one of voterbase_id, first_name, last_name, full_name, city, state, zip, phone, dob (date of birth), address, or row_id. COLUMN_NAME should be the corresponding column name as it appears in your source CSV.
- addressTypes (optional): A list of address types to apply to addresses ingested from the CSV. Valid options are RESIDENTIAL, MAILING, and BUSINESS. Each ingested address is associated with every selected type. Only used when address is included in columnSelections. Defaults to ["RESIDENTIAL"].
More details about column selections:
- The only constraint on required matching fields is that you must specify at least one field.
- If you include full_name as a required or optional field, you can't also include first_name or last_name.
- If you include address, you must make it an optional field. (It can't be a required field because matching on address is too unreliable to be used as a strict requirement. It can be helpful as an optional matching field, though.)
- If your source CSV has an ID column that uniquely identifies each row, you can specify it for tracking purposes using the row_id canonical field. If you don't specify an ID column, we'll identify the records in your CSV using row numbers. You may want to use these IDs to link your matched audience back to your original CSV data. Note that if you include row_id, you must make it a required field. Also note that you can reuse the same column for both row_id and one of the matching fields.
Example request:
{
"audienceId": "1",
"customerId": "123",
"audienceName": "Some unique audience name",
"columnSelections": {
"address": {
"column_name": "address",
"is_required": false
},
"first_name": {
"column_name": "first_name",
"is_required": true
},
"last_name": {
"column_name": "last_name",
"is_required": true
},
"state": {
"column_name": "state",
"is_required": true
},
"zip": {
"column_name": "zip",
"is_required": false
}
}
}Example response:
{
"id": "1",
"createdAt": "2025-01-15T20:13:05.026281+00:00",
"customerId": "123",
"name": "Some unique audience name",
"status": "PENDING",
"metadata": null,
"isArchived": false,
"canCreateLookalikeFrom": false,
"errorInfoUserFacing": null,
"numLookalikesRequested": null,
"lookalikeIncludeOriginalAudience": null,
"isFilteredFromLookalike": false,
"audienceLookalikeCreatedFrom": null,
"exports": [],
"filters": null,
"uploadedAudienceCsv": {
"id": "1",
"createdAt": "2025-01-15T20:13:05.013135+00:00",
"status": "ACTIVE"
},
"uploadedAudienceCsvMatchMetadata": null
}GET /audience
Retrieve an existing audience.
Request parameters
- audienceId: The audience ID.
- customerId (optional): The customer ID associated with the audience. If the audience has a customer ID (i.e. was created via the API), this will be required to retrieve it. If the audience does not have a customer ID (i.e. was created via the app), this isn't required to retrieve it.
Example request:
{
id: 1,
customerId: "123"
}Example response:
{
"id": "1",
"createdAt": "2025-01-15T20:13:05.026281+00:00",
"customerId": "123",
"name": "Some unique audience name",
"status": "ACTIVE",
"metadata": {
"size": 58015,
"gender__F": 28613,
"gender__M": 28394,
"num_phones": 23219,
"party__DEM": 19474,
"party__IND": 24750,
"party__REP": 13791,
"race__AAPI": 6404,
"race__NONE": 4749,
"race__BLACK": 5995,
"race__WHITE": 28536,
"gender__NONE": 1008,
"race__LATINO": 11811,
"age_group__55+": 12733,
"age_group__18-34": 29053,
"age_group__35-54": 15211,
"age_group_alt__65+": 7171,
"age_group_alt__18-24": 11803,
"race__NATIVE_AMERICAN": 520
},
"isArchived": false,
"canCreateLookalikeFrom": true,
"errorInfoUserFacing": null,
"numLookalikesRequested": null,
"lookalikeIncludeOriginalAudience": null,
"isFilteredFromLookalike": false,
"audienceLookalikeCreatedFrom": null,
"exports": [
{
"id": "2948",
"createdAt": "2025-06-25T21:20:49.032192+00:00",
"downloadUrl": "...",
"externalBqTableName": "...",
"purchaseCostBreakdown": {
"phone": {
"price_per_record": 6,
"count_already_paid": 0,
"count_total_available": 205,
"count_requiring_payment": 205,
"count_uploaded_and_not_purchased": 0,
}
},
"size": 250,
"status": "ACTIVE",
"totalCostInCents": 1230,
}
],
"filters": null,
"uploadedAudienceCsv": {
"id": "1",
"createdAt": "2025-01-15T20:13:05.013135+00:00",
"status": "ACTIVE"
},
"uploadedAudienceCsvMatchMetadata": {
"numMatches": 58333,
"numUniqueMatches": 58015,
"columnMatchResults": [
{
"column": "zip",
"percentOfMatches": 0.9867999245709975,
"percentMissing": 0.0
},
{
"column": "state",
"percentOfMatches": 1.0,
"percentMissing": 0.0
},
{
"column": "address",
"percentOfMatches": 0.9870913548077418,
"percentMissing": 0.0
},
{
"column": "last_name",
"percentOfMatches": 1.0,
"percentMissing": 0.0
},
{
"column": "first_name",
"percentOfMatches": 1.0,
"percentMissing": 0.0
}
]
}
}POST /create_audience_export
Synchronously create an audience export. Response is the export record, with information about the associated audience record nested under the audience key.
If the export has a cost associated with it, the returned export record will have a status of REQUIRES_PAYMENT and it will include a totalCostInCents field. To access the export, you'll first need to purchase it using /purchase_audience_export.
If the export has no cost (either because you're not exporting any new paid information or because your organization is exempt from export fees), its status will be ACTIVE and you'll be able to access it immediately using the signed URL in the downloadUrl field (if it's a CSV) or in the location indicated by the externalBqTableName field.
You can always retrieve a full list of exports for a given audience using /audience.
Request parameters
- audienceId: The audience ID.
- exportType: The type of export you'd like to create. Valid options are "CSV" and "BQ". If you select "BQ", you'll first need to configure the Matchbook<>BigQuery integration in Matchbook.
- optionalFieldTypes (required): A list of paid contact info field types to be included in the export in addition to the standard fields that are provided free of charge. The current options are phone, address, mailing_address, best_address, vote_history, issue_scores, media_scores, support_scores, turnout_scores, and lookalike_scores. The response shows the cost (in cents) of the proposed export and includes a breakdown of how that cost was computed.
- customerId (optional): The customer ID associated with the audience. If the audience has a customer ID (i.e. was created via the API), this is required. If the audience does not have a customer ID (i.e. was created via the app), this isn't required.
- customBqTableName (optional): If you set exportType to "BQ" and provide this field, we'll export the audience to this table in your BigQuery dataset instead of generating a table name on your behalf.
Example request:
{
"audienceId": 1,
"exportType": "CSV",
"customerId": "123",
"optionalFieldTypes": [
"phone",
]
}Example response:
{
"id": "1",
"type": "CSV",
"createdAt": "2025-08-25T21:20:49.032192+00:00",
"errorInfoUserFacing": null,
"purchaseCostBreakdown": {
"phone": {
"price_per_record": 6,
"count_total_available": 23219,
"count_already_paid": 0,
"count_requiring_payment": 23219
},
},
"size": 58015,
"status": "REQUIRES_PAYMENT",
"totalCostInCents": 139314,
"downloadUrl": null,
"audience": {
"id": "1",
"customerId": "123",
"metadata": {
"size": 58015,
"gender__F": 28613,
"gender__M": 28394,
"num_phones": 23219,
"party__DEM": 19474,
"party__IND": 24750,
"party__REP": 13791,
"race__AAPI": 6404,
"race__NONE": 4749,
"race__BLACK": 5995,
"race__WHITE": 28536,
"gender__NONE": 1008,
"race__LATINO": 11811,
"age_group__55+": 12733,
"age_group__18-34": 29053,
"age_group__35-54": 15211,
"age_group_alt__65+": 7171,
"age_group_alt__18-24": 11803,
"race__NATIVE_AMERICAN": 520
}
}
}POST /purchase_audience_export
After creating an audience export via /create_audience_export and receiving a response with status REQUIRES_PAYMENT, synchronously purchase the export. Response is the export record (including downloadUrl or externalBqTableName) along with a few fields of information about the associated audience record nested under the audience key. Your organization will be invoiced for the purchase.
Request parameters:
- exportId: The export ID from /create_audience_export.
- customerId (optional): The customer ID associated with the audience/export. If the audience has a customer ID (i.e. was created via the API), this will be required. If the audience does not have a customer ID (i.e. was created via the app), this isn't required.
Example request:
{
"exportId": "1",
"customerId": "123"
}Example response:
{
"id": "1",
"createdAt": "2025-01-22T18:43:38.342670+00:00",
"errorInfoUserFacing": null,
"purchaseCostBreakdown": {
"phone": {
"price_per_record": 6,
"count_total_available": 23219,
"count_already_paid": 0,
"count_requiring_payment": 23219
}
},
"status": "ACTIVE",
"totalCostInCents": 139314,
"downloadUrl": "https://storage.googleapis.com/matchbook-exports/...",
"audience": {
"id": "1",
"customerId": "123",
"metadata": {
"size": 58015,
"gender__F": 28613,
"gender__M": 28394,
"num_phones": 23219,
"party__DEM": 19474,
"party__IND": 24750,
"party__REP": 13791,
"race__AAPI": 6404,
"race__NONE": 4749,
"race__BLACK": 5995,
"race__WHITE": 28536,
"gender__NONE": 1008,
"race__LATINO": 11811,
"age_group__55+": 12733,
"age_group__18-34": 29053,
"age_group__35-54": 15211,
"age_group_alt__65+": 7171,
"age_group_alt__18-24": 11803,
"race__NATIVE_AMERICAN": 520
}
}
}POST /create_lookalike_audience
Asynchronously create a lookalike audience based on an existing matched audience. To be eligible for lookalike expansion, an audience must be based on matching at least 1,000 people to the voter file from an uploaded CSV. Response is the audience record.
Request parameters:
- baseAudienceId: ID of a matched audience to use as the base for the lookalike.
- customerId: Customer ID to use for the new audience. If the base audience does not have a customer ID (i.e. it was created via the app), that is OK — the base audience only needs to match the passed in customer ID if it has one.
- size: Desired size of your lookalike audience as an integer.
- audienceName: Name for the new lookalike audience. Must be unique by customerId
- includeOriginalAudience (optional): Boolean, whether to include the original audience's members in the lookalike.
- filters (optional): A JSON dictionary containing filters to be applied, in which entries are of the form specified below:
"filters": {
// Integer
"ageEnd": 70,
// Boolean
"ageIncludeUnknown": true,
// Integer
"ageStart": 24,
// String: CELL is only valid option currently
"contactInfo": "CELL",
// List of strings, all possible options listed below
"genders": [
"NONE",
"M",
"F"
],
// Boolean
"hasDonorHistory": true,
// List of strings, all possible options listed below
"races": [
"BLACK",
"LATINO",
"NATIVE_AMERICAN",
"WHITE",
"NONE",
"AAPI"
],
// Date (string) formatted like 'YYYY-MM-DD'
"registrationDateEnd": "2025-12-31",
// Date (string) formatted like 'YYYY-MM-DD'
"registrationDateStart": "1900-01-01",
// See "Geographic filters" pages (links below) for details
"congressionalDistricts": null,
"stateLegLowerDistricts": null,
"stateLegUpperDistricts": null,
"counties": null,
// List of strings, all two-digit state codes including DC
"states": ["NY"],
// Five digit zip code as string. If present, `distanceFromZip` must also be present.
"zip": "11215"
// Integer, in miles (can be 0). If present, `zip` must also be present.
"distanceFromZip": 3,
}See Counties, Congressional districts, Upper state legislative districts, and Lower state legislative districts for details on the possible values for those fields.
Note that geographic filters are applied with a conjunction, meaning that only voters who match all of these geographic filters will be included in your audience.
Example request:
{
"audienceName": "Lookalike of some unique audience name",
"baseAudienceId": 1,
"size": 2000,
"customerId": "123",
"filters": {
"states": [
"CO",
"CA"
]
}
}Example response:
{
"id": "6",
"createdAt": "2025-01-22T17:17:17.715679+00:00",
"customerId": "123",
"name": "Lookalike of some unique audience name",
"status": "PENDING",
"metadata": null,
"isArchived": false,
"canCreateLookalikeFrom": false,
"errorInfoUserFacing": null,
"numLookalikesRequested": 2000,
"lookalikeIncludeOriginalAudience": null,
"isFilteredFromLookalike": false,
"audienceLookalikeCreatedFrom": {
"id": "1"
},
"exports": [],
"filters": {
"id": "10",
"createdAt": "2025-01-22T18:22:57.710604+00:00",
"states": [
"CO",
"CA"
],
"counties": null,
"congressionalDistricts": null,
"stateLegLowerDistricts": null,
"stateLegUpperDistricts": null,
"zip": null,
"distanceFromZip": null,
"ageStart": null,
"ageEnd": null,
"ageIncludeUnknown": null,
"genders": null,
"races": null,
"registrationDateStart": null,
"registrationDateEnd": null,
"hasDonorHistory": null,
"contactInfo": null,
"sortField": null,
"sortAsc": null,
"limit": null
},
"uploadedAudienceCsv": null,
"uploadedAudienceCsvMatchMetadata": null
}GET /audiences
Retrieve a list of audiences for your organization. To get an audience's details, follow this up with an
Request parameters
- customerId (optional): Only return audiences with this customerId. If no customer ID is passed, this will return all audiences.
Example request:
{
customerId: "123"
}Example response:
[
{
"id": "1",
"createdAt": "2025-01-15T20:13:05.026281+00:00",
"customerId": "123",
"name": "Some unique audience name",
"status": "ACTIVE",
"isArchived": false,
"matchSource": "CSV",
"errorInfoUserFacing": null
},
{
"id": "6",
"createdAt": "2025-01-22T17:17:17.715679+00:00",
"customerId": "123",
"name": "Lookalike of some unique audience name",
"status": "ACTIVE",
"isArchived": false,
"matchSource": null,
"errorInfoUserFacing": null
}
]
