Beta. This endpoint is not final. Field names and response shapes can change before general availability.
Overview
The Calls API places outbound calls and reads records of calls that have already happened, inbound or outbound. A call is always tied to a deployment, which supplies the Finn, the voice, the phone number, and the prompt configuration. You do not pass an agent to this API. You pass a deployment ID and the variables that deployment expects.
If you want to launch many calls at once, use a campaign through outbound campaigns rather than looping this endpoint. Per-call creation is intended for event-driven calls: a form submission, a webhook from your CRM, a support ticket escalation.
Place a call
curl -X POST https://api.hirefinn.ai/v1/calls \
-H "Authorization: Bearer $FINN_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-48812-callback" \
-d '{
"deployment_id": "dep_7yQ2m4Kx",
"to": "+14155550142",
"variables": {
"customer_name": "Ada Lovelace",
"order_id": "48812"
},
"metadata": {
"crm_record": "lead_9931"
}
}'
| Field | Type | Required | Notes |
|---|---|---|---|
deployment_id | string | yes | Outbound deployment to run. See api-deployments. |
to | string | yes | Destination in E.164 format, including the + and country code. |
variables | object | no | Values substituted into the prompt. Keys must match the variables the deployment declares. See agents-variables. |
metadata | object | no | Opaque key/value pairs echoed back on the call record and on webhooks. |
from | string | no | Overrides the deployment's outbound number. Must be a number on your account. |
Whether from overrides are permitted on every plan is not yet settled. If the deployment already has a number pool attached, omit from and let routing choose.
Always send an Idempotency-Key. A retried POST without one places a second real call to a real person. See api-idempotency.
The response returns immediately with a queued call. Dialing has not started yet.
{
"id": "call_3xR8dNqA",
"deployment_id": "dep_7yQ2m4Kx",
"direction": "outbound",
"status": "queued",
"to": "+14155550142",
"from": "+14085550199",
"created_at": "2026-07-30T09:14:02Z",
"metadata": { "crm_record": "lead_9931" }
}
Call status
| Status | Meaning |
|---|---|
queued | Accepted, waiting for a concurrency slot. |
dialing | Carrier is placing the call. |
in_progress | Answered, agent is talking. |
completed | Call ended normally. Transcript and analysis follow. |
failed | Could not be placed. Check error. |
no_answer | Rang out with no pickup. |
busy | Carrier returned busy. |
canceled | Canceled before it connected. |
Do not poll for status. Subscribe to call events instead, described in webhook-events. Polling burns rate limit and still lags the actual call.
A call sitting in queued usually means you are at your concurrency ceiling. Calls queue rather than fail, so a burst of POSTs looks accepted and then stalls. Read concurrency before you size a burst.
Retrieve a call
curl https://api.hirefinn.ai/v1/calls/call_3xR8dNqA \
-H "Authorization: Bearer $FINN_API_KEY"
The completed record includes the fields above plus:
| Field | Type | Notes |
|---|---|---|
started_at | string | ISO 8601, when the call connected. Null if never answered. |
ended_at | string | ISO 8601. |
duration_seconds | integer | Billed talk time. Zero for unanswered calls. |
outcome | string | Business result. See call-outcomes. |
transcript | array | Turn objects with speaker and text. |
recording_url | string | Present only if recording is enabled and consent rules allow it. See recording-consent. |
analysis | object | Post-call extraction. See post-call-analysis. |
error | object | Present on failed. Contains code and message. |
Transcript and analysis are not populated the instant a call ends. If you fetch on the completed webhook and find analysis empty, that is expected timing, not a missing field. The exact ordering guarantee between call completion and analysis availability is not yet settled.
recording_url is short-lived. Treat it as a download link, not a permanent address. Retention is governed by your workspace policy in retention.
List calls
curl -G https://api.hirefinn.ai/v1/calls \
-H "Authorization: Bearer $FINN_API_KEY" \
--data-urlencode "deployment_id=dep_7yQ2m4Kx" \
--data-urlencode "status=completed" \
--data-urlencode "created_after=2026-07-01T00:00:00Z" \
--data-urlencode "limit=50"
Filters: deployment_id, direction, status, outcome, created_after, created_before. Results are cursor paginated, newest first. See api-pagination.
Listing is not an export path. Pulling a large history through this endpoint will hit api-rate-limits long before it finishes. For bulk history, use the export in call-logs.
TypeScript example
const res = await fetch("https://api.hirefinn.ai/v1/calls", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.FINN_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `ticket-${ticketId}-callback`,
},
body: JSON.stringify({
deployment_id: "dep_7yQ2m4Kx",
to: "+14155550142",
variables: { customer_name: "Ada Lovelace", order_id: "48812" },
}),
});
if (!res.ok) {
const err = await res.json();
throw new Error(`${res.status} ${err.error?.code}: ${err.error?.message}`);
}
const call = await res.json();
console.log(call.id, call.status);
Cancel a queued call
curl -X DELETE https://api.hirefinn.ai/v1/calls/call_3xR8dNqA \
-H "Authorization: Bearer $FINN_API_KEY"
Cancel works only while a call is queued. Once dialing starts the carrier owns it and the request returns a conflict. Error shapes are listed in api-errors.