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

# Mock Tool Calls: Setup

> Set up mock tool calls for your agent's platform.

Find your agent's platform below to see what Bluejay mocks for you and what your tool server must do. Pick each tool's mode first, as shown in [Mock tool calls](https://docs.getbluejay.ai/test/simulations/mock-tools.md).

| Platform | Web or chat simulations | Phone or SIP simulations |
| - | - | - |
| ElevenLabs | Native | Your tool server asks Bluejay |
| Google CES, Dialogflow CX | Native for tools Bluejay runs | Not supported natively |
| Retell, Vapi | Your tool server asks Bluejay | Your tool server asks Bluejay |
| Bland | Not supported natively | Your tool server asks Bluejay |
| LiveKit, Pipecat, your own code | Bluejay SDK helper | Bluejay SDK helper |

"Native" means you only turn on **Mock tool calls**. "Your tool server asks Bluejay" means your tool handler calls the [resolver](#retell-vapi-and-other-platforms) before it runs the real tool.

## Turn on mocking

Do this for every platform. Open the agent's **Simulation Settings** tab and turn on **Mock tool calls**. It applies to every simulation on the agent, and each tool starts on **Generated**.

With the API, call [`PUT /v1/agents/{agent_id}/mock-tools`](https://docs.getbluejay.ai/test/simulations/mock-tools-reference#turn-mocking-on-or-off). With MCP or Bluejay AI, ask to mock tool calls for the agent.

## ElevenLabs

Bluejay mocks ElevenLabs agents natively on web simulations. Your agent needs versioning turned on in ElevenLabs.

<video controls muted playsInline preload="metadata" src="https://mintcdn.com/bluejay/8i7dAIenLlFEYFAM/videos/mock-tools-elevenlabs-toggle.mp4?fit=max&auto=format&n=8i7dAIenLlFEYFAM&q=85&s=0ab5d8e9cf3aa1c45b881e219585464f" />

When you turn mocking on, Bluejay creates a branch named `bluejay-mocks` on the agent in your ElevenLabs workspace.

* The branch is a copy of your live version. It has a client tool in place of each webhook tool, with the same name and schema. Tools set to **Real backend** stay webhooks and call your server.
* The branch takes 0% of traffic. Live callers and your main version are never touched.
* Bluejay reuses the branch for every run. When you publish a new live version, Bluejay rebuilds the branch the next time you queue a run. Changing a tool to or from **Real backend** rebuilds it right away.
* Don't edit the branch. Bluejay overwrites it.

In a simulation, Bluejay connects to the branch, and each tool call comes back to Bluejay instead of your server.

* Each tool gets the Digital Human's override, the Fixed response, or a generated result, in that order.
* If Bluejay can't produce a result, the tool gets an error, "No mock result is available for this tool in this test." Your real tool never runs, including calls the agent makes early or drops after an interruption.

<video controls muted playsInline preload="metadata" src="https://mintcdn.com/bluejay/8i7dAIenLlFEYFAM/videos/mock-tools-elevenlabs-output.mp4?fit=max&auto=format&n=8i7dAIenLlFEYFAM&q=85&s=9d8f656f6998facb063b7078b57d2f0d" />

<video controls muted playsInline preload="metadata" src="https://mintcdn.com/bluejay/8i7dAIenLlFEYFAM/videos/mock-tools-elevenlabs-run.mp4?fit=max&auto=format&n=8i7dAIenLlFEYFAM&q=85&s=2db59ed53a0a516079a6ce6630a0a8b7" />

To turn mocking off, turn off **Mock tool calls**. Bluejay archives the branch and deletes its tool copies. Turning it on again creates a new branch.

ElevenLabs phone and SIP agents use the [resolver](#retell-vapi-and-other-platforms), because ElevenLabs can't route a phone call to a branch. Bluejay sends `X-Simulation-Result-Id` on every SIP call. Pass it to your tool webhook as a dynamic variable.

## Google CES and Dialogflow CX

When Google hands a tool call to Bluejay during a simulation, Bluejay answers it. This works for voice and chat. Turn on mocking. Nothing else is needed.

* A tool set to **Real backend** gets an error result, since nothing can run your real tool on this path.
* A result that is not a JSON object arrives as `{"result": <value>}`.
* Tools that Google runs itself, such as OpenAPI tools, still call your real system. Bluejay can't mock them yet.

## Retell, Vapi and other platforms

Retell and Vapi are not yet supported natively. Instead, your tool server asks Bluejay for the mock before it runs the real tool.

1. Read the Bluejay test id from the call.
2. Call `POST /v1/tool-mocks/resolve` with your Bluejay API key.
3. If `mocked` is true, return `result` and skip the real tool. On `mocked: false`, run the real tool.

Where to find the test id:

| How Bluejay reaches your agent | Test id |
| - | - |
| Retell or Vapi web call | The call's metadata, metadata.test\_result\_id. Retell and Vapi include the call in tool requests. |
| SIP | The `X-Simulation-Result-Id` header. |
| Phone number | No id. Send the Digital Human's phone number as `caller_number`. |

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
curl -X POST https://api.getbluejay.ai/v1/tool-mocks/resolve \
  -H "X-API-Key: $BLUEJAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tool_name": "check_availability", "arguments": {"day": "Thursday"}, "test_result_id": 125145}'
```

```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
{"mocked": true, "result": {"slots": ["Thursday 7:40 AM", "Thursday 4:05 PM"]}}
```

A Retell custom function with FastAPI:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import os, httpx
from fastapi import FastAPI

app = FastAPI()

@app.post("/check_availability")
def check_availability(body: dict):
    test_id = (body["call"].get("metadata") or {}).get("test_result_id")
    if test_id:
        try:
            r = httpx.post("https://api.getbluejay.ai/v1/tool-mocks/resolve", timeout=10,
                headers={"X-API-Key": os.environ["BLUEJAY_API_KEY"]},
                json={"tool_name": "check_availability", "arguments": body["args"], "test_result_id": int(test_id)})
            r.raise_for_status()
            data = r.json()
        except Exception:
            return {"error": "This tool is unavailable in this test."}
        if data.get("mocked"):
            return data["result"]
    return my_calendar.slots(**body["args"])
```

* Only call Bluejay when the call has a test id or comes from a Bluejay number. Real callers then never wait on Bluejay.
* On `mocked: false`, run the real tool. When the call has a test id and Bluejay errors or times out, return an error result instead, so a test never reaches your real backend. Generated results can take a few seconds, so wait up to 10.
* When Bluejay can't produce a result for a test, it answers `mocked: true` with an error result.
* A tool set to **Real backend** answers `mocked: false`, so your real tool runs. Every other tool is mocked, generated unless you set it to **Fixed**.

## Custom agents (LiveKit, Pipecat)

When your tools run in your own code, wrap each tool with the Bluejay SDK helper. During a Bluejay simulation it returns the mocked result. On any other call it runs your real tool.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
pip install bluejay-sdk
export BLUEJAY_API_KEY=<your Bluejay API key>
```

### LiveKit

Add `@mockable` under `@function_tool`, and call `bind_livekit(ctx)` at the start of your entrypoint.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from bluejay.helpers.tool_mocks import bind_livekit, mockable

class Scheduler(Agent):
    @function_tool
    @mockable
    async def check_availability(self, day: str) -> str:
        return await my_calendar.slots(day)

async def entrypoint(ctx: JobContext):
    bind_livekit(ctx)
    ...
```

The mock is looked up by the function name. If your tool has a different name in Bluejay, use `@mockable(name="check_availability")`.

### Pipecat

Bind the call when the bot starts, then wrap the handler body with `mock_or_call`.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from bluejay.helpers.tool_mocks import bind, mock_or_call

async def bot(args):
    bind(test_result_id=args.body.get("X-Simulation-Result-Id"))
    ...

async def check_availability(params):
    result = await mock_or_call("check_availability", params.arguments, lambda: my_calendar.slots(**params.arguments))
    await params.result_callback(result)
```

### How the helper finds the call

* `bind_livekit` reads `X-Simulation-Result-Id` from the dispatch metadata, then from participant attributes. For SIP calls into LiveKit, map the header with `headers_to_attributes` on your inbound trunk.
* On plain phone calls the header is dropped, so `bind_livekit` falls back to the caller's number, `sip.phoneNumber`.
* In any other server, call `bind(test_result_id=...)` or `bind(caller_number=...)` yourself.

A mocked result is returned as a string. Objects are JSON-encoded. Timeouts and the environment variables are in the [reference](https://docs.getbluejay.ai/test/simulations/mock-tools-reference.md#sdk-helper).

## Phone

No platform routes a Bluejay caller to a test version on its own, so phone tests always need your tool server to ask Bluejay. Use the [resolver](#retell-vapi-and-other-platforms) or the [SDK helper](#custom-agents-livekit-pipecat).

* SIP calls carry `X-Simulation-Result-Id`. Send it as `test_result_id`.
* Phone calls drop SIP headers. Send the Digital Human's phone number as `caller_number`, in E.164 form. Bluejay matches it to the Digital Human calling from that number right now.
* When your agent calls the Digital Human, the Digital Human's number is the number your agent dialed.


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