---
title: Audiences
slug: audiences
icon: {"faIcon":"fa-solid fa-user-group"}
createdAt: 2024-09-30T19:21:52.218Z
---

# Cookbook:

## Upload a CSV and match it to the voter file:

1. **[Upload a CSV](docId\:iOCsDy_AFvBDwt_8lrxnG)*.*
2. Using the audience ID returned in step 1, [retrieving that audience](docId\:iOCsDy_AFvBDwt_8lrxnG) every 5 seconds or so until the `uploadedAudienceCsv.status` value in the audience response is `READY_FOR_COLUMN_SELECTION`.
3. [Match your uploaded CSV](docId\:iOCsDy_AFvBDwt_8lrxnG) to the voter file.
4. Using that same audience ID (which will be returned in step 3 as well), [retrieve that audience](docId\:iOCsDy_AFvBDwt_8lrxnG) every 5 seconds or so until the audience's status is `ACTIVE`.

## Import a list from BigQuery and match it to the voter file:

1. If you haven't already enabled our BigQuery integration, open the Matchbook app and configure it in your organization's settings.
2. [Import a BigQuery table](docId\:iOCsDy_AFvBDwt_8lrxnG) from your dataset.
3. Follow steps 2-4 from the instructions in the previous section ("Upload a CSV"),&#x20;

## Create a lookalike of a matched CSV audience:

1. Follow steps 1-4 in [upload a CSV and match it to the voter file](docId\:iOCsDy_AFvBDwt_8lrxnG) above to match your CSV to the voter file.
2. Using the audience ID of your matched audience as the `baseAudienceId` parameter, [create a lookalike audience](docId\:iOCsDy_AFvBDwt_8lrxnG) from your matched audience.
3. Using the audience ID returned by step 2, [retrieve that audience](docId\:iOCsDy_AFvBDwt_8lrxnG) 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](docId\:iOCsDy_AFvBDwt_8lrxnG) or [creating a lookalike of a matched CSV audience](docId\:iOCsDy_AFvBDwt_8lrxnG):

1. [Create an export](docId\:iOCsDy_AFvBDwt_8lrxnG) using the audience ID for the audience you'd like to export.
2. [Poll for updates](docId\:iOCsDy_AFvBDwt_8lrxnG) to the audience until the export's status is no longer `IN_PROGRESS`.
3. If the export's status is now `REQUIRES_PAYMENT`, [purchase it](docId\:iOCsDy_AFvBDwt_8lrxnG) by passing in the export ID returned in step 1.
4. 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](docId\:iOCsDy_AFvBDwt_8lrxnG) 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:&#x20;**&#x55;RL of the CSV.

### Example request:

```json
{
    "customerId": "123",
    "url": "https://storage.googleapis.com/your-public-bucket/your_csv.csv"
}
```

### Example response:

```json
{
    "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:

```json
{
    "customerId": "123",
    "tableName": "my_bq_table"
}
```

### Example response:

```json
{
    "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:&#x20;**&#x41;rbitrary 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:&#x20;**&#x41; 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&#x20;**(optional)**:&#x20;**&#x41; 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:

```json
{
	"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:

```json
{
    "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:&#x20;**&#x54;he audience ID.
- **customerId&#x20;**(optional)**:&#x20;**&#x54;he 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:

```json
{
  id: 1,
  customerId: "123"
}
```

### Example response:

```json
{
    "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](docId\:iOCsDy_AFvBDwt_8lrxnG).

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](docId\:iOCsDy_AFvBDwt_8lrxnG).

### Request parameters

- **audienceId:** The audience ID.
- **exportType:&#x20;**&#x54;he 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](https://app.indigo.engineering/manage_organization/bq) in Matchbook.
- **optionalFieldTypes&#x20;**(required)**:&#x20;**&#x41; 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&#x20;**(optional)**:&#x20;**&#x54;he 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:

```json
{
	"audienceId": 1,
	"exportType": "CSV",
	"customerId": "123",
	"optionalFieldTypes": [
		"phone",
	]
}
```

### Example response:

```json
{
    "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](docId\:iOCsDy_AFvBDwt_8lrxnG) 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](docId\:iOCsDy_AFvBDwt_8lrxnG).
- **customerId&#x20;**(optional)**:&#x20;**&#x54;he 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:

```json
{
    "exportId": "1",
    "customerId": "123"
}
```

### Example response:

```json
{
    "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:&#x20;**&#x43;ustomer 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&#x20;**(optional): Boolean, whether to include the original audience's members in the lookalike.
- **filters&#x20;**(optional)**:** A JSON dictionary containing filters to be applied, in which entries are of the form specified below:

```json
    "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](docId:4AV_7-40kr95PixGJ7T9H), [Congressional districts](docId\:iXcvRa3bErSrG-pRmQ4iR), [Upper state legislative districts](docId:61wktlKm0HWXODAJQ3Cql), and [Lower state legislative districts](docId\:Gjgzg-MjrCFZXcwN6-Qq1) 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:

```json
{
    "audienceName": "Lookalike of some unique audience name",
    "baseAudienceId": 1,
    "size": 2000,
    "customerId": "123",
    "filters": {
        "states": [
            "CO",
            "CA"
        ]
    }
}
```

### Example response:

```json
{
    "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&#x20;

### Request parameters

- **customerId&#x20;**(optional)**:&#x20;**&#x4F;nly return audiences with this `customerId`. If no customer ID is passed, this will return all audiences.

### Example request:

```json
{
  customerId: "123"
}
```

### Example response:

```json
[
  {
    "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
  }
]

```

