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
| Change | Considered breaking |
|---|---|
| Adding a new field to a response | No |
| Adding a new optional request parameter | No |
| Adding a new value to an existing string field | No |
| Renaming or removing a response field | Yes |
| Changing a field's type | Yes |
| Making an optional parameter required | Yes |
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.
Related pages
| Page | Covers |
|---|---|
| Errors | Status codes, error codes and what to retry |
| Pagination | limit, offset and the maximum page size for each endpoint |
| Rate limits | Per-key limits and RateLimit-* headers |
| Idempotency | Retrying writes safely without an idempotency key |
| Webhooks | Receiving call.completed events |