Beta. Every endpoint on this page is live and documented as shipped. Shapes can still change before general availability.
Overview
An audience is a named list of contacts that an outbound deployment dials. The Audiences API creates audiences, adds and removes contacts, lists them and archives audiences you no longer need. To dial an audience, pass its id as audience_id to POST /deployments; see api-deployments.
Base URL: https://api.hirefinn.ai/api/v1. Auth: Authorization: Bearer finn_live_.... See authentication.
Every contact needs a country_code. Finn builds the number it dials from country_code plus phone_number, so a contact without a country code is rejected.
Endpoints
| Method | Path | Rate limit (per API key) |
|---|---|---|
GET | /audiences | 300 per minute, shared by all audience reads |
GET | /audiences/{audience_id} | Read bucket |
GET | /audiences/{audience_id}/contacts | Read bucket |
POST | /audiences | 60 per minute, shared by all audience writes |
POST | /audiences/{audience_id}/contacts | Write bucket |
DELETE | /audiences/{audience_id}/contacts/{phone} | Write bucket |
DELETE | /audiences/{audience_id} | Write bucket |
The audience object
{
"id": "c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48",
"name": "October renewals",
"contact_count": 2,
"source": "api",
"is_dynamic": false,
"created_at": "2026-10-01T08:30:12.000Z",
"updated_at": null
}
| Field | Type | Notes |
|---|---|---|
id | string | UUID. |
name | string | Display name. |
contact_count | integer | Number of contacts in the audience. |
source | string | api for audiences created through this API. Audiences created in the dashboard show their origin, for example manual_upload. |
is_dynamic | boolean | true for audiences kept in sync from an external source. Dynamic audiences cannot be deployed through the API. |
created_at | string | ISO 8601. |
updated_at | string or null | ISO 8601. null until the audience is first edited. |
Archived audiences are not returned by any endpoint.
The contact object
On input, a contact is an object with these keys. Every other key becomes a custom field: a column stored with the contact and passed with it when a deployment dials.
| Field | Type | Required | Rules |
|---|---|---|---|
country_code | string or number | yes | 1 to 4 digits, optionally prefixed with +, for example "+91" or 1. |
phone_number | string or number | yes | The number without its country code: 4 to 14 digits, with country code and number together at most 15 digits. Spaces, dashes, dots and parentheses are stripped. A number starting with + must begin with its country_code, which is then removed. |
name | string | no | Up to 255 characters, no line breaks. |
| any other key | string, number or boolean | no | Up to 50 custom fields per contact. Key: 1 to 64 characters, without commas, double quotes or line breaks, and not name, phone_number or country_code in any letter case. Value: up to 500 characters, no line breaks. null values are skipped. |
Contacts are stored as { "name", "phone_number", "country_code", ...custom fields } with phone_number as digits only and country_code with a leading +:
{ "name": "Asha Rao", "phone_number": "9876543210", "country_code": "+91", "plan": "gold" }
Two contacts are duplicates when country_code plus phone_number is the same number. Duplicates, within one request or against contacts already in the audience, are skipped and counted in duplicates_skipped.
Limits: at most 5,000 contacts per request and 50,000 contacts per audience.
List audiences
GET /audiences
Returns your organization's audiences, newest first.
| Query parameter | Type | Default | Notes |
|---|---|---|---|
limit | integer | 50 | 1 to 200. Out-of-range values are clamped. |
offset | integer | 0 | Number of items to skip. |
curl "https://api.hirefinn.ai/api/v1/audiences?limit=50" \
-H "Authorization: Bearer $FINN_API_KEY"
{
"success": true,
"data": [
{
"id": "c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48",
"name": "October renewals",
"contact_count": 2,
"source": "api",
"is_dynamic": false,
"created_at": "2026-10-01T08:30:12.000Z",
"updated_at": null
}
],
"limit": 50,
"offset": 0
}
There is no total count. Page with offset until a page returns fewer than limit items.
Retrieve an audience
GET /audiences/{audience_id}
curl https://api.hirefinn.ai/api/v1/audiences/c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48 \
-H "Authorization: Bearer $FINN_API_KEY"
Returns { "success": true, "data": { ... } } with one audience object.
Create an audience
POST /audiences
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | 1 to 255 characters after trimming. |
contacts | array | no | Up to 5,000 contact objects. Omit to create an empty audience and add contacts later. |
curl -X POST https://api.hirefinn.ai/api/v1/audiences \
-H "Authorization: Bearer $FINN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "October renewals",
"contacts": [
{ "name": "Asha Rao", "phone_number": "9876543210", "country_code": "+91", "plan": "gold" },
{ "name": "Dan Hill", "phone_number": "+14155550142", "country_code": "1", "plan": "silver" }
]
}'
Response 201 Created:
{
"success": true,
"data": {
"id": "c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48",
"name": "October renewals",
"contact_count": 2,
"source": "api",
"is_dynamic": false,
"created_at": "2026-10-01T08:30:12.000Z",
"updated_at": null
},
"contacts_added": 2,
"duplicates_skipped": 0
}
If any contact fails validation, nothing is created and the response is 400 invalid_request with a details array of up to 50 { "index", "message" } entries pointing at the failing contacts:
{
"error": "invalid_request",
"message": "1 contact(s) failed validation",
"details": [{ "index": 1, "message": "country_code is required" }]
}
There is no idempotency key. A retried create makes a second audience.
List contacts
GET /audiences/{audience_id}/contacts
| Query parameter | Type | Default | Notes |
|---|---|---|---|
limit | integer | 100 | 1 to 1,000. Out-of-range values are clamped. |
offset | integer | 0 | Number of contacts to skip. |
curl "https://api.hirefinn.ai/api/v1/audiences/c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48/contacts?limit=100" \
-H "Authorization: Bearer $FINN_API_KEY"
{
"success": true,
"data": [
{ "name": "Asha Rao", "phone_number": "9876543210", "country_code": "+91", "plan": "gold" },
{ "name": "Dan Hill", "phone_number": "4155550142", "country_code": "+1", "plan": "silver" }
],
"total": 2,
"limit": 100,
"offset": 0
}
Contacts are returned as stored, in file order, with every value as a string. To keep the contact file safe to open in a spreadsheet, Finn stores a name or custom field value that starts with =, @, + or - with a leading ', so "=SUM(A1)" comes back as "'=SUM(A1)". Values that look like numbers, such as "-12.5" or "+1 415 555 0142", are left alone, and so are the phone number and country code columns. Strip the leading ' if you need the original value. total is the number of contacts in the audience. Audiences uploaded in the dashboard keep their own column names, for example phone or mobile instead of phone_number.
Add contacts
POST /audiences/{audience_id}/contacts
| Field | Type | Required | Notes |
|---|---|---|---|
contacts | array | yes | 1 to 5,000 contact objects. |
curl -X POST https://api.hirefinn.ai/api/v1/audiences/c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48/contacts \
-H "Authorization: Bearer $FINN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contacts": [
{ "name": "Ravi Menon", "phone_number": "9123456780", "country_code": "+91", "plan": "gold" }
]
}'
{
"success": true,
"data": {
"id": "c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48",
"name": "October renewals",
"contact_count": 3,
"source": "api",
"is_dynamic": false,
"created_at": "2026-10-01T08:30:12.000Z",
"updated_at": "2026-10-02T10:04:55.000Z"
},
"contacts_added": 1,
"duplicates_skipped": 0
}
New custom field names are added as new columns. Validation works as on create: one invalid contact rejects the whole request.
Remove a contact
DELETE /audiences/{audience_id}/contacts/{phone}
phone is the full number including its country code, for example 919876543210. Digits are compared, so +91 98765-43210 matches the same contact; a + in the path should be URL-encoded as %2B or left out. Every contact with that number is removed.
curl -X DELETE https://api.hirefinn.ai/api/v1/audiences/c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48/contacts/919876543210 \
-H "Authorization: Bearer $FINN_API_KEY"
{
"success": true,
"data": {
"id": "c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48",
"name": "October renewals",
"contact_count": 2,
"source": "api",
"is_dynamic": false,
"created_at": "2026-10-01T08:30:12.000Z",
"updated_at": "2026-10-02T10:06:31.000Z"
},
"contacts_removed": 1
}
An audience cannot be emptied this way: removing its last contact returns 409 audience_would_be_empty. Archive the audience instead.
Adding and removing contacts is refused while a running deployment uses the audience (409 audience_in_use, with that deployment_id in the body). A deployment counts as running when its status is in_progress, active, running or live; pending and paused deployments don't block edits. Stop the deployment first.
Archive an audience
DELETE /audiences/{audience_id}
Archives the audience, as the dashboard does. It is not permanently deleted, but it disappears from every endpoint in this API and cannot be used for new deployments. Archiving does not stop a deployment that is already dialing it; stop that with api-deployments.
curl -X DELETE https://api.hirefinn.ai/api/v1/audiences/c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48 \
-H "Authorization: Bearer $FINN_API_KEY"
{
"success": true,
"data": { "id": "c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48", "archived": true }
}
Errors
| Status | error | Endpoint | When |
|---|---|---|---|
| 400 | invalid_request | all | audience_id is not a UUID; name missing or too long; contacts is not an array, is empty when required, or has more than 5,000 items; a contact fails validation (with details); phone is not a phone number. |
| 401 | api_key_required | all | No Bearer finn_live_... key. |
| 401 | invalid_api_key | all | Key unknown or revoked. |
| 404 | audience_not_found | all except list and create | No active audience with that ID in your organization. |
| 404 | contacts_file_missing | contact list, add, remove | The audience's contact file no longer exists. |
| 404 | contact_not_found | remove contact | No contact with that number. |
| 409 | audience_in_use | add, remove contact | A deployment with status in_progress, active, running or live uses the audience. Body includes deployment_id. |
| 409 | audience_not_editable | add, remove contact | The audience has no contact file to edit. |
| 409 | audience_limit_exceeded | add contacts | The audience would exceed 50,000 contacts. |
| 409 | audience_would_be_empty | remove contact | It is the last contact. |
| 429 | rate_limited | all | Over the bucket's per-minute limit. |
| 500 | internal_error | all | Unexpected server error. |
Error bodies have the shape { "error": "...", "message": "..." }; internal_error has no message. See api-errors.