> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pineyard.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Round-robin transfer and missed transfers

> Share transferred callers across your team's numbers, and see which transfers nobody picked up.

When a caller asks to speak to a person, a **round-robin** transfer sends each caller to the next number on a list you
keep, such as your sales team's phones. After the call, Pineyard works out whether your team member picked up. Calls
nobody picked up are listed as **missed transfers** so your team can call those leads back. Calls that were picked up
show the recording and transcript of the conversation with your team.

> [!NOTE]
>
> These are two separate features, and either may not be available for your workspace yet. Contact Pineyard to enable
> them.
>
> * **Round-robin transfer.** When it's off, the dashboard doesn't offer **Round robin**, the API refuses
>   `transferMode: "ROUND_ROBIN"` with `400 transfer_round_robin_unavailable`, and the transfer-list endpoints answer
>   `404 not_found`.
> * **Missed transfers and team conversations.** When it's off, the dashboard has no **Missed transfers** filter,
>   `GET /calls?transfer=…` answers `400 transfer_filter_unavailable`, and calls carry no `transfer` or `humanLeg`
>   fields.

## How round robin works

1. During a call, the caller asks for a person.
2. Pineyard picks the next **active** number on the agent's list and transfers the call to it. Each transfer moves one
   place along the list, whoever answered last time. Numbers that are switched off, and numbers already on the call,
   are skipped.
3. The call goes to the agent's **fallback number** instead when the list has no usable number, when Pineyard can't
   pick one in time, or when your workspace is suspended. Without a fallback number, the transfer can't complete and
   the call continues with the AI.

A transferred caller rings one number only. If that person doesn't answer, the caller isn't passed on to the next
number; call them back from the missed transfers list.

## How missed transfers work

