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

# Agent quickstart

> Place a call, learn how it ended, and read the transcript: the call lifecycle an autonomous agent needs, in three requests.

## Overview

A voice call is asynchronous. You dispatch it, it runs for a minute or two without you, and then there is an outcome to collect. This page is the shortest complete path through that lifecycle: place a call, learn how it ended, read what was said.

Authentication is a separate story. If your agent already has an API key, you are ready. If it does not have one and there is no human around to paste one in, see the [Agent onboarding API](/platform/connect-your-agent) first.

<Note>
  Send your key as `Authorization: Bearer YOUR_API_KEY`. A bare `Authorization: YOUR_API_KEY` with no prefix is also accepted. `x-api-key` is not read and does not authenticate.
</Note>

## Prerequisites

* A Bland API key, exported as `BLAND_API_KEY`.
* A phone number to call, in [E.164](https://en.wikipedia.org/wiki/E.164) format (`+15551234567`).
* Optionally, a URL Bland can reach, if you want the outcome pushed to you instead of polling for it.

## The lifecycle

<Steps>
  <Step title="Place the call">
    `POST /v1/calls` dispatches the call and returns immediately with a `call_id`. It does not wait for the call to finish.

    ```bash theme={null}
    curl -X POST https://api.bland.ai/v1/calls \
      -H "Authorization: Bearer $BLAND_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "phone_number": "+15551234567",
        "task": "Call Rossi Hardware and ask whether they carry 10mm hex keys. Thank them and end the call.",
        "voice": "Karen",
        "max_duration": 5,
        "keywords": ["Rossi Hardware"]
      }'
    ```

    ```json Response theme={null}
    {
      "status": "success",
      "call_id": "9d404c1b-6a23-4426-953a-a52c392ff8f1"
    }
    ```

    `max_duration` (in minutes) bounds how long you can be waiting. Set it deliberately: it is the upper bound on your own wait loop.
  </Step>

  <Step title="Learn how it ended">
    Two ways, and you should pick one on purpose rather than defaulting to the second.

    **Push.** Add a `webhook` to the request body. When the call ends, Bland POSTs the full call object to that URL, transcript included. Nothing to poll.

    ```bash theme={null}
    curl -X POST https://api.bland.ai/v1/calls \
      -H "Authorization: Bearer $BLAND_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "phone_number": "+15551234567",
        "task": "Ask whether they carry 10mm hex keys.",
        "webhook": "https://your-agent.example.com/bland-webhook",
        "webhook_events": ["call"]
      }'
    ```

    `webhook` alone gives you the post-call payload. `webhook_events` is optional on top of it, and streams progress *during* the call (`call` covers connected, transferred, and ended). See [Post call webhooks](/tutorials/post-call-webhooks) for the payload shape and [Webhook signing](/tutorials/webhook-signing) for verifying it is really us.

    **Poll.** If you have no URL Bland can reach, `GET /v1/calls/{call_id}` returns the current state of the call.

    ```bash theme={null}
    curl https://api.bland.ai/v1/calls/$CALL_ID \
      -H "Authorization: Bearer $BLAND_API_KEY"
    ```

    Poll on a fixed interval (5 seconds is plenty) and stop when `completed` is `true`. Do not tighten the loop hoping to finish sooner: the call takes as long as the call takes.

    The fields that tell you what happened:

    | Field | What it tells you |
    | - | - |
    | `completed` | `true` once the call is over. This is your loop's exit condition. |
    | `status` | `completed`, `failed`, `busy`, `no-answer`, `canceled`, or `unknown`. |
    | `answered_by` | `human`, `voicemail`, `unknown`, or `no-answer`. A completed call that reached voicemail is still `status: completed`. |
    | `error_message` | Populated when `status` is `failed`. Read it before retrying. |
    | `call_ended_by` | `ASSISTANT` or `USER`. |

    <Warning>
      A completed call is not automatically a successful one. Check `answered_by` and `error_message` too: a call that went to voicemail, or that a carrier blocked as spam, still comes back `completed`.
    </Warning>
  </Step>

  <Step title="Read the transcript">
    Same endpoint, once `completed` is `true`. If you took the push path, this payload already arrived at your webhook and you can skip the request entirely.

    ```bash theme={null}
    curl https://api.bland.ai/v1/calls/$CALL_ID \
      -H "Authorization: Bearer $BLAND_API_KEY" | jq '{
        summary,
        concatenated_transcript,
        answered_by,
        call_length
      }'
    ```

    * `concatenated_transcript` is the whole conversation as one string. Use it when you are going to feed the call to a model.
    * `transcripts` is the same content as an array of turns, each with `text`, `user` (`user`, `assistant`, `robot`, or `agent-action`), and `created_at`. Use it when you need turn boundaries or timing.
    * `summary` is a short model-written recap generated when the call ends.
    * `recording_url` is present only if you passed `record: true`.

    Post-call fields can take up to a minute after hangup to settle while the audio is processed, so a transcript that looks short immediately after `completed` flips may still be filling in.
  </Step>
</Steps>

## Push or poll?

<CardGroup cols={2}>
  <Card title="Poll" icon="arrows-rotate">
    You are a hosted agent with no inbound URL: a chat assistant, a sandboxed runtime, a laptop behind NAT. There is nowhere for Bland to deliver to, so `GET /v1/calls/{call_id}` on an interval is the right answer, not a workaround.
  </Card>

  <Card title="Subscribe" icon="webhook">
    You have any reachable URL: a server, a serverless function, a tunnel. Set `webhook` and stop polling. You get the outcome the moment it exists instead of one poll interval later, and the payload already contains the transcript.
  </Card>
</CardGroup>

Developing locally is the case that looks like it needs polling but does not. The [Bland CLI](/sdks/cli) forwards webhooks to a local port, so you can take the push path from your laptop with no tunnel to set up:

```bash theme={null}
bland listen --forward-to http://localhost:3000/webhook
```

Point the call's `webhook` at the URL the forwarder prints, and your local handler receives the real post-call payload.

## Next steps

<CardGroup cols={2}>
  <Card title="Call recipes for agents" icon="book" href="/platform/agent-call-recipes">
    Voicemail, proper nouns, and escalating to a human.
  </Card>

  <Card title="Agent onboarding API" icon="key" href="/platform/connect-your-agent">
    How a bot gets its own API key and phone number.
  </Card>

  <Card title="Send Call API reference" icon="code" href="/api-v1/post/calls">
    Every parameter `POST /v1/calls` accepts.
  </Card>

  <Card title="Bland MCP Server" icon="plug" href="/integrations/mcp/overview">
    The same operations as MCP tools, with nothing to install.
  </Card>
</CardGroup>

***

Docs for agents: [llms.txt](/llms.txt)


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