> 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.

# Inbound and outbound routing

> How a number decides what answers it, which numbers can place outbound calls and from where, pinning a number to a flow version, per-number recording, and the number statuses and capabilities you'll see.

A phone number in Talkif is a small routing record: which flow answers calls *to* it, whether calls can be placed *from* it, and a few per-number overrides. Get those three things right and everything else — campaigns, schedules, the API — just works, because they all route through the same rules.

## Inbound: the connected flow

Every inbound call is answered by the flow **connected** to the number it arrived on, at the version the connection specifies. A number is connected to at most one flow; a flow can be connected to many numbers.

#### Dashboard

Open the number and choose a flow under **Connected Agent** — only *published* flows are offered. Leave the version on latest unless you have a reason to pin.

#### API

### Request

PATCH [https://api.talkif.ai/api/v1/phone/numbers/\{phoneNumberId}/connect-flow](https://api.talkif.ai/api/v1/phone/numbers/\{phoneNumberId}/connect-flow)

```curl
curl -X PATCH https://api.talkif.ai/api/v1/phone/numbers/phoneNumberId/connect-flow \
     -H "Authorization: Bearer <token>" \
     -H "Content-Type: application/json" \
     -d '{
  "flowId": "550e8400-e29b-41d4-a716-446655440000"
}'
```

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

async function main() {
    const client = new TalkifClient({
        token: "YOUR_TOKEN_HERE",
    });
    await client.phoneNumbers.connectFlow({
        phoneNumberId: "phoneNumberId",
        flowId: "550e8400-e29b-41d4-a716-446655440000",
    });
}
main();

```

```python
import requests

url = "https://api.talkif.ai/api/v1/phone/numbers/phoneNumberId/connect-flow"

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

response = requests.patch(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/phone/numbers/phoneNumberId/connect-flow"

	payload := strings.NewReader("{\n  \"flowId\": \"550e8400-e29b-41d4-a716-446655440000\"\n}")

	req, _ := http.NewRequest("PATCH", 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/phone/numbers/phoneNumberId/connect-flow")

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

request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"flowId\": \"550e8400-e29b-41d4-a716-446655440000\"\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.patch("https://api.talkif.ai/api/v1/phone/numbers/phoneNumberId/connect-flow")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"flowId\": \"550e8400-e29b-41d4-a716-446655440000\"\n}")
  .asString();
```

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

$client = new \GuzzleHttp\Client();

$response = $client->request('PATCH', 'https://api.talkif.ai/api/v1/phone/numbers/phoneNumberId/connect-flow', [
  'body' => '{
  "flowId": "550e8400-e29b-41d4-a716-446655440000"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

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

```csharp
using RestSharp;

var client = new RestClient("https://api.talkif.ai/api/v1/phone/numbers/phoneNumberId/connect-flow");
var request = new RestRequest(Method.PATCH);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"flowId\": \"550e8400-e29b-41d4-a716-446655440000\"\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"] as [String : Any]

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

let request = NSMutableURLRequest(url: NSURL(string: "https://api.talkif.ai/api/v1/phone/numbers/phoneNumberId/connect-flow")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "PATCH"
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()
```

### Request

PATCH [https://api.talkif.ai/api/v1/phone/numbers/\{phoneNumberId}/disconnect-flow](https://api.talkif.ai/api/v1/phone/numbers/\{phoneNumberId}/disconnect-flow)

```curl
curl -X PATCH https://api.talkif.ai/api/v1/phone/numbers/phoneNumberId/disconnect-flow \
     -H "Authorization: Bearer <token>" \
     -H "Content-Type: application/json" \
     -d '{}'
```

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

async function main() {
    const client = new TalkifClient({
        token: "YOUR_TOKEN_HERE",
    });
    await client.phoneNumbers.disconnectFlow({
        phoneNumberId: "phoneNumberId",
    });
}
main();

```

```python
import requests

url = "https://api.talkif.ai/api/v1/phone/numbers/phoneNumberId/disconnect-flow"

payload = {}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.patch(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/phone/numbers/phoneNumberId/disconnect-flow"

	payload := strings.NewReader("{}")

	req, _ := http.NewRequest("PATCH", 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/phone/numbers/phoneNumberId/disconnect-flow")

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

request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{}"

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.patch("https://api.talkif.ai/api/v1/phone/numbers/phoneNumberId/disconnect-flow")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{}")
  .asString();
```

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

$client = new \GuzzleHttp\Client();

$response = $client->request('PATCH', 'https://api.talkif.ai/api/v1/phone/numbers/phoneNumberId/disconnect-flow', [
  'body' => '{}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

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

```csharp
using RestSharp;

var client = new RestClient("https://api.talkif.ai/api/v1/phone/numbers/phoneNumberId/disconnect-flow");
var request = new RestRequest(Method.PATCH);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [] as [String : Any]

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

let request = NSMutableURLRequest(url: NSURL(string: "https://api.talkif.ai/api/v1/phone/numbers/phoneNumberId/disconnect-flow")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "PATCH"
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()
```

What happens on an inbound call, in order:

1. The number is looked up. Unknown number → rejected (`unknown_phone_number`).
2. The number must have a connected flow. None → answered **busy**, and a *failed* call is recorded with `no_connected_flow` so the misconfiguration is visible in History.
3. Your balance must be at or above the minimum (\$1.00). Below → busy, recorded with `insufficient_balance`.
4. The flow version is resolved — *latest* or the pinned version — and the call is admitted like any other. See [How a call works](/getting-started/how-a-call-works).

Disconnecting a flow returns the number to step 2: inbound calls are answered busy until something is connected again. Do it deliberately when retiring a line; don't leave production numbers disconnected.

### Pinning a version

By default a connection follows **latest**: whatever you publish next is what the number answers with, immediately. Pass a `flowVersion` when connecting to **pin** the number to that version. Pinned numbers ignore later publishes — until you publish *over* that exact version, in which case the pin is moved forward to the new one. Use it for lines that must not change without an explicit step; the rules are in [Flows and versions](/concepts/flows-and-versions#which-version-does-a-call-run).

## Outbound: the from-number

An outbound call — from the dashboard, the API, a campaign or a schedule — is placed **from** a number you own. Two things must be true of it:

* It has the **voice** capability and `canInitiateCalls: true`. Talkif-purchased, SIP and own-Twilio numbers do; WhatsApp numbers are receive-only.
* It's **Active**, and its rental isn't suspended.

The call is routed through the number's **provider**: a Talkif number goes out over Talkif's carrier, a SIP number over your trunk, a Twilio-account number through your Twilio account. That's why [`POST /calls`](/api-reference/calls/make-call) takes both `fromNumber` and `providerId` — the provider is the one that owns the number, and it's returned on the number's record.

The from-number is the caller ID the person sees. Choose one they'll recognise — a local number for a local campaign, your main line for support callbacks. Carriers may override or block caller IDs that don't belong to the trunk; on SIP that's a carrier-side setting.

## Per-number recording

Recording has an account-wide default. A flow can force it on or off; a number can too, and the order is **flow → number → account** (the first explicit setting wins). Set a number's override on its page or with [`PUT /phone/numbers/{id}`](/api-reference/phone-numbers/update-phone-number) (`recordingOverride`: `use_account_default`, `enabled`, `disabled`).

Use a number override for lines with a legal position of their own — a line in a jurisdiction that requires consent, say — knowing that a flow which forces recording on will still record on it.

## What a number record tells you

**`status`** `ACTIVE · PENDING · SUSPENDED · RELEASED`

`ACTIVE` — provisioned and usable. `PENDING` — being provisioned; for WhatsApp, waiting for Meta to confirm calling is enabled. `SUSPENDED` — disabled, usually for unpaid rental. `RELEASED` — given back; no longer yours.

---

**`capabilities`** `VOICE · SMS · MMS · WHATSAPP`

What the number can do. Only `VOICE` matters for calling; a WhatsApp number carries `WHATSAPP` and not `VOICE`.

---

**`channelType`** `pstn · whatsapp`

A `pstn` number is identified by its E.164 digits; a `whatsapp` number by Meta's Phone Number ID. The same digits can exist once per channel.

---

**`canInitiateCalls`** `boolean`

Whether it can be a from-number. `false` for WhatsApp.

---

**`billingStatus`** `active · processing · past_due · suspended`

Rental state for purchased numbers — see [Buy a number](/telephony/buy-a-number#paying-for-it).

---

**`providerId · providerType`** `uuid · enum`

Which provider routes it: the platform's system provider (`TWILIO`), your SIP trunk (`SIP`), your Twilio account (`OWN_TWILIO`), or a WABA (`WHATSAPP`).

---

**`twilioEdge · twilioRegion`** `string`

Where the number's carrier traffic is served from — see [Regions and edges](/telephony/regions-and-edges).

---

Give every number a **friendly name** ("Support line", "Outbound – Berlin"). It appears in call lists and campaign setup, and it's the difference between recognising a line and reading digits.

## Next

#### [Make a call](/calls/make-a-call)

Place an outbound call from a number.

#### [Flows and versions](/concepts/flows-and-versions)

Everything about versions and pinning.