Finn Voice API
Programmatic access — auth, endpoints, webhooks.
Finn Voice API
The Finn API is the same surface our dashboard uses. Build voice agents, launch campaigns, ingest analytics — all programmatically.
This guide gets you to your first live API call in ~5 minutes.
Quickstart (3 steps)
1. Grab an API key
Dashboard → Settings → Integrations → API Keys → Generate key.
You'll get two key tiers:
fnn_test_*— sandbox. No carrier billing, no real dials. Use for development.fnn_live_*— production. Real calls, real money.
Keys are org-scoped and inherit your plan's rate limits + quotas.
2. Set environment variables
export FINN_API_KEY=fnn_test_xxxxxxxxxxxxxxxxxxxx
export FINN_BASE_URL=https://stage-api.hirefinn.ai/v1
For production, swap to:
export FINN_BASE_URL=https://api.hirefinn.ai/v1
3. Make your first call
List your finns:
curl $FINN_BASE_URL/finns \
-H "Authorization: Bearer $FINN_API_KEY"
Expected response:
{
"data": [
{
"id": "fn_abc123",
"name": "MBBS Callflow",
"voice_id": "voice_warm_indian_f",
"language": "en-IN",
"created_at": "2026-04-12T10:30:00Z"
}
],
"pagination": {
"page": 1,
"per_page": 50,
"total": 1,
"has_more": false
}
}
Done. You're talking to the API.
Base URL
| Environment | URL |
|---|---|
| Production | https://api.hirefinn.ai/v1 |
| Stage | https://stage-api.hirefinn.ai/v1 |
All endpoints are versioned under /v1. Breaking changes ship under a new path prefix (/v2, /v3); additive fields roll into the current version.
Authentication
Every request needs an API key in the Authorization header:
Authorization: Bearer fnn_live_xxxxxxxxxxxxxxxxxxxx
Key management
- Generate in Dashboard → Settings → Integrations → API Keys.
- Rotate — same screen. Old key revoked immediately on confirmation.
- Revoke — instant. All in-flight requests using the revoked key fail with
401. - Scope — keys are org-scoped. Use separate keys per environment, per service.
Best practices
- Store keys in environment variables or a secret manager. Never commit to source.
- Use
fnn_test_*for CI and local dev. Production credentials should never see a developer laptop. - Rotate quarterly even without an incident.
- Audit which key created which resource via the
created_by_key_idfield on most resources.
Code examples
Hit the same endpoint in 3 languages.
cURL
curl https://api.hirefinn.ai/v1/finns \
-H "Authorization: Bearer $FINN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Aria — Dental Reminders",
"voice_id": "voice_warm_indian_f",
"language": "en-IN",
"system_prompt": "You are Aria, a friendly...",
"welcome_message": "Hi, this is Aria from Smile Dental..."
}'
Node / TypeScript
import { Finn } from "@finn-voice/sdk";
const finn = new Finn({ apiKey: process.env.FINN_API_KEY });
const agent = await finn.finns.create({
name: "Aria — Dental Reminders",
voice_id: "voice_warm_indian_f",
language: "en-IN",
system_prompt: "You are Aria, a friendly...",
welcome_message: "Hi, this is Aria from Smile Dental...",
});
console.log(agent.id);
Python
from finn_voice import Finn
finn = Finn(api_key=os.environ["FINN_API_KEY"])
agent = finn.finns.create(
name="Aria — Dental Reminders",
voice_id="voice_warm_indian_f",
language="en-IN",
system_prompt="You are Aria, a friendly...",
welcome_message="Hi, this is Aria from Smile Dental...",
)
print(agent.id)
Response format
Every successful response wraps payload in a consistent envelope.
Single resource
{
"data": {
"id": "fn_abc123",
"name": "MBBS Callflow",
"created_at": "2026-04-12T10:30:00Z"
}
}
List
{
"data": [ /* array of resources */ ],
"pagination": {
"page": 1,
"per_page": 50,
"total": 312,
"has_more": true
}
}
Timestamps
All timestamps are ISO-8601 UTC (2026-05-22T14:30:00Z).
IDs
Resource IDs are prefixed by type for grep-ability:
| Prefix | Resource |
|---|---|
fn_ | Finn (voice agent) |
aud_ | Audience |
dep_ | Deployment |
cal_ | Call |
ph_ | Phone number |
whk_ | Webhook subscription |
Pagination
List endpoints accept:
?page=2&per_page=100
pagedefaults to 1.per_pagedefaults to 50, max 200.- Response includes
pagination.has_more— whentrue, incrementpageand re-fetch.
Cursor pagination for high-cardinality endpoints (calls, transcripts):
?cursor=eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0yMlQxMjowMDowMFoifQ&per_page=100
Cursor is opaque — pass it back as-is to fetch the next page.
Filtering & sorting
Most list endpoints support:
?filter[status]=active&filter[call_type]=outbound&sort=-created_at
filter[field]=value— exact match. Some fields accept arrays:filter[status]=active,paused.sort=field— ascending. Prefix with-for descending. Comma-separate multiple:sort=-created_at,name.
Idempotency
Send a unique Idempotency-Key header on POST requests that create resources. Finn deduplicates retried requests within 24 hours.
curl https://api.hirefinn.ai/v1/deployments \
-H "Authorization: Bearer $FINN_API_KEY" \
-H "Idempotency-Key: campaign-may-cohort-3-attempt-1" \
-d '{ "finn_id": "fn_abc123", ... }'
If you retry with the same key, you get the cached response from the first call — no duplicate resource created.
Errors
JSON envelope:
{
"error": {
"type": "validation_error",
"code": "missing_field",
"message": "phone_number_id is required",
"field": "phone_number_id",
"request_id": "req_xy12abc"
}
}
Include request_id in any support ticket — it's how we trace your call through our logs.
Status codes
| Status | Meaning | Retry? |
|---|---|---|
400 | Validation error — payload shape wrong | No |
401 | API key missing / invalid | No |
403 | Scoped to different org / plan tier | No |
404 | Resource not found | No |
409 | Conflict (duplicate name, phone already bound) | No |
422 | Business-rule reject (compliance, quota) | No |
429 | Rate limited | Yes — respect Retry-After |
5xx | Server side | Yes — exponential backoff |
Common error codes
| Code | When |
|---|---|
missing_field | Required field absent from payload |
invalid_field | Field present but value invalid |
not_authenticated | Bearer token missing |
invalid_token | Token revoked or malformed |
forbidden_scope | Token doesn't have access to this resource |
quota_exceeded | Plan limit hit |
rate_limited | Too many requests per second |
compliance_block | Request rejected by compliance rules |
wallet_insufficient | Not enough credits to launch deployment |
Rate limits
| Plan | RPS | Daily |
|---|---|---|
| Starter | 5 | 5,000 |
| Pro | 25 | 50,000 |
| Growth | 100 | 250,000 |
| Enterprise | Negotiated | Negotiated |
Hitting the limit returns 429 with headers:
Retry-After: 12
X-RateLimit-Limit: 25
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1716391823
Respect Retry-After (seconds) or back off exponentially on 5xx.
Resources at a glance
| Resource | Endpoint | Description |
|---|---|---|
| Finns | /v1/finns | Voice agents |
| Audiences | /v1/audiences | Contact lists |
| Phone numbers | /v1/phone-numbers | Owned + rented DIDs |
| Deployments | /v1/deployments | Campaigns + inbound bindings |
| Calls | /v1/calls | Individual call records |
| Voices | /v1/voices | Available TTS voices |
| Webhooks | /v1/webhooks | Event subscriptions |
| Wallet | /v1/wallet/{orgId} | Balance + transactions |
Webhooks
Subscribe to events. POST'd to your URL with HMAC-SHA256 signature in X-Finn-Signature.
Register
curl https://api.hirefinn.ai/v1/webhooks \
-H "Authorization: Bearer $FINN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/finn-webhook",
"events": ["call.completed", "deployment.completed"],
"secret": "whsec_yourSecretHere"
}'
Event payload
{
"id": "evt_2H4abc",
"type": "call.completed",
"created_at": "2026-05-22T14:30:00Z",
"data": {
"call_id": "cal_xyz789",
"deployment_id": "dep_xyz789",
"duration_seconds": 142,
"outcome": "qualified",
"recording_url": "https://...",
"transcript_url": "https://..."
}
}
Verifying signature (Node)
import crypto from "crypto";
function verify(req: Request, secret: string): boolean {
const sig = req.headers.get("X-Finn-Signature") || "";
const body = req.body; // raw bytes!
const expected = crypto
.createHmac("sha256", secret)
.update(body)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
Event catalog
| Event | Fires when |
|---|---|
call.started | Carrier picked the call up |
call.completed | Call ended (any reason) |
call.transferred | Warm transfer to human |
deployment.started | Campaign began dialing |
deployment.paused | Manually paused |
deployment.completed | Audience exhausted |
wallet.low_balance | Balance dropped below configured threshold |
wallet.topup_complete | Successful recharge settled |
compliance.action_required | Manual review needed |
Reliability
- Retries on
5xx/ timeout: 5 attempts over ~10 minutes with exponential backoff. - Order is best-effort, not guaranteed — use timestamps + idempotent handlers.
- Use the dashboard's webhook log to replay failed deliveries.
SDKs
Official:
- Node / TypeScript —
npm install @finn-voice/sdk— fully typed, retry built-in - Python —
pip install finn-voice— sync + async clients
Community (unsupported):
- Go, Ruby, PHP — links in the repo README
All SDKs wrap the REST surface 1:1, handle retries, and ship types for every resource.
OpenAPI spec
Machine-readable spec lives at:
https://api.hirefinn.ai/openapi.json
Use it to generate clients in any language, validate request bodies in CI, or import into Postman:
Postman → File → Import → Link → https://api.hirefinn.ai/openapi.json
Testing locally
For local webhook development:
# Forward Finn webhooks to your dev machine
ngrok http 3000
# Or use the Finn CLI's built-in tunnel
finn webhooks listen --forward-to http://localhost:3000/webhook
The CLI also stubs API responses so you can write integration tests without a live key.
Versioning
- Path versioning —
/v1,/v2. Breaking changes get a new prefix. - Sunset window — at least 12 months between deprecation announcement and removal.
- Header opt-in for beta features:
X-Finn-Beta: enable=workflow-canvas-v2
Subscribe to the Changelog for the deprecation calendar.
Limits & quotas
| Limit | Value |
|---|---|
| Max concurrent deployments | Per plan (5 Starter → unlimited Enterprise) |
| Max audience size | 5M rows |
| Max system prompt length | 32K characters |
| Max webhook URL length | 2048 characters |
| Webhook payload max size | 1 MB |
| Recording retention | 90 days default, configurable per plan |
| API key lifetime | Indefinite (rotate manually) |
Next steps
- Build an agent — try the Creating a Finn walkthrough end-to-end via API.
- Launch a campaign — use the Deployments doc as a recipe.
- Wire your CRM — see Integrations for webhook patterns.
- Tune for cost — read Wallet & AI Credits to understand the pulse-billing model.
Stuck? support@hirefinn.ai — include the request_id from the error response if you have one.
Was this page helpful?
Still stuck or have feedback?
Email support@hirefinn.ai or use the chat bubble in the bottom-right corner — it's a Finn that knows the Academy cold.