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

# Rehearse a call against a persona

> Text-only: no call is placed, and it runs whatever prompt the assistant has saved right now, on the assistant's own LLM provider, model, and generation settings. Despite the `builds` path, it works on any assistant — built or hand-written.

Three inputs aim the simulation:

- `persona` — who answers. `behavior` goes verbatim into the caller model's system prompt; `path` defaults to `negative`, because the uncooperative arc is where prompts break.
- `context` — the scene: what the caller already knows about who is calling and why. Omit it to fall back to the build brief's context; with neither, the caller answers like a genuine cold call.
- `variables` — the customer record for this one call, same as `variables` on a real outbound call. Both sides see it: the agent substitutes its tokens, the caller answers consistently with it.

Anything wrong with the setup — an unsupported provider, a bad body — fails as a plain JSON 400 before any streaming starts. Seen enough mid-conversation? Just disconnect: the run stops server-side, and so does the spend.



## OpenAPI

````yaml /voice-agents/api-reference/openapi.yaml post /realtime-assistants/builds/{assistantId}/simulate
openapi: 3.1.0
info:
  title: UpliftAI Voice Agents API
  version: '1.0'
  description: >-
    Build voice assistants, start conversations on the web or the phone, run
    calling campaigns, and receive outcomes by webhook. All endpoints take a
    bearer API key unless noted otherwise.
servers:
  - url: https://api.upliftai.org/v1
security:
  - apiKey: []
tags:
  - name: Assistants
  - name: Authoring an assistant
  - name: Starting a conversation
  - name: Sessions & call records
  - name: Campaigns
  - name: Campaign outcomes
  - name: Contacts
  - name: Google Sheet sources
  - name: Integrations & delivery health
