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

# Agora Simulations

> Test agents that live in an Agora RTC channel via WebRTC

export const CopyPageButton = () => {
  if (typeof window !== 'undefined') {
    setTimeout(function () {
      if (document.getElementById('ck-tools')) return;
      var anchor = document.getElementById('content-area') || document.querySelector('.mdx-content');
      if (!anchor) return;
      if (!document.getElementById('ck-style')) {
        var s = document.createElement('style');
        s.id = 'ck-style';
        s.textContent = '#ck-tools{position:absolute;top:6px;right:0;z-index:100;font-family:inherit;}' + '.ck-row{display:inline-flex;align-items:stretch;border:1px solid rgba(0,0,0,0.15);border-radius:8px;overflow:hidden;background:#fff;}' + ':root.dark .ck-row{background:rgba(255,255,255,0.06);border-color:rgba(255,255,255,0.12);}' + '.ck-btn{padding:5px 12px;border:none;background:none;cursor:pointer;font-size:13px;font-weight:500;font-family:inherit;color:#374151;}' + ':root.dark .ck-btn{color:#d1d5db;}' + '.ck-btn:hover{background:rgba(0,0,0,0.04);}' + ':root.dark .ck-btn:hover{background:rgba(255,255,255,0.06);}' + '.ck-chevron{padding:5px 8px;border:none;background:none;cursor:pointer;font-size:14px;font-family:inherit;color:#374151;}' + ':root.dark .ck-chevron{color:#d1d5db;}' + '.ck-chevron:hover{background:rgba(0,0,0,0.04);}' + ':root.dark .ck-chevron:hover{background:rgba(255,255,255,0.06);}' + '.ck-divider{width:1px;background:rgba(0,0,0,0.12);flex-shrink:0;}' + ':root.dark .ck-divider{background:rgba(255,255,255,0.12);}' + '.ck-dd{position:absolute;top:calc(100% + 4px);right:0;min-width:180px;background:#fff;border:1px solid rgba(0,0,0,0.12);border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.1);padding:4px;display:none;z-index:200;}' + ':root.dark .ck-dd{background:#1f2937;border-color:rgba(255,255,255,0.1);box-shadow:0 4px 16px rgba(0,0,0,0.35);}' + '.ck-item{display:block;width:100%;padding:7px 12px;border:none;background:none;border-radius:6px;cursor:pointer;font-size:13px;font-family:inherit;text-align:left;color:#374151;}' + ':root.dark .ck-item{color:#d1d5db;}' + '.ck-item:hover{background:rgba(0,0,0,0.05);}' + ':root.dark .ck-item:hover{background:rgba(255,255,255,0.07);}';
        document.head.appendChild(s);
      }
      var wrap = document.createElement('div');
      wrap.id = 'ck-tools';
      var row = document.createElement('div');
      row.className = 'ck-row';
      var mainBtn = document.createElement('button');
      mainBtn.className = 'ck-btn';
      mainBtn.textContent = 'Copy page';
      var divider = document.createElement('span');
      divider.className = 'ck-divider';
      var chevron = document.createElement('button');
      chevron.className = 'ck-chevron';
      chevron.textContent = '▾';
      var dd = document.createElement('div');
      dd.className = 'ck-dd';
      function closeDD() {
        dd.style.display = 'none';
      }
      function openDD() {
        dd.style.display = 'block';
      }
      chevron.onclick = function (e) {
        e.stopPropagation();
        if (dd.style.display === 'block') {
          closeDD();
        } else {
          openDD();
        }
      };
      document.addEventListener('click', function (e) {
        if (!e.target.closest('#ck-tools')) {
          closeDD();
        }
      });
      document.addEventListener('keydown', function (e) {
        if (e.key === 'Escape') {
          closeDD();
        }
      });
      function makeItem(label, fn) {
        var b = document.createElement('button');
        b.className = 'ck-item';
        b.textContent = label;
        b.onclick = function () {
          fn();
          closeDD();
        };
        return b;
      }
      function getMarkdown() {
        var walk = function (node) {
          if (!node) return '';
          if (node.nodeType === 3) return node.textContent || '';
          if (node.nodeType !== 1) return '';
          var tag = node.tagName.toLowerCase();
          var skip = ['script', 'style', 'svg', 'noscript', 'button', 'iframe'];
          if (skip.indexOf(tag) !== -1) return '';
          if (node.id === 'ck-tools') return '';
          var ch = Array.from(node.childNodes).map(walk).join('');
          if (tag === 'h1') return '\n# ' + ch.trim() + '\n\n';
          if (tag === 'h2') return '\n## ' + ch.trim() + '\n\n';
          if (tag === 'h3') return '\n### ' + ch.trim() + '\n\n';
          if (tag === 'p') return '\n' + ch.trim() + '\n\n';
          if (tag === 'pre') return '\n```\n' + node.textContent.trim() + '\n```\n\n';
          if (tag === 'li') return '- ' + ch.trim() + '\n';
          if (tag === 'code') return '`' + ch.trim() + '`';
          return ch;
        };
        var content = document.querySelector('.mdx-content') || document.getElementById('content-area') || document.body;
        return walk(content).replace(/\n\n\n+/g, '\n\n').trim();
      }
      function copyMd() {
        var md = getMarkdown();
        navigator.clipboard.writeText(md).then(function () {
          mainBtn.textContent = 'Copied!';
          setTimeout(function () {
            mainBtn.textContent = 'Copy page';
          }, 2000);
        });
      }
      function viewMd() {
        var md = getMarkdown();
        var safe = md.split('&').join('&amp;').split('<').join('&lt;').split('>').join('&gt;');
        var html = '<!DOCTYPE html><html><head><meta charset="utf-8"><style>body{font-family:monospace;max-width:860px;margin:40px auto;padding:0 24px;line-height:1.7;white-space:pre-wrap;word-wrap:break-word}</style></head><body>' + safe + '</body></html>';
        window.open(URL.createObjectURL(new Blob([html], {
          type: 'text/html'
        })), '_blank');
      }
      function openClaude() {
        var prompt = 'Can you read this Cekura docs page ' + window.location.href + ' so I can ask you questions?';
        window.open('https://claude.ai/new?q=' + encodeURIComponent(prompt), '_blank');
      }
      mainBtn.onclick = copyMd;
      dd.appendChild(makeItem('Copy page', copyMd));
      dd.appendChild(makeItem('View as Markdown', viewMd));
      dd.appendChild(makeItem('Open in Claude', openClaude));
      row.appendChild(mainBtn);
      row.appendChild(divider);
      row.appendChild(chevron);
      wrap.appendChild(row);
      wrap.appendChild(dd);
      anchor.style.position = 'relative';
      anchor.insertBefore(wrap, anchor.firstChild);
    }, 50);
  }
  return null;
};

