Skip to main content

Finn Voice API

Programmatic access — auth, endpoints, webhooks.

8 min read

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

EnvironmentURL
Productionhttps://api.hirefinn.ai/v1
Stagehttps://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_id field 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:

PrefixResource
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
  • page defaults to 1.
  • per_page defaults to 50, max 200.
  • Response includes pagination.has_more — when true, increment page and 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

StatusMeaningRetry?
400Validation error — payload shape wrongNo
401API key missing / invalidNo
403Scoped to different org / plan tierNo
404Resource not foundNo
409Conflict (duplicate name, phone already bound)No
422Business-rule reject (compliance, quota)No
429Rate limitedYes — respect Retry-After
5xxServer sideYes — exponential backoff

Common error codes

CodeWhen
missing_fieldRequired field absent from payload
invalid_fieldField present but value invalid
not_authenticatedBearer token missing
invalid_tokenToken revoked or malformed
forbidden_scopeToken doesn't have access to this resource
quota_exceededPlan limit hit
rate_limitedToo many requests per second
compliance_blockRequest rejected by compliance rules
wallet_insufficientNot enough credits to launch deployment

Rate limits

PlanRPSDaily
Starter55,000
Pro2550,000
Growth100250,000
EnterpriseNegotiatedNegotiated

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

ResourceEndpointDescription
Finns/v1/finnsVoice agents
Audiences/v1/audiencesContact lists
Phone numbers/v1/phone-numbersOwned + rented DIDs
Deployments/v1/deploymentsCampaigns + inbound bindings
Calls/v1/callsIndividual call records
Voices/v1/voicesAvailable TTS voices
Webhooks/v1/webhooksEvent 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

EventFires when
call.startedCarrier picked the call up
call.completedCall ended (any reason)
call.transferredWarm transfer to human
deployment.startedCampaign began dialing
deployment.pausedManually paused
deployment.completedAudience exhausted
wallet.low_balanceBalance dropped below configured threshold
wallet.topup_completeSuccessful recharge settled
compliance.action_requiredManual 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 / TypeScriptnpm install @finn-voice/sdk — fully typed, retry built-in
  • Pythonpip 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

LimitValue
Max concurrent deploymentsPer plan (5 Starter → unlimited Enterprise)
Max audience size5M rows
Max system prompt length32K characters
Max webhook URL length2048 characters
Webhook payload max size1 MB
Recording retention90 days default, configurable per plan
API key lifetimeIndefinite (rotate manually)

Next steps

  1. Build an agent — try the Creating a Finn walkthrough end-to-end via API.
  2. Launch a campaign — use the Deployments doc as a recipe.
  3. Wire your CRM — see Integrations for webhook patterns.
  4. 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.