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

# Run a FormBharo Voice Call Directly from Your Web Page

> Embed FormBharo voice calls in your own page using WebRTC: generate a conversation ID, open the audio connection, and poll for answers as they arrive.

export const FORMBHARO_URL = "https://api.formbharo.artpark.ai";

You can embed a FormBharo voice call directly in your web page using WebRTC. The call opens an audio connection to the agent, which asks questions out loud. You poll a key-less endpoint to read the answers as they come in.

## Before you start

You need two things: the **agent ID** for the agent you want to run, and the **URL of your FormBharo server**.

Before the call begins, fetch the agent's public profile with `GET /api/v1/agents/{agent_id}/public`. This route requires no API key, so you can call it safely from the browser. It returns the agent's `title`, its `questions` list, and an `agent_speaks_first` flag — enough to render a form UI or a progress indicator before the audio connection is open.

```bash curl theme={null}
curl {FORMBHARO_URL}/api/v1/agents/AGENT_ID/public
```

Use `agent_speaks_first` to decide whether to show a "waiting for agent" state or a push-to-talk prompt when the call first connects.

## Run the call

<Steps>
  <Step title="Generate a conversation ID">
    Create a unique conversation ID in your browser before opening the connection. FormBharo uses whatever ID you provide — it does not generate one for you.

    ```javascript javascript theme={null}
    const conversationId = crypto.randomUUID();
    ```

    Store this value — you'll pass it to the WebRTC offer and use it later to poll for answers.
  </Step>

  <Step title="Open the audio connection">
    Send a `POST /offer` request to start the WebRTC handshake. Note that this path is at the **bare root** of the server — it is not under `/api/v1` — and it requires no API key.

    Send a JSON body with these fields:

    | Field          | Value                                       |
    | -------------- | ------------------------------------------- |
    | `sdp`          | Your browser's WebRTC SDP offer string      |
    | `type`         | Always `"offer"`                            |
    | `request_data` | Object with `agentId` and `conversation_id` |

    The response is a WebRTC SDP answer. Use it to complete the peer connection on the browser side.

    To exchange ICE candidates after the initial handshake, send them with `PATCH /offer`.

    ```javascript javascript theme={null}
    // Step 1: Generate a conversation ID
    const conversationId = crypto.randomUUID();

    // Step 2: Create a peer connection and generate an SDP offer
    const pc = new RTCPeerConnection();

    pc.addTransceiver("audio", { direction: "sendrecv" });

    await pc.setLocalDescription(await pc.createOffer());

    // Wait for ICE gathering to complete
    await new Promise((resolve) => {
      if (pc.iceGatheringState === "complete") {
        resolve();
      } else {
        pc.addEventListener("icegatheringstatechange", () => {
          if (pc.iceGatheringState === "complete") resolve();
        });
      }
    });

    // Step 3: Send the offer to FormBharo
    const offerResponse = await fetch(
      "{FORMBHARO_URL}/offer",
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          sdp: pc.localDescription.sdp,
          type: "offer",
          request_data: {
            agentId: "AGENT_ID",
            conversation_id: conversationId,
          },
        }),
      }
    );

    const answer = await offerResponse.json();

    // Step 4: Apply the SDP answer to complete the handshake
    await pc.setRemoteDescription(new RTCSessionDescription(answer));

    // Play incoming audio
    pc.addEventListener("track", (event) => {
      const audio = new Audio();
      audio.srcObject = event.streams[0];
      audio.play();
    });
    ```
  </Step>

  <Step title="Read answers as they arrive">
    Poll `GET /api/v1/data/{agent_id}/{conversation_id}` every two seconds or so to read answers as the agent collects them. This route requires no API key.

    * Before the first answer is written, the endpoint returns an empty object `{}`.
    * Once the call is underway, it returns a `form_data` object (keyed by question ID) and a `form_status` string.

    ```javascript javascript theme={null}
    async function pollAnswers(agentId, conversationId) {
      const url = `{FORMBHARO_URL}/api/v1/data/${agentId}/${conversationId}`;

      const interval = setInterval(async () => {
        const res = await fetch(url);
        const data = await res.json();

        if (data.form_status === "complete" || data.form_status === "screened_out") {
          clearInterval(interval);
        }

        console.log("Current answers:", data.form_data);
      }, 2000);
    }
    ```
  </Step>
</Steps>

<Warning>
  Anyone who knows the agent ID can read or overwrite live answers through the open routes. If your agents collect sensitive data, put your own authentication layer in front of these routes at the infrastructure level.
</Warning>

<Tip>
  See **Answers** for details on reading `form_data` and `form_status`, including why you should never rely on truthiness alone when checking answer values.
</Tip>
