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

# Get Run

> Returns the full run, with `case` (and nested `agent`+`persona`) expanded inline. Use the dedicated sub-routes for turns and evaluation.

Returns the full run row with `case` (and the nested `agent` + `persona`) expanded inline. For the transcript see `runs/:id/turns`; for just the score see `runs/:id/evaluation`.


## OpenAPI

````yaml GET /agent-eval/runs/{id}
openapi: 3.1.0
info:
  title: Yappr API
  description: >
    Create and manage AI voice agents, purchase phone numbers, configure tools,
    and initiate calls — all via REST.
  version: 1.0.0
  contact:
    url: https://goyappr.com
servers:
  - url: https://api.goyappr.com
    description: Production
security:
  - apiKey: []
paths:
  /agent-eval/runs/{id}:
    parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
          format: uuid
    get:
      tags:
        - Agent Eval
      summary: Get run
      description: >-
        Returns the full run, with `case` (and nested `agent`+`persona`)
        expanded inline. Use the dedicated sub-routes for turns and evaluation.
      operationId: getRun
      responses:
        '200':
          description: Run
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalRun'
        '404':
          description: Not found
components:
  schemas:
    EvalRun:
      type: object
      description: >-
        One execution of a case. Append-only after creation except for
        status/billing/lifecycle fields.
      required:
        - id
        - company_id
        - status
        - mode
        - created_at
      properties:
        id:
          type: string
          format: uuid
        company_id:
          type: string
          format: uuid
        case_id:
          type: string
          format: uuid
          nullable: true
          description: >-
            FK; nullable because cases can be deleted while runs are kept as
            history.
        case:
          $ref: '#/components/schemas/EvalCase'
        suite_id:
          type: string
          format: uuid
          nullable: true
        suite_run_id:
          type: string
          format: uuid
          nullable: true
          description: >
            Groups runs spawned by a single `POST /agent-eval/suites/{id}/run`
            call.

            Pass to `GET /agent-eval/suites/{id}/runs/{suite_run_id}` for the
            aggregate.
        status:
          type: string
          enum:
            - queued
            - running
            - completed
            - failed
            - cancelled
          description: >
            User-facing run status. Flips to `completed`/`failed` as soon as the
            conversation ends — BEFORE the worker scores assertions and bills.
            **Do not treat `score` / `pass_fail` / `total_cost_cents` as final
            until `queue_status === "done"`.**
        queue_status:
          type: string
          enum:
            - pending
            - claimed
            - done
          description: >
            Internal worker pipeline state. `pending` = waiting in queue,
            `claimed` = the cron worker has dispatched it to pipecat, `done` =
            the worker has finished scoring + billing. Always poll for
            `queue_status === "done"` before reading the scoring / billing
            fields. The transient window between `status === "completed"` and
            `queue_status === "done"` is typically &lt; 5 seconds but can be
            longer under contention.
        mode:
          type: string
          enum:
            - text
            - voice
          default: text
          description: >-
            Always `text` in v1. `voice` is reserved for the future loopback
            mode.
        agent_id:
          type: string
          format: uuid
          nullable: true
          description: >-
            Denormalized snapshot — same as `case.agent_id` at the moment the
            run was created.
        persona_id:
          type: string
          format: uuid
          nullable: true
        agent_model:
          type: string
          nullable: true
          description: Model identifier the agent ran on. Surfaced for cost auditing.
        persona_model:
          type: string
          nullable: true
        started_at:
          type: string
          format: date-time
          nullable: true
        ended_at:
          type: string
          format: date-time
          nullable: true
        duration_ms:
          type: integer
          nullable: true
        score:
          type: number
          nullable: true
          minimum: 0
          maximum: 100
        pass_fail:
          type: boolean
          nullable: true
        termination_reason:
          type: string
          nullable: true
          enum:
            - persona_goodbye
            - agent_ended
            - max_turns
            - timeout
            - error
            - cancelled
          description: Why the run stopped.
        evaluation:
          $ref: '#/components/schemas/Evaluation'
          description: Populated when status is `completed` or `failed`.
        agent_input_tokens:
          type: integer
          default: 0
        agent_output_tokens:
          type: integer
          default: 0
        persona_input_tokens:
          type: integer
          default: 0
        persona_output_tokens:
          type: integer
          default: 0
        agent_cost_cents:
          type: integer
          default: 0
        persona_cost_cents:
          type: integer
          default: 0
        total_cost_cents:
          type: integer
          default: 0
          description: Total amount debited from the company's credit balance for this run.
        error:
          type: string
          nullable: true
          description: Populated when `status='failed'`.
        agent_overrides:
          type: object
          additionalProperties: true
          nullable: true
          description: >-
            Snapshot of the case's `agent_overrides` plus any per-run overrides
            supplied at create time.
        created_at:
          type: string
          format: date-time
    EvalCase:
      type: object
      description: >-
        A specific eval scenario — persona + target agent + scenario + success
        criteria.
      required:
        - id
        - company_id
        - agent_id
        - persona_id
        - name
        - scenario
        - success_criteria
        - max_turns
        - pass_threshold
        - tool_policy
        - created_at
      properties:
        id:
          type: string
          format: uuid
        company_id:
          type: string
          format: uuid
        agent_id:
          type: string
          format: uuid
          description: >-
            Agent under test. Full agent record is expanded inline as `agent` in
            API responses.
        agent:
          $ref: '#/components/schemas/Agent'
        persona_id:
          type: string
          format: uuid
        persona:
          $ref: '#/components/schemas/EvalPersona'
        suite_id:
          type: string
          format: uuid
          nullable: true
          description: >-
            Optional parent suite. When null, the case is ad-hoc — runnable on
            its own but not part of a regression sweep.
        name:
          type: string
          example: Yes path — caller agrees on first ask
        description:
          type: string
          nullable: true
        scenario:
          type: string
          description: >
            Free-form one-paragraph framing the persona LLM is given on top of
            its identity. Describe the situation that prompted the call.
          example: >-
            The persona is responding to a missed call from your business about
            their recent inquiry. They have time to talk for 5 minutes.
        success_criteria:
          type: array
          items:
            $ref: '#/components/schemas/Assertion'
          description: Array of assertions evaluated after the run completes.
        max_turns:
          type: integer
          minimum: 1
          maximum: 100
          default: 20
          description: >-
            Hard cap on conversation turns. Hitting this terminates the run with
            `termination_reason='max_turns'`.
        pass_threshold:
          type: number
          minimum: 0
          maximum: 100
          default: 80
          description: Weighted-score threshold (0-100) for `pass_fail=true`.
        agent_overrides:
          type: object
          additionalProperties: true
          nullable: true
          description: >
            Optional per-case overrides applied to the agent's saved config at
            run time (e.g. a different `system_prompt` or `flow_config` for A/B
            testing). Same shape as the agent record. The agent on disk is never
            mutated.
        tool_policy:
          type: string
          enum:
            - mock
            - real
            - allowlist
          default: mock
          description: >
            How the agent's tools behave during the run. `mock` (default): every
            tool call returns a synthetic success result the worker fabricates
            from the tool's declared output schema. `real`: tools fire for real
            (charges real money, hits real systems). `allowlist`: tools whose
            name appears in `tool_allowlist` fire for real, the rest return mock
            results.
        tool_allowlist:
          type: array
          items:
            type: string
          default: []
          description: >-
            Used only when `tool_policy='allowlist'`. List of tool names
            (camelCase) that should fire for real.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        deleted_at:
          type: string
          format: date-time
          nullable: true
    Evaluation:
      type: object
      description: >-
        Roll-up of assertion results that's also stored on
        `eval_runs.evaluation`.
      required:
        - score
        - pass_fail
        - results
      properties:
        score:
          type: number
          minimum: 0
          maximum: 100
          description: Weighted percentage of assertions that passed.
        pass_fail:
          type: boolean
          description: True iff `score >= case.pass_threshold`.
        results:
          type: array
          items:
            $ref: '#/components/schemas/AssertionResult'
    Agent:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        type:
          type: string
          enum:
            - prompt
            - flow
          default: prompt
          description: >
            `prompt` (default, legacy): a single `system_prompt` drives the
            call; the LLM decides when to call attached tools. `flow`: the call
            is driven by a `flow_config` graph of conversation, tool-call, and
            routing nodes. `type` is **immutable** post-create — to change
            types, create a new agent.
        flow_config:
          oneOf:
            - $ref: '#/components/schemas/FlowConfig'
            - type: 'null'
          description: >
            The flow graph. Required when `type='flow'`, must be omitted/null
            when `type='prompt'`.
        system_prompt:
          type: string
        description:
          type: string
          nullable: true
        voice:
          type: string
          enum:
            - Michal
            - Yonatan
            - David
            - Rachel
            - Gil
            - Noa
            - Maya
            - Adam
            - Shira
            - Avigail
            - Amir
            - Liat
            - Omer
            - Tamar
            - Tom
            - Benny
            - Nir
            - Natan
            - Yael
            - Dvora
            - Yosef
            - Shir
            - Anat
            - Ariel
            - Roi
            - Shlomo
            - Dana
            - Alon
            - Ruth
            - Yuval
        background_sound:
          type: string
          nullable: true
          enum:
            - call_center
            - open_office
            - cafe
            - outdoor
          description: >-
            Ambient background sound mixed under the agent's voice during calls.
            Null means silent (default).
        background_sound_volume:
          type: number
          minimum: 0
          maximum: 0.6
          default: 0.3
          description: >-
            Volume of the background sound, 0.0–0.6. Capped to protect
            turn-taking.
        language:
          type: string
          enum:
            - he
            - en
        temperature:
          type: number
          minimum: 0
          maximum: 2
        greeting_message:
          type: string
          nullable: true
        agent_speaks_first:
          type: boolean
        vad_stop_secs:
          type: number
          minimum: 0.05
          maximum: 5
          default: 0.5
          description: Seconds of silence before VAD confirms speech has stopped
        vad_start_secs:
          type: number
          minimum: 0.05
          maximum: 2
          default: 0.2
          description: Seconds of speech before VAD confirms speech has started
        vad_confidence:
          type: number
          minimum: 0
          maximum: 1
          default: 0.7
          description: Minimum confidence threshold for voice detection
        silence_timeout_secs:
          type: number
          minimum: 10
          maximum: 900
          default: 60
          description: >-
            Seconds of caller silence before auto-hangup. Prevents idle calls
            wasting credits.
        max_continuous_speech_secs:
          type: number
          minimum: 0
          maximum: 300
          default: 120
          description: >-
            Max seconds one party can speak non-stop before auto-hangup. Catches
            answering machines. 0 = disabled.
        max_call_duration_secs:
          type: number
          minimum: 0
          maximum: 3600
          default: 600
          description: >-
            Hard cap on total call duration regardless of activity. 0 =
            disabled.
        lead_memory_enabled:
          type: boolean
          default: true
          description: >-
            When true, the matched lead's long-term memory context is injected
            into the system prompt at call time.
        is_active:
          type: boolean
        webhook_url:
          type: string
          format: uri
          nullable: true
        webhook_events:
          type: array
          items:
            type: string
            enum:
              - call.started
              - call.answered
              - call.ended
              - call.failed
              - call.no_answer
              - call.dnc_blocked
              - transcript.ready
              - call.analyzed
        extraction_parameters:
          type: array
          description: >-
            AI extraction parameters — after each call, values are extracted
            from the transcript and included in the call.analyzed webhook
            payload (and stored on the call log).
          items:
            type: object
            required:
              - name
              - description
            properties:
              name:
                type: string
                example: customerName
                description: Parameter key in the extracted_data output
              description:
                type: string
                example: The caller's full name as mentioned during the conversation
                description: Instructions for the AI on what to extract
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    EvalPersona:
      type: object
      description: >
        Reusable caller archetype consumed by eval cases. The `identity_prompt`
        plus `behavior_traits` shape how the persona LLM responds; the same
        persona can be reused across many cases.
      required:
        - id
        - company_id
        - name
        - identity_prompt
        - language
        - created_at
      properties:
        id:
          type: string
          format: uuid
        company_id:
          type: string
          format: uuid
        name:
          type: string
          example: Frustrated tenant
        description:
          type: string
          nullable: true
        identity_prompt:
          type: string
          description: >-
            System prompt fragment defining who the persona is. Written
            second-person ("You are…").
          example: >-
            You are a 38-year-old tenant calling about a leaking pipe in your
            kitchen. You're frustrated because this is the third time you've
            reported it.
        behavior_traits:
          type: object
          additionalProperties: true
          description: >
            Free-form JSON. Common keys: `patience` (low|medium|high),
            `verbosity` (terse|chatty), `cooperation` (cooperative|adversarial),
            `interruption_tendency` (none|occasional|frequent), `accent` /
            `dialect` hints, `goal` (what the persona is trying to achieve in
            the call).
          example:
            patience: low
            verbosity: chatty
            cooperation: cooperative
            interruption_tendency: occasional
            goal: Get a maintenance technician scheduled today
        language:
          type: string
          enum:
            - he
            - en
          default: en
        voice_config:
          type: object
          additionalProperties: true
          description: >-
            Forward-compat for v2 voice-mode (TTS/STT loopback). Ignored in text
            mode.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        deleted_at:
          type: string
          format: date-time
          nullable: true
    Assertion:
      type: object
      description: >
        A single success criterion attached to an eval case. Discriminated by
        `kind`. Each assertion has a `weight` (defaults to 1.0) used in the
        weighted score calculation: `score = sum(weight * passed?1:0) /
        sum(weight) * 100`. Cases pass when `score >= pass_threshold`.
      required:
        - type
      properties:
        type:
          type: string
          enum:
            - must_say
            - must_not_say
            - must_call_tool
            - must_reach_node
            - custom_llm_judge
          description: >
            Assertion kind. Each kind reads a different combination of the
            sibling fields below.
        weight:
          type: number
          default: 1
          minimum: 0
          description: Relative weight in the case score.
        description:
          type: string
          nullable: true
          description: >-
            Optional human-readable note shown in the dashboard alongside this
            assertion.
        pattern:
          type: string
          nullable: true
          description: >
            For `must_say` / `must_not_say`: a regex evaluated against the full
            agent transcript (case-insensitive). Plain substrings work too —
            they're valid regex. For literal punctuation, escape with
            backslashes (e.g. `goodbye\\?`).
        tool_name:
          type: string
          nullable: true
          description: >-
            `must_call_tool` only: name of a tool the agent must invoke at least
            once during the run.
        args_match:
          type: object
          additionalProperties: true
          nullable: true
          description: >
            `must_call_tool` only: optional shape the tool's arguments must
            match. Each key must equal the value. The sentinel `"$present"`
            checks the key exists with a non-null value.
        node_id:
          type: string
          nullable: true
          description: >-
            `must_reach_node` only: flow node id the run must enter. Only
            meaningful for flow agents — silently fails for prompt-mode agents.
        rubric:
          type: string
          nullable: true
          description: >-
            `custom_llm_judge` only: natural-language rubric the judge LLM
            scores the run against. (Engine stub in v1 — returns `passed=false,
            reason='not_implemented'`; full LLM-as-judge ships in v1.1.)
    AssertionResult:
      type: object
      description: One assertion's outcome after the run completed.
      required:
        - assertion
        - passed
        - weight
      properties:
        assertion:
          $ref: '#/components/schemas/Assertion'
        passed:
          type: boolean
        weight:
          type: number
        reason:
          type: string
          nullable: true
          description: >-
            Short human-readable explanation. For LLM judges this is the judge's
            verdict.
    FlowConfig:
      type: object
      required:
        - nodes
      description: >
        The full flow definition stored in `agents.flow_config`. Constraints
        enforced at validate-time

        (any failure returns 400 `FLOW_INVALID` with one issue per problem — see
        `FlowInvalidError`):


        - Exactly one node of `type=start` (`no_start` / `multiple_starts`).

        - Start node has a `next_step_id` (`start_unwired`).

        - All `next_step_id` references resolve to a real node id
        (`unknown_target_node`).

        - Node ids are unique.

        - Every conversation node has non-empty `instructions`
        (`instructions_missing`).

        - Every conversation transition has a non-empty `description`
        (`transition_description_missing`).
          The description is the natural-language trigger the voice agent reads at runtime to pick a path —
          a bare `label` is not sufficient.
        - Every tool_call node has a `tool_id` (`tool_id_missing`).

        - At most 127 unique typed extraction contracts may be exposed in one
        flow
          (`too_many_extraction_contracts`). Nodes with identical effective tool/config schemas
          deduplicate; integration arguments participate only in `ai_extract` mode.
        - Every integration_call node has a valid `action` for its `provider`
        (`action_invalid`),
          an `integration_id` (`integration_id_missing`), and that integration belongs to the caller's
          company and is `active` (`integration_not_in_company`). Provider validity is enforced at
          schema parse time (`schema_invalid` if missing/unknown).
        - tool_call / integration_call nodes have a wired `success_next_step_id`
        (`success_not_wired`).

        - transfer nodes have a `transfer_to` (`transfer_to_missing`).

        - **Only `end` and `transfer` nodes may be terminal.** conversation,
        tool_call, and integration_call
          nodes must have at least one outgoing edge (a transition for conversation; `success` for the others).
          Violations: `terminal_not_allowed` per offending node, `no_terminal` if the flow has none at all.
        - Every node must be reachable from start (`unreachable_node`).
      properties:
        nodes:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/FlowNode'
        flow_config_version:
          type: string
          default: '1'
        metadata:
          type: object
          additionalProperties: true
          description: >-
            Optional metadata surfaced to the eval LLM context (e.g. agent
            purpose, customer name conventions).
          properties:
            custom_metadata_keys:
              type: array
              items:
                type: string
              description: >
                User-declared keys that this flow's `args_template` token
                interpolation expects to

                find under the `{{metadata.<key>}}` namespace at dispatch time.
                Surfaced in the

                dashboard so callers know which keys to pass in `POST /calls
                body.metadata`.

                Keys not listed here still resolve at runtime if passed (and to
                empty string if

                not) — this list is purely a hint for the UI / API consumers.
    FlowNode:
      description: Discriminated by `type`.
      oneOf:
        - $ref: '#/components/schemas/StartNode'
        - $ref: '#/components/schemas/ConversationNode'
        - $ref: '#/components/schemas/ToolCallNode'
        - $ref: '#/components/schemas/IntegrationCallNode'
        - $ref: '#/components/schemas/TransferNode'
        - $ref: '#/components/schemas/EndNode'
      discriminator:
        propertyName: type
        mapping:
          start:
            $ref: '#/components/schemas/StartNode'
          conversation:
            $ref: '#/components/schemas/ConversationNode'
          tool_call:
            $ref: '#/components/schemas/ToolCallNode'
          integration_call:
            $ref: '#/components/schemas/IntegrationCallNode'
          transfer:
            $ref: '#/components/schemas/TransferNode'
          end:
            $ref: '#/components/schemas/EndNode'
    StartNode:
      type: object
      required:
        - id
        - type
      properties:
        id:
          type: string
        type:
          type: string
          enum:
            - start
        name:
          type: string
        position:
          $ref: '#/components/schemas/FlowNodePosition'
        agent_speaks_first:
          type: boolean
          default: true
          description: >
            Whether the bot speaks first on call connect. When false, the bot
            waits silently for the caller to speak first (silence_timeout_secs
            on the agent still applies). For flow agents this OVERRIDES the
            agent-level agent.agent_speaks_first / agent.greeting_message
            fields.
        greeting:
          type: string
          nullable: true
          description: Spoken greeting (only used when agent_speaks_first is true)
        is_literal:
          type: boolean
          default: false
          description: >-
            When true, speak `greeting` verbatim. When false, treat it as an LLM
            instruction.
        next_step_id:
          type: string
          nullable: true
        auto_advance:
          type: boolean
          default: true
          description: >
            Whether to enter the first conversation node immediately on session
            start (default `true`). When `false`, the bot's greeting is
            delivered in start-node context only; the first conversation node is
            entered only after the user's first reply (via internal
            advancement).
    ConversationNode:
      type: object
      required:
        - id
        - type
        - instructions
        - transitions
      allOf:
        - $ref: '#/components/schemas/GlobalNodeFields'
      properties:
        id:
          type: string
        type:
          type: string
          enum:
            - conversation
        name:
          type: string
        position:
          $ref: '#/components/schemas/FlowNodePosition'
        instructions:
          type: string
          description: >-
            Layered on top of agent.system_prompt as a system message at step
            entry
        transitions:
          type: array
          items:
            $ref: '#/components/schemas/Transition'
    ToolCallNode:
      type: object
      required:
        - id
        - type
        - transitions
      description: >
        Deterministic tool dispatch. Tool args are owned by the tool itself

        (`payload_config.static_parameters` for literals plus

        `payload_config.extraction_parameters` for runtime-extracted values) —

        a `tool_call` node carries no per-node `args_template`. The same tool

        used by N flow nodes always sends the same shape; if you need a

        different shape per step, create a separate tool or supply a

        `config_override`. At call start, the effective referenced-tool config
        is

        registered as a flat argument-submission schema: one named string field

        per `extraction_parameters` entry, with `required` (default `true`)

        controlling which fields must be collected. The model never submits a

        `node_id` or nested `args` object. The runtime assembles standard call

        metadata and static parameters; the submitter exposes only extraction

        fields. Payload merge order is standard metadata, then static
        parameters,

        then extracted values, so extracted values win deliberate name

        collisions. Keep names unique unless that override is intentional.

        Schemas remain fixed for that live call; tool/config-override edits
        apply

        on the next call. Stale

        `args_template` payloads on this node type are silently dropped at parse

        time.
      properties:
        id:
          type: string
        type:
          type: string
          enum:
            - tool_call
        name:
          type: string
        position:
          $ref: '#/components/schemas/FlowNodePosition'
        tool_id:
          type: string
          format: uuid
          nullable: true
          description: References an existing row in the company's tools table
        config_override:
          type: object
          additionalProperties: true
          nullable: true
          description: >-
            Per-node override merged shallow over the referenced tool's config
            (array replacement). Open-shape — each tool type has its own valid
            keys.
        transitions:
          $ref: '#/components/schemas/ToolCallTransitions'
        pre_fire_announcement:
          type: boolean
          nullable: true
          description: >
            When true, the platform plays a short hold tone the moment this node
            fires so the caller

            doesn't sit in silence while the action runs. The tone is
            platform-controlled (not

            configurable) and stops automatically when the action returns. Use
            it for actions you

            expect to take more than ~500 ms (webhook to a slow CRM, complex DB
            lookup); skip it for

            fast actions to avoid making the call feel chatty.
        timeout_secs:
          type: number
          nullable: true
          minimum: 1
          maximum: 600
          description: >
            Per-node hard cap on execution time. When the action doesn't return
            in this many seconds,

            the runtime cancels it and routes to `error_next_step_id` with a
            `tool_timeout_after_Ns`

            error. When null/omitted on a webhook tool, the controller uses the
            effective tool

            `config.timeout_seconds` plus one second of dispatch overhead. Other
            tool nodes use the

            platform default (30s). Set this only when the flow needs a
            different node-level limit.
    IntegrationCallNode:
      type: object
      required:
        - id
        - type
        - provider
        - integration_id
        - action
        - transitions
      description: >
        Calls an OAuth-backed third-party integration (e.g. Google Calendar,
        Gmail) directly from the flow,

        without going through the `tools` table. The integration config —
        provider, account, action — lives on

        the node itself; the runtime resolves each entry in `args_template` per
        its declared mode (literal /

        ai_extract), interpolating `{{node.arg}}` and `{{metadata.key}}`
        mustache tokens, and dispatches

        against the matching integration client. Routing is deterministic and
        identical to `tool_call`

        (success / error / custom JSONPath branches, mutually exclusive, exactly
        one out-edge per fire).


        `provider` is locked at node creation; you cannot flip Calendar → Gmail
        on an existing node.

        `integration_id` must reference an `active` row in the caller's company
        `integrations` table whose

        `provider` matches this node's `provider`. `action` must be one of the
        actions in the catalog for the

        chosen provider.


        Action catalog:

        - `google_calendar.create_event` — required args: `summary`,
        `start_time`, `end_time`. Optional: `attendees`, `description`,
        `location`, `calendar_id`, `time_zone`.

        - `google_calendar.list_events` — optional args: `time_min`, `time_max`,
        `max_results`, `query`, `calendar_id`, `time_zone`.

        - `google_calendar.check_availability` — required args: `start_time`,
        `end_time`. Optional: `calendar_id`, `time_zone`.

        - `google_calendar.cancel_event` — required args: `event_id`. The
        runtime auto-resolves which calendar the event lives on (tries `primary`
        first, falls back to scanning the user's other writable calendars on a
        404), so `calendar_id` is intentionally NOT in the catalog for this
        action.

        - `gmail.send_email` — required args: `to`, `subject`, `body`. Optional:
        `html`, `cc`, `bcc`.


        For Google Calendar actions: `calendar_id` defaults to the user's
        `primary` calendar when blank. `time_zone` is an IANA name
        (`"Asia/Jerusalem"`) — when set, both Google's response is pinned to
        that zone and the event being created is stamped with it.


        **Calendar response post-processing**: `create_event`, `list_events`,
        and `check_availability` responses are sanitized for the LLM before they
        reach the agent — Gemini Live's ISO 8601 parser handles offsets
        unreliably, so the runtime strips the offset and seconds from each
        event's `dateTime` (leaving wall-clock format `"2026-05-10 16:30"`),
        removes per-event `timeZone` fields, and prepends a top-level `timeZone`
        + `timeZone_note` anchor. The agent only ever sees the sanitized view;
        JSONPath custom transitions (`transitions.custom[].jsonpath`) match
        against the sanitized view too. The raw, untouched Google response is
        preserved separately on the call event as `raw_response_preview` (see
        `FlowTraceToolCall` schema) for audit.
      properties:
        id:
          type: string
        type:
          type: string
          enum:
            - integration_call
        name:
          type: string
        position:
          $ref: '#/components/schemas/FlowNodePosition'
        provider:
          type: string
          enum:
            - google_calendar
            - gmail
          description: >-
            Locked at node creation. Must match the linked integration's
            provider.
        integration_id:
          type: string
          format: uuid
          description: >-
            References an active row in the caller's company integrations table
            whose provider matches this node's provider.
        action:
          type: string
          description: >-
            Provider-scoped action identifier — see the action catalog in the
            description.
          example: create_event
        args_template:
          type: object
          additionalProperties:
            $ref: '#/components/schemas/ArgValue'
          description: >
            Map of arg name → `ArgValue`. Required keys depend on the chosen
            action (see catalog above).

            Each `ArgValue` is one of: literal string, `{mode:'literal'}`, or
            `{mode:'ai_extract'}`.

            Both `literal.value` and `ai_extract.description` may contain
            `{{<node_id>.<arg_name>}}`

            tokens (cross-node slot refs to earlier `ai_extract` args) and
            `{{metadata.<key>}}`

            tokens (per-call metadata, built-in keys plus any user-declared
            custom keys; see

            `CallMetadataKey` and `FlowConfig.metadata.custom_metadata_keys`).
        pre_fire_announcement:
          type: boolean
          nullable: true
          description: >
            When true, the platform plays a short hold tone the moment this node
            fires so the caller

            doesn't sit in silence while the action runs. Tone is
            platform-controlled (not

            configurable) and stops automatically when the action returns.
            Recommended for

            `create_event` / `send_email` / network-bound actions; skip for
            `check_availability`

            which is fast.
        timeout_secs:
          type: number
          nullable: true
          minimum: 1
          maximum: 600
          description: >
            Per-node hard cap on execution time. When the action doesn't return
            in this many seconds,

            the runtime cancels it and routes to `error_next_step_id`. Null =
            use the platform

            default (30s).
        transitions:
          $ref: '#/components/schemas/ToolCallTransitions'
    TransferNode:
      type: object
      required:
        - id
        - type
      allOf:
        - $ref: '#/components/schemas/GlobalNodeFields'
      properties:
        id:
          type: string
        type:
          type: string
          enum:
            - transfer
        name:
          type: string
        position:
          $ref: '#/components/schemas/FlowNodePosition'
        transfer_to:
          type: string
          nullable: true
          example: '+972501234567'
        transfer_message:
          type: string
          nullable: true
    EndNode:
      type: object
      required:
        - id
        - type
      allOf:
        - $ref: '#/components/schemas/GlobalNodeFields'
      description: >
        Terminal. Reaching an End node hangs up the call. For per-call data
        extraction or webhook delivery, use the agent-level
        `extraction_parameters` and `webhook_url` / `webhook_events` fields on
        the agent (those apply uniformly to prompt + flow agents).
      properties:
        id:
          type: string
        type:
          type: string
          enum:
            - end
        name:
          type: string
        position:
          $ref: '#/components/schemas/FlowNodePosition'
        farewell:
          type: string
          nullable: true
        is_literal:
          type: boolean
          default: false
    FlowNodePosition:
      type: object
      description: >-
        UI-only persisted x/y on the React Flow canvas. The runtime ignores
        this.
      properties:
        x:
          type: number
        'y':
          type: number
    GlobalNodeFields:
      type: object
      description: >
        Optional fields available on every node EXCEPT start and tool_call. When
        `is_global`

        is true, the node is reachable from any conversation node without an
        explicit edge —

        the eval LLM gets it as an additional candidate transition with a strong
        "prefer

        labeled transitions" bias. Use sparingly (recommended ≤3 globals per
        flow). Good for

        misclassification recovery and universal escape hatches; bad for
        happy-path shortcuts.
      properties:
        is_global:
          type: boolean
          default: false
          description: >-
            Mark this node as globally reachable. Only valid on
            conversation/end/transfer nodes.
        global_jump_description:
          type: string
          nullable: true
          description: >
            REQUIRED when is_global=true. Natural-language condition the eval
            LLM uses to decide whether

            to jump here. Describe a clear user-side signal, not an agent
            intent.

            Example: "User reveals they're actually an owner, not a tenant".
    Transition:
      type: object
      required:
        - id
        - label
        - description
        - next_step_id
      description: >
        A labeled outgoing branch from a conversation node. The voice agent
        picks one

        (or stays) at each user-turn boundary by calling the `pick_transition`
        tool.
      properties:
        id:
          type: string
          description: Stable transition id (used in the flow graph and traces)
        label:
          type: string
          description: >-
            Short human-readable label, also surfaced in the dashboard and call
            traces
        description:
          type: string
          minLength: 1
          description: >
            REQUIRED, non-empty. The natural-language trigger the voice agent
            reads at runtime

            to decide whether to take this path. Describe a user-side signal
            (what the caller

            said or implied), not an agent intent. Example: "Caller confirmed
            they want a demo

            scheduled" — not "agent books the demo". An empty description is
            rejected with

            error code `transition_description_missing`.
        next_step_id:
          type: string
          description: Destination node id
    ToolCallTransitions:
      type: object
      description: >
        Routing for a tool_call node. Routing is deterministic, no LLM. error
        fires on hard failures

        (timeout, 4xx/5xx, integration disconnected). Otherwise custom[] is
        evaluated in declaration order

        and the first match wins; success fires only if no custom matched.
        Exactly one out-edge per fire.
      properties:
        success_next_step_id:
          type: string
          nullable: true
        error_next_step_id:
          type: string
          nullable: true
        custom:
          type: array
          default: []
          items:
            $ref: '#/components/schemas/CustomTransition'
    ArgValue:
      description: >
        Discriminated union for one entry in
        `IntegrationCallNode.args_template`. Each arg can be one of

        three shapes:

          1. Bare string — shorthand for `{mode: 'literal', value: '<the string>'}`.
          2. `{mode: 'literal',    value}` — sent as-is.
          3. `{mode: 'ai_extract', description}` — the live agent runtime extracts this arg from the
             conversation right before the action fires; `description` guides extraction.

        Both `value` and `description` strings may contain mustache tokens that
        are interpolated at

        dispatch time:

          - `{{<node_id>.<arg_name>}}` — resolves to the value an earlier node AI-extracted from
            the conversation. Both `integration_call` AND `tool_call` source nodes are addressable.
            For `integration_call` sources the referenced arg must be declared in `ai_extract` mode
            in that node's `args_template` (validated at save). For `tool_call` sources the arg
            name must match an entry in the linked tool's `config.payload_config.extraction_parameters`
            — these aren't double-checked at save time because they live on the tool config (not the
            flow_config), so a typo renders to empty string at runtime; design an `error` branch
            on the downstream node. Refs to nodes that don't exist raise `args_template_dangling_reference`.
          - `{{metadata.<key>}}` — resolves against per-call metadata. Built-in keys: `id`,
            `direction`, `agent_number`, `user_number`, `agent_name`. User-defined keys come from
            the `metadata` dict passed to `POST /calls`; declare expected custom keys via
            `FlowConfig.metadata.custom_metadata_keys`. Missing metadata keys resolve to an empty
            string at runtime — NOT a save-time error.
      oneOf:
        - type: string
          description: >-
            Literal-mode shorthand. May contain `{{node.arg}}` /
            `{{metadata.key}}` tokens.
        - type: object
          required:
            - mode
            - value
          properties:
            mode:
              type: string
              enum:
                - literal
            value:
              type: string
              description: >-
                Literal value. May contain `{{node.arg}}` / `{{metadata.key}}`
                tokens.
        - type: object
          required:
            - mode
            - description
          properties:
            mode:
              type: string
              enum:
                - ai_extract
            description:
              type: string
              description: >-
                Natural-language hint the runtime uses to bind this slot from
                conversation. May contain `{{node.arg}}` / `{{metadata.key}}`
                tokens to splice prior context into the extraction prompt.
    CustomTransition:
      type: object
      required:
        - id
        - label
        - jsonpath
        - equals
        - next_step_id
      description: >
        Deterministic branch for a tool-call node, matched by JSONPath equality
        against the tool's parsed response body.

        Custom branches are evaluated top-to-bottom, first match wins; mutually
        exclusive with success/error (only one

        out-edge fires per tool fire). See
        /docs/api-reference/agents/flow-config-schema for the full runtime
        semantics.
      properties:
        id:
          type: string
        label:
          type: string
        jsonpath:
          type: string
          example: $.status
          description: >
            Dotted path into the tool's parsed response body (root `$`).
            Supported: `$.foo.bar`, `$.list[0].name`,

            `$.items[2]`. NOT supported: recursive descent (`$..`), wildcards
            (`$.*`), filter expressions.
        equals:
          type: string
          example: no_availability
          description: >
            Compared via string equality after JSON-style stringification of the
            extracted value:

            booleans become "true"/"false" (lowercase), null becomes "null",
            numbers via str(). Match the

            stringified form exactly or the branch never fires.
        next_step_id:
          type: string
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: API Key
      description: >-
        Your Yappr API key (e.g. `ypr_live_...`). Generate one in the dashboard
        under **Settings → API Keys**.

````