Skip to main content

API reference

Conventions

Base URL, response envelope, IDs, timestamps, versioning.

Base URL

Every request goes to one base URL:

https://api.hirefinn.ai/api/v1

The version is part of the path, not a header. Use HTTPS for every request.

The API is for server-to-server use. Call it from your backend, never from a web page: a request that carries a browser Origin header Finn doesn't allow is rejected with 403 origin_not_allowed, and a key in browser code is visible to anyone who opens the page.

Authentication

Send your API key as a bearer token on every request:

curl https://api.hirefinn.ai/api/v1/finns \
  -H "Authorization: Bearer $FINN_API_KEY"

Each key belongs to one organization and can reach only that organization's resources. Keys have no scopes. See Authentication to create and rotate keys, and Errors for the responses a rejected key gets.

Every path under /api/v1 is authenticated before routing, including paths that don't exist. A request without a valid key gets 401, even when the path is wrong. The one exception is a body that isn't valid JSON: it's rejected with 400 before the key is checked, so it gets 400 even with no key. See Errors outside the normal format.

Requests

Send Content-Type: application/json on every request with a body. Request bodies can be up to 10 MB.

curl -X POST https://api.hirefinn.ai/api/v1/calls \
  -H "Authorization: Bearer $FINN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "finn_id": "8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1",
    "to_number": "+14155550123",
    "country_code": "1"
  }'

Field names use snake_case. Booleans must be real JSON booleans, not the strings "true" and "false". Most endpoints ignore query parameters they don't recognize, so a misspelled filter returns unfiltered results instead of an error. Check parameter names against the page for each resource.

Responses

A successful response is JSON with success: true and the result in data:

{
  "success": true,
  "data": {
    "call_uuid": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
    "finn_id": "8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1",
    "to_number": "+14155550123",
    "status": "initiated"
  }
}

List endpoints also return the limit and offset that were applied. See Pagination.

A failed request has no success field. It returns an error code and usually a message:

{
  "error": "finn_not_found",
  "message": "No Finn with that id in this organization"
}

Your code should check the error code, not the message. Errors lists every code.

The HTTP status code tells you what kind of result you got. Most creates return 201. POST /calls and POST /phone_numbers return 202, because the call or the number setup finishes after the response is sent.

Object IDs

Finns, calls, deployments, audiences, phone numbers and webhook endpoints have UUID IDs:

8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1

A call's ID field is call_uuid. Other objects use id, and a request that refers to another object uses a named field such as finn_id. If an ID isn't a valid UUID, the request fails with 400 invalid_request before Finn looks anything up. Uppercase and lowercase are both accepted.

There are two exceptions. Voice IDs are strings from the voice provider, not UUIDs (see Voices). An audience contact is identified by its phone number.

Don't sort by ID: UUIDs don't contain a time or an order. Sort by created_at.

Timestamps

Timestamps are ISO 8601 strings in UTC with millisecond precision:

{
  "created_at": "2026-09-18T11:12:31.000Z",
  "updated_at": "2026-09-18T11:14:08.000Z"
}

Timestamp field names end in _at. Durations are whole seconds in fields ending in _seconds.

Money and credits

Calls are billed in credits from your organization's wallet. Prices are JSON numbers in the major unit of the currency field, so 10.05 with "currency": "INR" means ₹10.05. They aren't integers in a minor unit like paise or cents. Fields such as price_per_credit and estimated_cost can have decimals. Round only when you display a value. See Wallet.

Null and missing fields

If a field has no value, the response usually includes it with null rather than leaving it out. Before you read a nested field, check whether its parent is null. For example, the billing block on a call is null if Finn couldn't look up the rate.

Versioning

ChangeConsidered breaking
Adding a new field to a responseNo
Adding a new optional request parameterNo
Adding a new value to an existing string fieldNo
Renaming or removing a response fieldYes
Changing a field's typeYes
Making an optional parameter requiredYes

Write clients that ignore fields they don't recognize. New fields and new status values can be added to v1 without a new version. Treat status, outcome and reason fields as strings that can take new values, not as a fixed list.

PageCovers
ErrorsStatus codes, error codes and what to retry
Paginationlimit, offset and the maximum page size for each endpoint
Rate limitsPer-key limits and RateLimit-* headers
IdempotencyRetrying writes safely without an idempotency key
WebhooksReceiving call.completed events