> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.talkif.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.talkif.ai/_mcp/server.

# Make a call

> Place a single outbound call from the dashboard or the API, what Talkif checks before dialing, why a request can be refused, and how to follow the call while it runs.

A single outbound call is the building block everything else is made of: a campaign is many of them with pacing; a schedule is one of them on a timer; your own system triggering a callback is one of them with `contactId` set. Understand this one and the rest are configuration.

## What a call needs

| Input                   | Meaning                                                                                                                                                                                                                                                                                                                                              |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Flow**                | A *published* flow. Optionally a specific version; otherwise latest                                                                                                                                                                                                                                                                                  |
| **From number**         | A number your account owns with the voice capability — the caller ID the person sees. Its provider routes the call                                                                                                                                                                                                                                   |
| **To number**           | E.164. Emergency numbers are refused                                                                                                                                                                                                                                                                                                                 |
| **Contact** (optional)  | Links the call to a contact record: their timeline shows it, and `{{contact.*}}` variables in the prompts are filled from it. If you omit it, the to-number is still matched against your contacts for prompt variables; and after analysis, Talkif creates or enriches a contact from the call — filling empty fields only, never overwriting yours |
| **Metadata** (optional) | Any JSON you want stored with the call — your order ID, your ticket number — returned on the call record                                                                                                                                                                                                                                             |

## Placing it

#### Dashboard

**Start a call** on the home screen, or **Calls**. Pick a contact or type a number, choose the from-number and the flow, and call. The call appears in the active list immediately with its live status and transcript.

#### API

### Request

POST [https://api.talkif.ai/api/v1/calls](https://api.talkif.ai/api/v1/calls)

```curl
curl -X POST https://api.talkif.ai/api/v1/calls \
     -H "Authorization: Bearer <token>" \
     -H "Content-Type: application/json" \
     -d '{
  "flowId": "550e8400-e29b-41d4-a716-446655440000",
  "fromNumber": "+15559876543",
  "providerId": "550e8400-e29b-41d4-a716-446655440000",
  "toNumber": "+15551234567"
}'
```

```typescript
import { TalkifClient } from "@talkif/sdk";

async function main() {
    const client = new TalkifClient({
        token: "YOUR_TOKEN_HERE",
    });
    await client.calls.makeCall({
        flowId: "550e8400-e29b-41d4-a716-446655440000",
        fromNumber: "+15559876543",
        providerId: "550e8400-e29b-41d4-a716-446655440000",
        toNumber: "+15551234567",
    });
}
main();

```

```python
import requests

url = "https://api.talkif.ai/api/v1/calls"

payload = {
    "flowId": "550e8400-e29b-41d4-a716-446655440000",
    "fromNumber": "+15559876543",
    "providerId": "550e8400-e29b-41d4-a716-446655440000",
    "toNumber": "+15551234567"
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.talkif.ai/api/v1/calls"

	payload := strings.NewReader("{\n  \"flowId\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"fromNumber\": \"+15559876543\",\n  \"providerId\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"toNumber\": \"+15551234567\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.talkif.ai/api/v1/calls")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"flowId\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"fromNumber\": \"+15559876543\",\n  \"providerId\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"toNumber\": \"+15551234567\"\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.talkif.ai/api/v1/calls")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"flowId\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"fromNumber\": \"+15559876543\",\n  \"providerId\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"toNumber\": \"+15551234567\"\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.talkif.ai/api/v1/calls', [
  'body' => '{
  "flowId": "550e8400-e29b-41d4-a716-446655440000",
  "fromNumber": "+15559876543",
  "providerId": "550e8400-e29b-41d4-a716-446655440000",
  "toNumber": "+15551234567"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.talkif.ai/api/v1/calls");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"flowId\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"fromNumber\": \"+15559876543\",\n  \"providerId\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"toNumber\": \"+15551234567\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "flowId": "550e8400-e29b-41d4-a716-446655440000",
  "fromNumber": "+15559876543",
  "providerId": "550e8400-e29b-41d4-a716-446655440000",
  "toNumber": "+15551234567"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.talkif.ai/api/v1/calls")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Response (201)

```json
{
  "callId": "550e8400-e29b-41d4-a716-446655440000",
  "callSid": "CA1234567890abcdef1234567890abcdef",
  "flowId": "string",
  "fromNumber": "+15559876543",
  "providerId": "string",
  "providerType": "twilio",
  "status": "queued",
  "toNumber": "+15551234567",
  "type": "initiated"
}
```

Two success shapes: **`201`** with `type: "initiated"` — the call is being placed now; **`202`** with `type: "queued"` — your account is at its concurrent-call limit, and the call will be placed automatically when a slot frees. The queued response carries `position` and `estimatedWaitSeconds`.

`providerId` is the provider that owns the from-number; both are on the number's record from [`GET /phone/numbers`](/api-reference/phone-numbers/list-phone-numbers).

## What's checked before dialing

In order, and each one is a distinct refusal you can act on:

#### Do Not Call

The to-number is checked against your [Do Not Call list](/calls/do-not-call). On the list → refused with `contact_on_dnc_list`. This applies to every outbound path — direct, campaign, schedule.

#### Legal calling hours

Talkif enforces calling-hours rules by the recipient's jurisdiction — for US numbers, 8 am – 9 pm in the recipient's local time (TCPA). Outside the window → refused with `outside_callable_hours`. Campaign and scheduled calls aren't refused; they're **held** until the window opens. This is a floor, not a substitute for your own compliance: you remain responsible for the rules that apply to you.

#### Balance

At least \$1.00 available and the account active → otherwise `insufficient_balance`. See [Credits and pricing](/concepts/credits-and-pricing#admission-overdraft-and-suspension).

#### Rate and concurrency

Requests over your per-minute rate limit → `rate_limit_exceeded`. All concurrent slots busy → the call is **queued** (202), unless the queue is full → `queue_full`. Limits and queue priority are in [Limits](/reference/limits).

#### Flow and number

The flow must have a published version (`flow_not_published`); the from-number must be active and voice-capable (`phone_number_not_active`, `phone_number_cannot_initiate_calls`).

## Following the call

While it runs, the call's `status` moves `queued → initiated → ringing → inprogress`, and the transcript grows turn by turn. The dashboard shows both live; from your own systems, use [real-time events](/integrate/real-time-events) rather than polling. When it ends you get a terminal status, an `endReason`, and everything in [Transcripts and recordings](/calls/transcripts-and-recordings).

The full state machine, with every status, end reason and failure code explained: [How a call works](/getting-started/how-a-call-works).

## Next

#### [Campaigns](/calls/campaigns)

The same call, for a whole list, with pacing and retries.

#### [Real-time events](/integrate/real-time-events)

Follow status and transcript from your own code.