Pineyard tracks transfers made in **round robin** and **webhook** mode (see
[Call transfer by webhook](https://docs.pineyard.ai/guides/call-transfer-webhook.md)), not transfers to a single static number. Tracking isn't
available on every phone number yet: a transfer Pineyard can't follow stays **Waiting for result** (`PENDING`). Ask
Pineyard which of your numbers are covered. Once the call ends, a tracked transfer gets one of these results:

* **Not picked up, busy, or couldn't connect.** You get a **Missed transfer** notification in the dashboard, and the
  call is listed under **Calls → Missed transfers**. A clear miss is reported once the network reports that the call
  ended, usually within a few minutes. When Pineyard has to listen to the call to tell, for example because a
  voicemail answered, it can take a few minutes longer.
* **Picked up.** The call page shows **Conversation with your team**, with the recording and transcript of that part
  of the call.
* **Needs review.** When Pineyard can't tell, the call is listed under **Needs review** instead of being guessed.

> [!WARNING]
>
> Pineyard needs the call's audio to tell most outcomes apart and to show the conversation. On a phone number where
> Pineyard can't get the audio, transfers that aren't a clear miss show as **Needs review**, with no recording or
> transcript.

## Set up the list

### In the dashboard

Open the agent, go to **Abilities → Transfer to a human**, and set **Transfer to** to **Round robin**. Add each team
member's name and number, put them in the order you want, and switch a number off while that person is away. You can
also paste or upload a CSV with one `name,number` per line. Set a **Fallback number** so callers still reach someone
when nobody on the list is available.

### With the API

Read the list with `GET /agents/{id}/transfer-targets` (scope `agents.view`). Replace it with
`PUT /agents/{id}/transfer-targets` (scope `agents.edit`). The order of the array is the rotation order. Rows you
leave out are removed. Past calls keep the name and number they were transferred to.

```bash theme={null}
curl -X PUT https://api.pineyard.ai/agents/$AGENT_ID/transfer-targets \
  -H "Authorization: Bearer $PINEYARD_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "targets": [
      { "label": "Asha", "phoneNumber": "+919800000001", "active": true },
      { "label": "Ravi", "phoneNumber": "+919800000002", "active": true },
      { "label": "Meera", "phoneNumber": "+919800000003", "active": false }
    ]
  }'
```

| Field | Meaning |
| - | - |
| `id` | Optional. Send it back to keep an existing row. Leave it out, or send `null`, for a new row. |
| `label` | The person's name, up to 80 characters. |
| `phoneNumber` | A valid phone number, up to 32 characters as written. Indian numbers can be written without `+91`, and spaces or dashes are ignored. Stored in E.164. |
| `active` | `false` skips the number without removing it. Default `true`. |

Both endpoints answer with the stored list. `PUT` can also return a `warnings` array (see below):

```json theme={null}
{
  "targets": [
    {
      "id": "00000000-0000-4000-8000-000000000001",
      "label": "Asha",
      "phoneNumber": "+919800000001",
      "active": true,
      "position": 0,
      "createdAt": "2026-11-05T10:00:00.000Z",
      "updatedAt": "2026-11-05T10:00:00.000Z"
    }
  ]
}
```

Then switch the agent to round robin with [`PATCH /agents/{id}`](https://docs.pineyard.ai/api-reference/update-an-agent) (scope
`agents.edit`). In `ROUND_ROBIN` mode, `transferNumber` is the optional fallback number:

```bash theme={null}
curl -X PATCH https://api.pineyard.ai/agents/$AGENT_ID \
  -H "Authorization: Bearer $PINEYARD_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "transferEnabled": true, "transferMode": "ROUND_ROBIN", "transferNumber": "+919800000000" }'
```

If transfer is on in round robin with no active number on the list and no fallback number, the change is saved but the
response (to the agent update, or to the list `PUT`) carries a `warnings` entry with code
`transfer_round_robin_no_fallback`: callers asking for a person would reach nobody. While round-robin transfer is
enabled for your workspace, `GET /agents` and `GET /agents/{id}` include `transferTargetCount`, the number of active
numbers on the list; the field is left out when it isn't. Duplicating an agent copies its list on a best-effort basis:
if the copy of the list fails, the duplicate is still created, with an empty list to fill in.

Errors:

| Status | `error` | Cause |
| - | - | - |
| 400 | `invalid_transfer_targets` | The list breaks a rule. `issues` lists each problem with `index` (the row, or `null` for the whole list), `field` and `code`: `too_many_targets` (more than 50), `label_required`, `label_too_long`, `invalid_phone_number`, `duplicate_phone_number` (the same number twice, however it's written). |
| 400 | `invalid_body` | The request isn't the right shape: `targets` is missing, a field has the wrong type, or a value is far past the limits above (more than 500 rows, a label over 200 characters, a number over 64 characters). A number of 33 to 64 characters is refused as `invalid_transfer_targets` with code `invalid_phone_number` instead. |
| 400 | `transfer_round_robin_unavailable` | Round-robin transfer isn't enabled for your workspace. |
| 404 | `not_found` | No such agent, or round-robin transfer isn't enabled for your workspace. |
| 409 | `agent_archived` | The agent is archived. |

## Find missed transfers

`GET /calls?transfer=missed` lists transferred calls nobody picked up, newest transfer first (scope `calls.view`).
`transfer=needs_review` lists the ones Pineyard couldn't decide, and `transfer=transferred` lists every tracked transfer.
Any other value is refused with `400 invalid_transfer_filter`. Each call in the list carries a `transfer` object:
`{ status, targetLabel, targetNumber, transferredAt }`.

[`GET /calls/{id}`](https://docs.pineyard.ai/api-reference/get-a-call-by-id) includes a `humanLeg` object on tracked transfers:

| Field | Meaning |
| - | - |
| `status` | `PENDING` (not known yet), `ANSWERED`, `NO_ANSWER`, `BUSY`, `FAILED` (couldn't connect) or `UNKNOWN` (needs review). |
| `statusReason` | A short machine-readable reason, such as `voicemail_announcement` or `two_party_speech`, or `null`. |
| `targetLabel`, `targetNumber` | Who the call was transferred to. |
| `transferredAt`, `endedAt`, `durationSeconds` | When the transfer happened, when the call ended, and how long it lasted after the transfer. |
| `hasRecording` | Whether a recording of the conversation with your team is available. |
| `hasFullRecording` | Whether the whole call (the AI part, the hand-off and the conversation with your team) is available as one recording. |
| `transcript` | `[{ speaker, text, startSeconds, endSeconds }]`, or `null`. `speaker` is a string such as `"1"`, numbered in order of appearance. |

Fetch the recording with `GET /calls/{id}/human-leg/recording` (scope `calls.view`). It returns `{ url, expiresAt }`: a
short-lived link, fetched fresh on every request, so ask again when it expires rather than storing it. Add `?leg=full`
for the whole call in one recording when `hasFullRecording` is `true`. It answers `404 not_found` when the call doesn't
exist or missed transfers aren't enabled for your workspace, `400 invalid_params` for a `leg` other than `human` or
`full`, and `404 no_recording` when there's no recording to serve or it is no longer kept.

## Testing

* Set a **fallback number** before going live.
* Make a few test calls, and ask the agent for a person each time. Each call should ring the next number on the list.
* On a number Pineyard tracks, leave one test transfer unanswered. It should appear under **Missed transfers**, with a
  notification, after the call ends.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.