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

# Pre-connect requests

> Look the caller up in your systems before the phone call is answered.

When a call arrives on your agent's number, the platform can call your HTTPS endpoint with the caller's number before answering, and use what you return to greet the caller by name, give the agent their record, or decline the call. It is configured on the agent record; the only code you write is the endpoint.

<Note>
  Telephony only. WebSocket and browser sessions never trigger pre-connect requests.
</Note>

## How it works

1. A call arrives on the agent's number. Before answering, the platform runs the agent's `pre_connect_requests` in order, at most two.
2. For each entry it makes one HTTPS request to your URL, carrying the values named in `sends`, such as `caller_number`.
3. Your endpoint replies with JSON within the timeout, 800 ms at most. The platform reads the values named in `returns` off the response by dotted path.
4. The platform answers the call. If your response included a `greeting` and the entry allows it, that greeting is spoken. Otherwise the agent's greeting is spoken, with any `{{name}}` placeholders filled from the captured values.
5. The captured values are placed at the top of the conversation, so the model knows them from the first turn.

Every step fails open. A timeout, a non-2xx status, an unparseable body or a missing value means the call is answered as if the lookup had never run. The one exception is a response of `{"reject": true}`, which ends the call unanswered.

## Add it to your agent

`pre_connect_requests` is a field on the agent record. Add it with `PUT /v1/agents/{id}` on an agent you already have, or include it in the body of `POST /v1/agents` when you [create one](/docs/voice-agents/voice-agent-api/create-agent). This update asks a CRM for the patient behind the caller's number, greets them by name, and keeps their record id for the conversation:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PUT https://agents.assemblyai.com/v1/agents/$AGENT_ID \
    -H "Authorization: $ASSEMBLYAI_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "greeting": "Thanks for calling Northside Dental, {{patient_name}}. How can I help?",
      "system_prompt": "You are the front desk for Northside Dental. The pre-connect context contains the caller'"'"'s patient_id; use it when you look up appointments and do not ask for it.",
      "pre_connect_requests": [
        {
          "http": {
            "url": "https://example.com/voice/lookup",
            "http_method": "POST",
            "headers": [{ "name": "Authorization", "value": "Bearer crm-service-token" }]
          },
          "sends": ["caller_number"],
          "returns": [
            { "name": "patient_name", "path": "patient.first_name", "default": "there" },
            { "name": "patient_id", "path": "patient.id" }
          ],
          "timeout_ms": 600,
          "allow_overrides": ["greeting"]
        }
      ]
    }'
  ```

  ```python Python theme={null}
  # pip install requests
  import os
  import requests

  resp = requests.put(
      f"https://agents.assemblyai.com/v1/agents/{os.environ['AGENT_ID']}",
      headers={"Authorization": os.environ["ASSEMBLYAI_API_KEY"]},
      json={
          "greeting": "Thanks for calling Northside Dental, {{patient_name}}. How can I help?",
          "system_prompt": (
              "You are the front desk for Northside Dental. The pre-connect context "
              "contains the caller's patient_id; use it when you look up appointments "
              "and do not ask for it."
          ),
          "pre_connect_requests": [
              {
                  "http": {
                      "url": "https://example.com/voice/lookup",
                      "http_method": "POST",
                      "headers": [{"name": "Authorization", "value": "Bearer crm-service-token"}],
                  },
                  "sends": ["caller_number"],
                  "returns": [
                      {"name": "patient_name", "path": "patient.first_name", "default": "there"},
                      {"name": "patient_id", "path": "patient.id"},
                  ],
                  "timeout_ms": 600,
                  "allow_overrides": ["greeting"],
              }
          ],
      },
  )
  resp.raise_for_status()
  print(resp.json()["pre_connect_requests"])
  ```

  ```javascript Node.js theme={null}
  // Node 18+ has fetch built in
  const res = await fetch(`https://agents.assemblyai.com/v1/agents/${process.env.AGENT_ID}`, {
    method: "PUT",
    headers: {
      Authorization: process.env.ASSEMBLYAI_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      greeting: "Thanks for calling Northside Dental, {{patient_name}}. How can I help?",
      system_prompt:
        "You are the front desk for Northside Dental. The pre-connect context contains the caller's patient_id; use it when you look up appointments and do not ask for it.",
      pre_connect_requests: [
        {
          http: {
            url: "https://example.com/voice/lookup",
            http_method: "POST",
            headers: [{ name: "Authorization", value: "Bearer crm-service-token" }],
          },
          sends: ["caller_number"],
          returns: [
            { name: "patient_name", path: "patient.first_name", default: "there" },
            { name: "patient_id", path: "patient.id" },
          ],
          timeout_ms: 600,
          allow_overrides: ["greeting"],
        },
      ],
    }),
  });
  const agent = await res.json();
  console.log(agent.pre_connect_requests);
  ```
</CodeGroup>

The response echoes the configuration. Header values are write-only and come back as the header name and when it was last set. Attach a phone number as in [Connect to Twilio](/docs/voice-agents/voice-agent-api/connect-to-twilio), and the lookup runs on every inbound call to that number.

| Field              | Type             | Description                                                                                                                                                             |
| ------------------ | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `http.url`         | string, required | Your endpoint. Must be `https://`, and its hostname must resolve in public DNS when the agent is saved.                                                                 |
| `http.http_method` | string           | `POST` (default), `PUT`, `PATCH`, `GET` or `DELETE`. Sent values travel in the JSON body for `POST`, `PUT` and `PATCH`, and as query parameters for `GET` and `DELETE`. |
| `http.headers`     | array            | Headers to send, each `{ "name", "value" }`. Use one to authenticate your endpoint. Values are stored encrypted and never returned.                                     |
| `sends`            | array of strings | What to send: any of the [call facts](#what-your-endpoint-receives) below, and any name an earlier entry captured. Nothing is sent unless you name it.                  |
| `returns`          | array            | Values to capture from the response. Each has a `name` you choose, a dotted `path` into the JSON, and an optional `default` used when the path does not resolve.        |
| `timeout_ms`       | integer          | Shortens this entry's timeout, from 1 to 800. Every entry has a ceiling of 800 ms; omit this for the full ceiling.                                                      |
| `allow_overrides`  | array of strings | Agent fields the response may replace. The only value today is `"greeting"`. Without it, a `greeting` in the response is ignored.                                       |

## What your endpoint receives

The request carries only what `sends` names. The platform supplies five call facts that any entry may send:

| Name            | Value                                                                                                                                           |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `caller_number` | The caller's number in E.164 form, for example `+14155550100`.                                                                                  |
| `dialed_number` | The agent's number the caller dialled, in E.164 form.                                                                                           |
| `direction`     | `"inbound"` or `"outbound"`.                                                                                                                    |
| `agent_id`      | The id of the agent handling the call.                                                                                                          |
| `session_id`    | The id of the session being created, the same id you will see in [session history](/docs/voice-agents/voice-agent-api/session-history) and webhooks. |

With `"sends": ["caller_number", "dialed_number", "direction"]` a `POST` endpoint receives:

```json theme={null}
{
  "caller_number": "+14155550100",
  "dialed_number": "+14155550199",
  "direction": "inbound"
}
```

A `GET` endpoint receives the same values as query parameters: `?caller_number=%2B14155550100&dialed_number=%2B14155550199&direction=inbound`.

A fact the platform does not have is left out, not sent blank. When the caller withholds their number, `caller_number` is missing from the request; treat that as an unknown caller, and the `default` on each capture applies.

The request is a plain HTTPS call with the headers you configured. There is no signature; authenticate it with a header value only you and the platform know.

## What your endpoint returns

Respond with `200` and a JSON object within the timeout.

| In the response                  | Effect                                                                                                                                                                                                                                           |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| A value at each `returns[].path` | Captured under `returns[].name`. Strings are taken as is; numbers and booleans become text; objects and arrays are ignored. Values over 512 characters are dropped. Paths are dotted: `patient.first_name`, or `results.0.id` to index an array. |
| Top-level `"greeting": "..."`    | Spoken instead of the agent's greeting on this call, if the entry lists `"greeting"` in `allow_overrides`.                                                                                                                                       |
| Top-level `"reject": true`       | The call is not answered.                                                                                                                                                                                                                        |
| Anything else                    | Ignored.                                                                                                                                                                                                                                         |

A response that is not JSON, not a 2xx, larger than 8 KB, or later than the timeout counts as no response. The platform does not retry.

For the clinic agent above, the CRM might answer:

```json theme={null}
{
  "patient": { "id": "pt_48213", "first_name": "Maria", "last_visit": "2026-07-02" }
}
```

The platform captures `patient_name` as `Maria` and `patient_id` as `pt_48213`, and the caller hears "Thanks for calling Northside Dental, Maria. How can I help?"

## Use the captured values

### In the greeting

Write `{{name}}` in the agent's `greeting` for any name in `returns`. The platform fills it from the captured value, or from that capture's `default` when the lookup returned nothing. An unknown caller to the clinic agent hears "Thanks for calling Northside Dental, there. How can I help?", so pick a default that reads well in the sentence.

Every placeholder has to end up with a non-empty value. If any `{{name}}` has neither a value nor a default, or resolves to an empty string, the platform speaks the agent's `greeting` exactly as written, braces included. An empty `default` is not a way to make a placeholder disappear.

Only names in `returns` are substituted. To speak the caller's number back, have your endpoint return it and capture it.

### As a greeting your endpoint writes

When your endpoint should decide the wording, return a top-level `greeting` and list `"greeting"` in the entry's `allow_overrides`:

```json theme={null}
{
  "patient": { "id": "pt_48213", "first_name": "Maria" },
  "greeting": "Thanks for calling Northside Dental, Maria. Are you calling about your appointment on Thursday?"
}
```

A returned greeting takes precedence over the agent's templated greeting.

### In the conversation

The platform puts the captured values at the top of the transcript as the result of a platform tool named `aai_pre_connect_context`:

```json theme={null}
{ "variables": { "patient_name": "Maria", "patient_id": "pt_48213" } }
```

You do not add this tool; the platform inserts the result whenever a pre-connect entry captured anything. The model treats the values as established facts, so it can pass `patient_id` to a tool without asking the caller for it. Tell the model in the `system_prompt` what the names mean, as the clinic agent does. If you [connect your own LLM](/docs/voice-agents/voice-agent-api/connect-your-own-llm), the same tool result appears in the `messages` your endpoint receives.

## Chain two lookups

A second entry can send what the first captured. Here the first request resolves the caller to a patient id and the second fetches that patient's next appointment:

```json theme={null}
{
  "pre_connect_requests": [
    {
      "http": {
        "url": "https://example.com/voice/lookup",
        "http_method": "POST",
        "headers": [{ "name": "Authorization", "value": "Bearer crm-service-token" }]
      },
      "sends": ["caller_number"],
      "returns": [
        { "name": "patient_id", "path": "patient.id" },
        { "name": "patient_name", "path": "patient.first_name", "default": "there" }
      ]
    },
    {
      "http": { "url": "https://example.com/voice/next-appointment", "http_method": "GET" },
      "sends": ["patient_id"],
      "returns": [
        { "name": "next_appointment", "path": "appointment.spoken_date", "default": "no upcoming appointment" }
      ]
    }
  ]
}
```

Entries run in order, each with its own 800 ms ceiling, so two entries can hold the call for up to about 1.6 s before the greeting. If the first entry fails, the second still runs, but `patient_id` is not sent because nothing captured it. If an earlier entry captures a value under the same name as a call fact, a later entry that sends that name sends your value, not the platform's.

## Limits and validation

The API checks the configuration when you save the agent and returns `422` naming the failing field:

| Rule                                                                                               | Error                                                                                                                     |
| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| At most two entries                                                                                | `pre_connect_requests: at most 2 pre-connect requests are supported; got 3`                                               |
| `timeout_ms` from 1 to 800                                                                         | `pre_connect_requests[0].timeout_ms: ... must be an integer between 1 and 800 ms`                                         |
| `sends` names a call fact or an earlier capture                                                    | `pre_connect_requests[0].sends[0]: 'customer_tier' is neither a call fact nor captured by an earlier pre-connect request` |
| `allow_overrides` contains only `greeting`                                                         | `pre_connect_requests[0].allow_overrides[0]: 'voice' is not an overridable field; allowed: greeting`                      |
| `http.url` is HTTPS                                                                                | `pre_connect_requests[0].http.url: webhook URL must use https://`                                                         |
| `http.url` host resolves in public DNS                                                             | `pre_connect_requests[0].http.url: webhook URL host 'crm.example.com' does not resolve`                                   |
| `returns[].path` is a dotted path of `[A-Za-z0-9_-]+` segments, at most 10 deep and 256 characters | `pre_connect_requests[0].returns[0].path: 'patient..id' is not a valid dotted path`                                       |
| Capture names are unique across all entries                                                        | `pre_connect_requests[1].returns[0].name: 'patient_id' is already captured by an earlier pre-connect request`             |

At call time: each entry has 800 ms, responses are read up to 8 KB, and each captured value is kept up to 512 characters.

## Test it

You can watch what the platform sends without writing any code. Point an entry at a request-capture service, name every call fact in `sends`, attach a number, and call it:

```json theme={null}
{
  "pre_connect_requests": [
    {
      "http": { "url": "https://webhook.site/your-unique-id", "http_method": "POST" },
      "sends": ["caller_number", "dialed_number", "direction", "agent_id", "session_id"]
    }
  ]
}
```

The request appears the moment the call arrives, before the greeting.

## Troubleshooting

**The request body is empty.** The entry's `sends` is empty or missing. The platform sends nothing you did not name; add `"sends": ["caller_number"]` and save the agent again.

**The request never arrives.** Check that the number is attached to this agent on the same regional host you created it on; agent ids are not shared between `agents.assemblyai.com` and `agents.us.assemblyai.com`. Then check your endpoint answers `200` within the timeout to a `POST` carrying your headers. A `401` from your own auth layer looks the same as a lookup that never ran, because the platform fails open either way.

**The greeting did not change.** A `greeting` in the response needs `"allow_overrides": ["greeting"]` on that entry. A templated greeting needs every `{{name}}` to resolve to a non-empty value, from the response or from its `default`.

**The number is missing on some calls.** The caller withheld their number. The platform omits `caller_number` rather than sending a placeholder.

**The model does not use the values.** Name them in the `system_prompt` and say what to do with them.

## Next steps

<CardGroup cols={2}>
  <Card title="Connect to Twilio" icon="phone" href="/docs/voice-agents/voice-agent-api/connect-to-twilio">
    Attach a phone number so calls reach this agent.
  </Card>

  <Card title="Tools" icon="wrench" href="/docs/voice-agents/voice-agent-api/tools/overview">
    Let the agent act on the caller's record during the call.
  </Card>

  <Card title="Webhooks" icon="bell" href="/docs/voice-agents/voice-agent-api/webhooks">
    Get the call's `from_number` and outcome after it ends.
  </Card>
</CardGroup>