<CopyPageButton />

## Overview

Cekura can simulate calls against any voice agent that runs inside an **Agora RTC channel**. Cekura joins the channel as a participant over WebRTC and exchanges 16 kHz mono PCM audio with your agent, then transcribes the conversation and runs your evaluators against it.

Unlike static credentials, the per-call join details (app id, channel, token, and optional encryption) are **minted at run time by an endpoint you configure** — they are fetched for each run and never stored by Cekura.

Use Agora Simulations when:

* Your agent joins an Agora channel and you mint short-lived join tokens per session.
* You want Cekura's tester to converse with the agent directly in the channel.

The page below has two parts:

* **How to test** — configure your agent and trigger runs from the dashboard or the API.
* **Session endpoint contract** — what your endpoint must return so Cekura can join.

<Tabs>
  <Tab title="How to test">
    <Tabs>
      <Tab title="Dashboard">
        <Steps>
          <Step title="Configure Agora credentials">
            Open your agent settings and pick **Agora** in the provider grid:

            <Frame>
              <img src="https://mintcdn.com/vocera/IJej3TqUFjZ3lsnI/images/agora/agora-select.png?fit=max&auto=format&n=IJej3TqUFjZ3lsnI&q=85&s=cc214b4ac09e33bcdbd0bcdbd38fd57f" alt="Select Agora in the provider grid" width="646" height="310" data-path="images/agora/agora-select.png" />
            </Frame>

            Then fill in the session-endpoint configuration:

            <Frame>
              <img src="https://mintcdn.com/vocera/IJej3TqUFjZ3lsnI/images/agora/agora-session.png?fit=max&auto=format&n=IJej3TqUFjZ3lsnI&q=85&s=3cade0cca25c2cb226122f6c7076c903" alt="Agora session endpoint configuration" width="638" height="590" data-path="images/agora/agora-session.png" />
            </Frame>

            **Required:**

            * **Session Endpoint URL** — the endpoint Cekura calls per run to mint the Agora channel + token (see **Session endpoint contract**).
            * **Client ID** — sent in the request body as `{"client_id": "..."}`.
            * **Secret** — sent in the auth header to your endpoint. Stored **encrypted**; write-only.

            **Optional:**

            * **Request Method** — `POST` (default) or `GET`. Cekura uses this verb when calling your session endpoint.
            * **Secret Header Name** — the header the secret is sent in. Defaults to `X-Cekura-Secret`.

            <Note>
              Your agent must be a live participant in the Agora channel the endpoint returns, exchanging raw `pcm_s16le` audio at **16 kHz mono**. See **Session endpoint contract** for the exact response shape.
            </Note>
          </Step>

          <Step title="Run tests from the dashboard">
            1. Select scenarios for your agent and click **Run**.
            2. With Agora configured, you will see a **WebRTC** option under **VOICE** in the **Configure Run** dialog.
            3. Select **WebRTC** and click **Run**.

            For each run Cekura will:

            * Call your session endpoint to fetch fresh join details (app id / channel / token / encryption).
            * Join the Agora channel as a participant and converse with your agent.
            * Capture the conversation, transcribe it, and run your evaluators against the result.
          </Step>

          <Step title="View results">
            Results appear in your dashboard like any other run — status, metrics, transcripts, and audio playback. The run type is shown as **Agora**.
          </Step>
        </Steps>
      </Tab>

      <Tab title="Code">
        Use the API to trigger Agora runs from your own code or CI.

        ## Prerequisites

        Configure Agora credentials on your agent first (same as the **Dashboard** tab): a **Session Endpoint URL**, **Client ID**, and **Secret**.

        ## API Endpoint

        ```
        POST https://api.cekura.ai/test_framework/v1/scenarios-external/run_scenarios_agora/
        ```

        ## Authentication

        Include your Cekura API key in the request header:

        ```
        X-CEKURA-API-KEY: <YOUR_API_KEY>
        ```

        ## Request Parameters

        **scenarios** (array | integer | string, required): Test scenarios to run

        * **Array format**: List of scenario IDs `[123, 456, 789]`
        * **Integer format**: Run the first N scenarios for the agent `5`
        * **String format**: Run all scenarios for the agent `"all"`

        **agent\_id** (integer, optional): Your Agent ID in Cekura. Required when using integer or `"all"` format for scenarios.

        **frequency** (integer, optional): Number of times to run each scenario (default: `1`).

        **name** (string, optional): Custom name for this test run.

        **concurrency\_limit** (integer, optional): Cap on parallel runs.

        ## Examples

        ### Single Run (cURL)

        ```bash theme={null}
        curl -X POST "https://api.cekura.ai/test_framework/v1/scenarios-external/run_scenarios_agora/" \
          -H "X-CEKURA-API-KEY: <YOUR_API_KEY>" \
          -H "Content-Type: application/json" \
          -d '{"agent_id": 12, "scenarios": [101], "frequency": 1}'
        ```

        ### Run All Scenarios (cURL)

        ```bash theme={null}
        curl -X POST "https://api.cekura.ai/test_framework/v1/scenarios-external/run_scenarios_agora/" \
          -H "X-CEKURA-API-KEY: <YOUR_API_KEY>" \
          -H "Content-Type: application/json" \
          -d '{"agent_id": 12, "scenarios": "all", "frequency": 1}'
        ```

        ### Python Example

        ```python theme={null}
        import requests

        API_KEY = "<YOUR_API_KEY>"
        BASE_URL = "https://api.cekura.ai/test_framework"

        headers = {
            "X-CEKURA-API-KEY": API_KEY,
            "Content-Type": "application/json",
        }

        payload = {
            "agent_id": 12,
            "scenarios": [101, 102],
            "frequency": 1,
            "name": "Agora Test Run",
        }

        resp = requests.post(
            f"{BASE_URL}/v1/scenarios-external/run_scenarios_agora/",
            headers=headers,
            json=payload,
        )
        print(resp.json())
        ```

        ## Response

        ```json theme={null}
        {
          "id": 16870,
          "name": "Agora Test Run",
          "agent": 12,
          "status": "in_progress",
          "success_rate": 0.0,
          "run_as_text": false,
          "is_cronjob": false,
          "runs": [
            {
              "id": 34625,
              "status": "running",
              "scenario": 101,
              "scenario_name": "Customer Support Scenario"
            }
          ],
          "created_at": "2026-06-03T09:32:59.484534Z",
          "updated_at": "2026-06-03T09:32:59.484942Z"
        }
        ```

        ## Error Responses

        ### Missing Agora credentials

        ```json theme={null}
        {
          "agent": ["Agent must have Agora credentials configured (agora_data with session_endpoint_url)."]
        }
        ```

        ### Missing session endpoint URL

        ```json theme={null}
        {
          "agent": ["Agent agora_data must include session_endpoint_url."]
        }
        ```

        ### Invalid scenarios

        ```json theme={null}
        {
          "scenarios": ["Invalid scenario IDs or scenarios not found"]
        }
        ```

        ### Insufficient balance

        ```json theme={null}
        {
          "detail": "Insufficient balance for this operation"
        }
        ```

        ## Monitor Results

        Poll for results using the [List Runs with IDs API](/api-reference/test_framework/list-runs-with-ids).
      </Tab>
    </Tabs>
  </Tab>

  <Tab title="Session endpoint contract">
    For every run, Cekura calls your configured **Session Endpoint URL** to obtain fresh Agora join details. This keeps tokens short-lived and lets you start the agent in the channel on demand.

    ### Request

    Cekura sends (method configurable, `POST` by default):

    ```
    POST <session_endpoint_url>
    X-Cekura-Secret: <your secret>            # header name configurable
    Content-Type: application/json

    { "client_id": "<your client id>" }
    ```

    For `GET`, `client_id` is sent as a query parameter and the secret header is unchanged.

    ### Response

    Return JSON with the join details. Cekura joins the channel as uid `1` (override with `join_uid` in the agent's Agora config); your token must authorize that uid.

    | Field             | Required | Description                                          |
    | ----------------- | -------- | ---------------------------------------------------- |
    | `agora_app_id`    | yes      | Agora App ID for the channel                         |
    | `agora_channel`   | yes      | Channel name to join                                 |
    | `agora_token`     | yes      | RTC token valid for the channel + join uid           |
    | `encryption_mode` | no       | Channel encryption mode name, e.g. `aes_256_gcm2`    |
    | `encryption_key`  | no       | Encryption key (when encryption is enabled)          |
    | `encryption_salt` | no       | Base64-encoded KDF salt (when encryption is enabled) |

    ```json theme={null}
    {
      "agora_app_id": "5bfbb040c5e34dd6917e05060bc05b97",
      "agora_channel": "your-channel-name",
      "agora_token": "007eJxTY...",
      "encryption_mode": "aes_256_gcm2",
      "encryption_key": "<base64 key>",
      "encryption_salt": "<base64 salt>"
    }
    ```

    <Note>
      Encryption is optional. Omit the `encryption_*` fields for an unencrypted channel. When present, `encryption_mode` must be a recognized Agora mode name and `encryption_salt` must be base64-encoded.
    </Note>
  </Tab>
</Tabs>