paths:
  /realtime-assistants/builds/{assistantId}/simulate:
    post:
      tags:
        - Authoring an assistant
      summary: Rehearse a call against a persona
      description: >-
        Text-only: no call is placed, and it runs whatever prompt the assistant
        has saved right now, on the assistant's own LLM provider, model, and
        generation settings. Despite the `builds` path, it works on any
        assistant — built or hand-written.


        Three inputs aim the simulation:


        - `persona` — who answers. `behavior` goes verbatim into the caller
        model's system prompt; `path` defaults to `negative`, because the
        uncooperative arc is where prompts break.

        - `context` — the scene: what the caller already knows about who is
        calling and why. Omit it to fall back to the build brief's context; with
        neither, the caller answers like a genuine cold call.

        - `variables` — the customer record for this one call, same as
        `variables` on a real outbound call. Both sides see it: the agent
        substitutes its tokens, the caller answers consistently with it.


        Anything wrong with the setup — an unsupported provider, a bad body —
        fails as a plain JSON 400 before any streaming starts. Seen enough
        mid-conversation? Just disconnect: the run stops server-side, and so
        does the spend.
      operationId: simulateAssistantBuild
      parameters:
        - name: assistantId
          in: path
          required: true
          description: The assistant to rehearse.
          schema:
            type: string
            format: uuid
          example: 452dda41-xxxx-xxxx-xxxx-xxxxxxxxxxxx
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SimulateRequest'
            example:
              persona:
                label: بے صبرا گاہک
                behavior: >-
                  Interrupts early, demands to know who is calling and where
                  they got the number, and hangs up if the answer is vague.
                path: negative
              context: >-
                Khwab Clinic, Lahore. You booked a dream-interpretation session
                last week and the clinic is calling back to walk you through the
                dream you submitted.
              variables:
                name: عائشہ صدیقی
                city: لاہور
                dream: پانی میں چلنے کا خواب
      responses:
        '200':
          description: >-
            An event stream, not a JSON body. It looks like this on the wire:


            ```text

            event: turn

            data: {"index":1,"role":"agent","text":"السلام علیکم، میں شفا کلینک
            سے ثناء بات کر رہی ہوں۔"}


            : ping


            event: turn

            data: {"index":2,"role":"caller","text":"جی، کون؟ آپ کو میرا نمبر
            کہاں سے ملا؟"}


            event: done

            data: {"personaId":"b8bed320-…","personaLabel":"بے صبرا
            گاہک","archetype":"happy","path":"negative","turns":[…]}

            ```


            - `turn` carries `{index, role, text}` — `index` is 1-based speaking
            order, `role` is `agent` or `caller`.

            - Exactly one terminal event ends the stream: `done` with the whole
            transcript, or `error` with a `{message}` — a conversation that
            broke mid-flight, or one that hit the five-minute ceiling.

            - Either side can end the conversation, and the caller often does —
            don't assume the transcript ends on the agent's closing line.

            - On `done`, `personaId` is minted for this run and `archetype` is
            always `happy`, whatever `path` you sent.

            - `: ping` keepalives arrive every 15 seconds; SSE parsers drop
            them.

            - A stream that ends with neither terminal event is a dropped
            connection — there is no resume, so run it again.
          content:
            text/event-stream:
              schema:
                type: string
              example: >
                event: turn

                data: {"index":1,"role":"agent","text":"السلام علیکم، میں خواب
                کلینک سے ڈاکٹر عامرہ بات کر رہی ہوں۔"}


                event: turn

                data: {"index":2,"role":"caller","text":"جی، کون؟ آپ کو میرا
                نمبر کہاں سے ملا؟"}


                event: done

                data:
                {"personaId":"b8bed320-xxxx-xxxx-xxxx-xxxxxxxxxxxx","personaLabel":"بے
                صبرا
                گاہک","archetype":"happy","path":"negative","turns":[{"speaker":"agent","text":"السلام
                علیکم، میں خواب کلینک سے ڈاکٹر عامرہ بات کر رہی
                ہوں۔"},{"speaker":"caller","text":"جی، کون؟ آپ کو میرا نمبر کہاں
                سے ملا؟"}]}
        '400':
          description: Validation failed, or the assistant's LLM cannot be simulated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                validation:
                  summary: The request body was rejected
                  value:
                    message:
                      - persona.behavior should not be empty
                      - >-
                        persona.path must be one of the following values: happy,
                        negative
                    error: Bad Request
                    statusCode: 400
                unsupportedProvider:
                  summary: The assistant's LLM provider is not supported
                  value:
                    message: Simulation does not support LLM provider 'anthropic'
                    error: Bad Request
                    statusCode: 400
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
components:
  schemas:
    SimulateRequest:
      type: object
      properties:
        persona:
          $ref: '#/components/schemas/SimulatePersona'
        context:
          type: string
          maxLength: 8000
          description: >-
            What the person picking up knows about who is ringing them and why.
            Overrides the `context` in the assistant's stored brief; with
            neither, they go in cold.
        variables:
          type: object
          additionalProperties:
            type: string
          description: >-
            The contact record for this one simulated call, handed to the agent
            under test as its caller record. Falls back to the first
            `sampleRows` entry of the stored brief. Keys are at most 64
            characters, and the whole object under 3000 characters of JSON.
      required:
        - persona
    Error:
      type: object
      properties:
        message:
          description: >-
            One message per failed field for validation errors, a single string
            otherwise.
          oneOf:
            - type: string
            - type: array
              items:
                type: string
        error:
          type: string
        statusCode:
          type: integer
    SimulatePersona:
      type: object
      description: >-
        Who picks up. Kept separate from `context` and `variables` so one
        persona can be re-run across scenarios and contacts.
      properties:
        label:
          type: string
          minLength: 1
          maxLength: 120
          description: Short name for the persona, echoed back on the transcript.
        behavior:
          type: string
          minLength: 1
          maxLength: 1000
          description: >-
            How they behave on the call, in plain language. Goes into the
            persona model's prompt verbatim, so write instructions rather than
            adjectives.
        path:
          type: string
          enum:
            - happy
            - negative
          default: negative
          description: >-
            How the conversation was meant to go, recorded on the transcript.
            `behavior` is what actually drives the caller.
      required:
        - label
        - behavior
  responses:
    Unauthorized:
      description: A bearer key was sent but is not recognized.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            message: invalid authorization
            error: Unauthorized
            statusCode: 401
    Forbidden:
      description: No API key sent, or the key lacks permission for this resource.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            message: Forbidden resource
            error: Forbidden
            statusCode: 403
    NotFound:
      description: No such resource in the API key's project.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            message: 'Realtime assistant not found: 00000000-0000-4000-8000-000000000000'
            error: Not Found
            statusCode: 404
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: Project API key (`sk_api_…`).

````