---
title: "Duplicate a customer flow"
method: POST
path: "/v1/customer-flow/{flowId}/duplicate"
tags: ["Customer Flow"]
---

# Duplicate a customer flow

`POST /v1/customer-flow/{flowId}/duplicate`

## Request

- Base URL: `https://api.roark.ai`
- URL: `https://api.roark.ai/v1/customer-flow/{flowId}/duplicate`
- Auth: HTTP bearer

Deep-copies a flow into a new project-owned flow. The copy carries the source's description, branching mode, linked agents, flow-level expectations and flow-owned metrics. A scripted flow copies its whole step graph; an improv flow copies its variants (personas, briefs, expectations). Duplicating a Roark-managed flow is how you customise it. Voicemail flows are Roark-managed and cannot be duplicated.

## Path parameters

- `flowId` string, uuid, required

## Request body

- DuplicateCustomerFlowInput: Options for duplicating a customer flow. Every field is optional.
  - `title` string: Title for the copy. Defaults to "Copy of" the source flow.
  - `agentIds` string[]: Agents to link on the copy. Omit to carry the source flow's agents over. Required when the source is a Roark-managed flow with no agents of its own.

## Response `201`

The duplicated customer flow

- object
  - `data` union, required: The conversation a simulated customer has with the agent under test.
    - ScriptedCustomerFlow: A flow whose conversation is written out as a graph of turns.
      - `id` string, uuid, required
      - `title` string, required
      - `description` string, nullable
      - `source` 'SYSTEM' | 'CUSTOM', required
      - `createdAt` string, required: Creation timestamp in ISO 8601 format
      - `updatedAt` string, required: Last update timestamp in ISO 8601 format
      - `agents` object[], required: The agents this flow is run against.
        - `id` string, uuid, required: Unique identifier of the agent
        - `name` string, required: Name of the agent
        - `description` string, nullable, required: Description of the agent
        - `customId` string, nullable, required: Custom identifier for the agent
        - `createdAt` string, required: Creation timestamp in ISO 8601 format
        - `updatedAt` string, required: Last update timestamp in ISO 8601 format
      - `agentExpectations` FlowExpectation[], required
        - `id` string, uuid, required
        - `prompt` string, required: What the agent under test is graded against.
      - `type` 'SCRIPTED', required
      - `scriptAdherence` 'LOOSE' | 'STRICT', required: How closely a run follows the script. LOOSE (default) hands the whole script to the simulated customer as one prompt; it keeps the call moving whatever your agent says. STRICT runs the script as a state machine on the agent service: at every agent step the simulated customer waits, silent, until your agent has said the expected line, and only then moves on. Scripted flows only; STRICT needs the agent-service transport and is not available on realtime models.
      - `offScriptPolicy` OffScriptPolicy, required: STRICT only. What the simulated customer does when your agent does not say the expected line. Each unmatched agent utterance is an attempt: `reaction` runs per attempt (STAY_SILENT, REPEAT its last scripted line, RESPOND once in character without moving on, or SAY `sayLine`), and `then` runs when attempts reach `maxAttempts` or your agent stays silent for `waitSeconds` (HANG_UP ends the call with ended reason SCRIPT_DIVERGED, HANG_UP_INVALIDATE ends it the same way and invalidates the run so it is scored by nothing and counted nowhere, MOVE_ON advances anyway, ADAPT hands the rest of the call to loose behaviour). Null: stay silent, 3 attempts, hang up. The default for every agent step; an AGENT_TURN step can carry its own.
        - `reaction` 'STAY_SILENT' | 'REPEAT' | 'RESPOND' | 'SAY', required
        - `sayLine` string, nullable
        - `maxAttempts` integer, required
        - `then` 'HANG_UP' | 'MOVE_ON' | 'ADAPT' | 'HANG_UP_INVALIDATE', required
        - `waitSeconds` integer, nullable
      - `branchingMode` 'DETERMINISTIC' | 'ADAPTIVE', required: How a run walks the graph. DETERMINISTIC ("Simulate every path" in the app) places one call per variant, each following its path exactly whatever the agent says. ADAPTIVE ("Adapt to your agent") collapses the paths into one call PER PERSONA, on which the simulated customer picks a branch from what the agent actually said. Both modes speak the exact authored lines, and neither changes how metrics or expectations grade.
      - `graph` FlowStep[]: The conversation, as a graph of steps. Present on a single flow; omitted from the list, where reading it would mean walking the project step graph once per row.
        - union: One step in a scripted flow's conversation. `nodeId` is the identity contract: include it to update the existing step, omit it to create a new one. A step continues into `steps` (more than one child is a branch point) and/or `mergeIntoNodeIds`, which names steps elsewhere in the same request that this step rejoins. Branches that come back together are represented that way rather than by repeating the shared step, so reading a flow, editing it and writing it back preserves it exactly. A merge target is named by its `nodeId` when it already exists, or by `ref` when it is being created in the same request. `ref` is a label you choose, it is request-local, and it is never stored or returned. Put the shared step inline under the first branch that reaches it and point the others at it: a top-level step is a root wired straight from the start of the flow, so a merge target parked there would also be reachable directly. A `CUSTOMER_TURN` describes what the simulated customer says and the persona phrases it; a `CUSTOMER_VERBATIM_TURN` is said word for word, and one placed as a top-level step opens the call the moment it connects, before the agent speaks. `CUSTOMER_FIRST_MESSAGE` is the retired name for that opening case: still accepted, stored and returned as `CUSTOMER_VERBATIM_TURN`. The two DTMF types are mirror images and both require `dtmfDigits`. `CUSTOMER_DTMF` is keys the simulated caller presses while navigating your agent. `AGENT_DTMF` is keys your agent under test is expected to press while navigating a menu the simulation is playing, so its digits are an assertion the run is graded against rather than an instruction, and it counts as an agent turn for role alternation. A `CUSTOMER_HANDOFF` hands the phone to another persona (`handoffPersonaId`), or back to the flow's own persona when it is null. The steps after it are spoken by whoever holds the phone. It sits between turns like a silence, so it may follow either side. In a STRICT flow an `AGENT_TURN` may carry its own `offScriptPolicy`, which replaces the flow-level one at that step. Omit it (or send null) to follow the flow's policy. Use it where one missed step makes the rest of the call meaningless: an authentication menu, say, with `then: HANG_UP_INVALIDATE`. Any step may set `stage`, the named phase of the flow it belongs to: the IVR, the login, the scenario itself. Steps in one stage are drawn as a group in the editor, and the name is yours: the reports say it back to you. The stage does not have to exist first, it is created the first time a step names it, and a name already in the project is matched ignoring case. A stage with `required: true` is a gate. A run that ends without getting PAST it never reached what was being measured, so it stays in the report, labelled with the stage it never completed and the one it stopped in, and is left out of the run score and the success-criteria verdict: an agent that never clears the IVR does not fail every check in a scenario it never started. `required` belongs to the stage rather than to the step, so a read returns it on every step in that stage, and a write that sets it on any one of them marks the whole stage. A flow that requires no stage is scored exactly as before. A stage with `analyze: false` is not measured. Its turns are kept out of the core call metrics (talk time, speech pace, turn and response timings), which is what an IVR usually wants alongside `required: true`: the run has to get through the menu, but how the agent sounded reading it is not what is being tested. Coverage is being extended package by package, so the judged metrics (quality, compliance, repetition, escalation, tone) still read the whole call for now. Nothing is cut and nothing is re-timed, so a turn at 4:10 is still at 4:10 with an earlier stage excluded, and rate metrics divide by the part of the call that was measured rather than by the whole of it. Defaults to true, belongs to the stage the same way `required` does, and turning it off on any one step turns it off for the stage.
          - object
            - `nodeId` string, uuid
            - `ref` string
            - `steps` FlowStep[]
            - `mergeIntoNodeIds` string[]
            - `stage` object, nullable
              - …
            - `type` 'AGENT_TURN', required
            - `content` string, nullable
            - `offScriptPolicy` object, nullable
              - …
          - object
            - `nodeId` string, uuid
            - `ref` string
            - `steps` FlowStep[]
            - `mergeIntoNodeIds` string[]
            - `stage` object, nullable
              - …
            - `type` 'CUSTOMER_TURN', required
            - `content` string, nullable
          - object
            - `nodeId` string, uuid
            - `ref` string
            - `steps` FlowStep[]
            - `mergeIntoNodeIds` string[]
            - `stage` object, nullable
              - …
            - `type` 'CUSTOMER_FIRST_MESSAGE', required
            - `content` string, nullable
          - object
            - `nodeId` string, uuid
            - `ref` string
            - `steps` FlowStep[]
            - `mergeIntoNodeIds` string[]
            - `stage` object, nullable
              - …
            - `type` 'CUSTOMER_VERBATIM_TURN', required
            - `content` string, nullable
          - object
            - `nodeId` string, uuid
            - `ref` string
            - `steps` FlowStep[]
            - `mergeIntoNodeIds` string[]
            - `stage` object, nullable
              - …
            - `type` 'CUSTOMER_SILENCE', required
            - `silenceDurationSeconds` integer, nullable
          - object
            - `nodeId` string, uuid
            - `ref` string
            - `steps` FlowStep[]
            - `mergeIntoNodeIds` string[]
            - `stage` object, nullable
              - …
            - `type` 'CUSTOMER_DTMF', required
            - `dtmfDigits` string, nullable
          - object
            - `nodeId` string, uuid
            - `ref` string
            - `steps` FlowStep[]
            - `mergeIntoNodeIds` string[]
            - `stage` object, nullable
              - …
            - `type` 'AGENT_DTMF', required
            - `dtmfDigits` string, nullable
          - object
            - `nodeId` string, uuid
            - `ref` string
            - `steps` FlowStep[]
            - `mergeIntoNodeIds` string[]
            - `stage` object, nullable
              - …
            - `type` 'VOICEMAIL', required
          - object
            - `nodeId` string, uuid
            - `ref` string
            - `steps` FlowStep[]
            - `mergeIntoNodeIds` string[]
            - `stage` object, nullable
              - …
            - `type` 'CUSTOMER_HANDOFF', required
            - `handoffPersonaId` string, uuid, nullable
          - object
            - `nodeId` string, uuid
            - `ref` string
            - `steps` FlowStep[]
            - `mergeIntoNodeIds` string[]
            - `stage` object, nullable
              - …
            - `type` 'SCENARIO_LINK', required
            - `linkedCustomerFlowId` string, uuid, nullable
            - `linkedCustomerFlowVariantId` string, uuid, nullable
      - `happyPath` ScriptedFlowHappyPath, required: One path through a scripted flow. The path engine owns which paths exist, so editing the graph is what creates and removes these.
        - `id` string, uuid, required
        - `title` string, required
        - `systemKey` string, nullable
        - `precededByCustomerFlowId` string, uuid, nullable, required
        - `precededByCustomerFlowVariantId` string, uuid, nullable, required
        - `isGenerated` boolean, required
        - `includeFlowExpectations` boolean, required: Whether this variant is graded on the flow's agentExpectations as well as its additionalExpectations. False grades it on its additionalExpectations only. Always true on the happy path.
        - `createdAt` string, required: Creation timestamp in ISO 8601 format
        - `updatedAt` string, required: Last update timestamp in ISO 8601 format
        - `personaOverride` object, nullable, required: The persona this runs as instead of the happy path's. Null means it inherits.
          - `id` string, uuid, required: Unique identifier of the persona
          - `name` string, required: The name the agent will identify as during conversations
          - `displayName` string, nullable: Label shown in place of the name across the dashboard (e.g. a short descriptor like "Irate Escalator"). The persona still identifies as `name` on calls. Omit or set null to display the name itself.
          - `description` string, nullable: Human-readable description of the persona
          - `language` 'EN' | 'ES' | 'DE' | 'HI' | 'FR' | 'NL' | 'AR' | 'EL' | 'IT' | 'ID' | 'TH' | 'JA' | 'TL' | 'MS' | 'ZH' | 'TR' | 'PT' | 'HE', required: Primary language ISO 639-1 code for the persona
          - `secondaryLanguage` 'EN', nullable: Secondary language ISO 639-1 code for code-switching (e.g., Hinglish, Spanglish)
          - `understoodLanguages` string[], required: Languages the persona can understand. Multilingual combinations are limited by multilingual speech recognition support.
          - `accent` 'US' | 'US_X_SOUTH' | 'GB' | 'ES' | 'DE' | 'IN' | 'FR' | 'NL' | 'SA' | 'GR' | 'AU' | 'IT' | 'ID' | 'TH' | 'JP' | 'NZ' | 'PH' | 'SG' | 'MY' | 'HK' | 'TR' | 'PT' | 'IL', required: Accent of the persona, defined using ISO 3166-1 alpha-2 country codes with optional variants
          - `age` 'CHILD' | 'TEENAGER' | 'ADULT' | 'ELDERLY', required: How old the caller sounds and behaves. Only ages the persona's accent has a voice for are accepted; defaults to ADULT, which every accent supports.
          - `gender` 'MALE' | 'FEMALE', required: Gender of the persona
          - `backgroundNoise` 'NONE' | 'AIRPORT' | 'CHILDREN_PLAYING' | 'CITY' | 'COFFEE_SHOP' | 'CONSTRUCTION' | 'CRYING_BABY' | 'DRIVING' | 'LIBRARY' | 'OFFICE' | 'THUNDERSTORM' | 'TRAIN', required: Background noise setting
          - `speechPace` 'SUPER_SLOW' | 'SLOW' | 'NORMAL' | 'FAST' | 'SUPER_FAST', required: Speech pace of the persona
          - `speechClarity` 'CLEAR' | 'VAGUE' | 'RAMBLING', required: Speech clarity of the persona
          - `hasDisfluencies` boolean, required: Whether the persona uses filler words like "um" and "uh"
          - `baseEmotion` 'NEUTRAL' | 'CHEERFUL' | 'CONFUSED' | 'FRUSTRATED' | 'SKEPTICAL' | 'RUSHED' | 'DISTRACTED' | 'ANGRY' | 'ANXIOUS' | 'SAD', required: Base emotional state of the persona
          - `intentClarity` 'CLEAR' | 'INDIRECT' | 'VAGUE', required: How clearly the persona expresses their intentions
          - `confirmationStyle` 'EXPLICIT' | 'VAGUE', required: How the persona confirms information
          - `memoryReliability` 'HIGH' | 'LOW', required: How reliable the persona's memory is
          - `responseTiming` 'RELAXED' | 'NORMAL' | 'QUICK' | 'BARGE_IN', required: Deprecated and inert: it no longer affects the call. It set how long the persona waited once the agent stopped talking, and measured across production simulations it moved the reply gap by less than the noise floor, because model and speech latency dominate it. Every persona now uses one voice-activity profile. Use `interruption` for a caller who talks over the agent. Still accepted and stored so existing clients keep working. BARGE_IN is stored as `responseTiming: QUICK` with `interruption: OCCASIONAL`.
          - `interruption` 'OFF' | 'BACKCHANNEL' | 'OCCASIONAL' | 'HEAVY', required: How much the persona talks over the agent while it is still speaking. OFF waits its turn. BACKCHANNEL makes listening noises ("mm-hm") over the agent without taking the floor, which tests whether the agent wrongly stops for them. OCCASIONAL adds cutting in on some long agent turns, HEAVY on most of them. Timing is randomised per turn, so two runs of the same persona do not interrupt at identical moments.
          - `backstoryPrompt` string, nullable: Background story and behavioral patterns for the persona
          - `idleMessages` string[], nullable, required: Messages the persona will say when the agent goes silent during a call. null = "Automatic": language-appropriate defaults are used at call time.
          - `idleTimeoutSeconds` integer, required: Seconds of silence before the persona sends an idle message
          - `idleMessageMaxSpokenCount` integer, required: Maximum number of idle messages the persona will send before giving up
          - `idleMessageResetCountOnUserSpeechEnabled` boolean, required: Whether the idle message counter resets when the agent speaks
          - `properties` object, required: Additional custom properties about the persona
          - `phoneNumber` string: The E.164 number every call with this persona uses, when Roark has pinned one for your project. Present only when set; read-only.
          - `createdAt` string, required: Creation timestamp
          - `updatedAt` string, required: Last update timestamp
        - `environment` EnvironmentResponse, required: A simulation environment: the ambient conditions a customer flow variant runs under. The list includes both your own and the ones Roark curates for every project.
          - `id` string, uuid, required
          - `name` string, required
          - `description` string, nullable
          - `backgroundNoise` 'NONE' | 'AIRPORT' | 'CHILDREN_PLAYING' | 'CITY' | 'COFFEE_SHOP' | 'CONSTRUCTION' | 'CRYING_BABY' | 'DRIVING' | 'LIBRARY' | 'OFFICE' | 'THUNDERSTORM' | 'TRAIN', required
          - `backgroundNoiseVolume` number, required
          - `createdAt` string, required: Creation timestamp in ISO 8601 format
          - `updatedAt` string, required: Last update timestamp in ISO 8601 format
        - `additionalExpectations` FlowExpectation[], required: Graded on top of the flow's own expectations, for this variant only.
          - `id` string, uuid, required
          - `prompt` string, required: What the agent under test is graded against.
        - `type` 'SCRIPTED', required
        - `steps` FlowStep[], required: The one path through the graph this variant runs, in order. Linear by construction, so these steps never nest.
          - union: One step in a scripted flow's conversation. `nodeId` is the identity contract: include it to update the existing step, omit it to create a new one. A step continues into `steps` (more than one child is a branch point) and/or `mergeIntoNodeIds`, which names steps elsewhere in the same request that this step rejoins. Branches that come back together are represented that way rather than by repeating the shared step, so reading a flow, editing it and writing it back preserves it exactly. A merge target is named by its `nodeId` when it already exists, or by `ref` when it is being created in the same request. `ref` is a label you choose, it is request-local, and it is never stored or returned. Put the shared step inline under the first branch that reaches it and point the others at it: a top-level step is a root wired straight from the start of the flow, so a merge target parked there would also be reachable directly. A `CUSTOMER_TURN` describes what the simulated customer says and the persona phrases it; a `CUSTOMER_VERBATIM_TURN` is said word for word, and one placed as a top-level step opens the call the moment it connects, before the agent speaks. `CUSTOMER_FIRST_MESSAGE` is the retired name for that opening case: still accepted, stored and returned as `CUSTOMER_VERBATIM_TURN`. The two DTMF types are mirror images and both require `dtmfDigits`. `CUSTOMER_DTMF` is keys the simulated caller presses while navigating your agent. `AGENT_DTMF` is keys your agent under test is expected to press while navigating a menu the simulation is playing, so its digits are an assertion the run is graded against rather than an instruction, and it counts as an agent turn for role alternation. A `CUSTOMER_HANDOFF` hands the phone to another persona (`handoffPersonaId`), or back to the flow's own persona when it is null. The steps after it are spoken by whoever holds the phone. It sits between turns like a silence, so it may follow either side. In a STRICT flow an `AGENT_TURN` may carry its own `offScriptPolicy`, which replaces the flow-level one at that step. Omit it (or send null) to follow the flow's policy. Use it where one missed step makes the rest of the call meaningless: an authentication menu, say, with `then: HANG_UP_INVALIDATE`. Any step may set `stage`, the named phase of the flow it belongs to: the IVR, the login, the scenario itself. Steps in one stage are drawn as a group in the editor, and the name is yours: the reports say it back to you. The stage does not have to exist first, it is created the first time a step names it, and a name already in the project is matched ignoring case. A stage with `required: true` is a gate. A run that ends without getting PAST it never reached what was being measured, so it stays in the report, labelled with the stage it never completed and the one it stopped in, and is left out of the run score and the success-criteria verdict: an agent that never clears the IVR does not fail every check in a scenario it never started. `required` belongs to the stage rather than to the step, so a read returns it on every step in that stage, and a write that sets it on any one of them marks the whole stage. A flow that requires no stage is scored exactly as before. A stage with `analyze: false` is not measured. Its turns are kept out of the core call metrics (talk time, speech pace, turn and response timings), which is what an IVR usually wants alongside `required: true`: the run has to get through the menu, but how the agent sounded reading it is not what is being tested. Coverage is being extended package by package, so the judged metrics (quality, compliance, repetition, escalation, tone) still read the whole call for now. Nothing is cut and nothing is re-timed, so a turn at 4:10 is still at 4:10 with an earlier stage excluded, and rate metrics divide by the part of the call that was measured rather than by the whole of it. Defaults to true, belongs to the stage the same way `required` does, and turning it off on any one step turns it off for the stage.
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
      - `edgeCases` ScriptedFlowVariant[], required: Every other way of running this flow.
        - `id` string, uuid, required
        - `title` string, required
        - `systemKey` string, nullable
        - `precededByCustomerFlowId` string, uuid, nullable, required
        - `precededByCustomerFlowVariantId` string, uuid, nullable, required
        - `isGenerated` boolean, required
        - `includeFlowExpectations` boolean, required: Whether this variant is graded on the flow's agentExpectations as well as its additionalExpectations. False grades it on its additionalExpectations only. Always true on the happy path.
        - `createdAt` string, required: Creation timestamp in ISO 8601 format
        - `updatedAt` string, required: Last update timestamp in ISO 8601 format
        - `personaOverride` object, nullable, required: The persona this runs as instead of the happy path's. Null means it inherits.
          - `id` string, uuid, required: Unique identifier of the persona
          - `name` string, required: The name the agent will identify as during conversations
          - `displayName` string, nullable: Label shown in place of the name across the dashboard (e.g. a short descriptor like "Irate Escalator"). The persona still identifies as `name` on calls. Omit or set null to display the name itself.
          - `description` string, nullable: Human-readable description of the persona
          - `language` 'EN' | 'ES' | 'DE' | 'HI' | 'FR' | 'NL' | 'AR' | 'EL' | 'IT' | 'ID' | 'TH' | 'JA' | 'TL' | 'MS' | 'ZH' | 'TR' | 'PT' | 'HE', required: Primary language ISO 639-1 code for the persona
          - `secondaryLanguage` 'EN', nullable: Secondary language ISO 639-1 code for code-switching (e.g., Hinglish, Spanglish)
          - `understoodLanguages` string[], required: Languages the persona can understand. Multilingual combinations are limited by multilingual speech recognition support.
          - `accent` 'US' | 'US_X_SOUTH' | 'GB' | 'ES' | 'DE' | 'IN' | 'FR' | 'NL' | 'SA' | 'GR' | 'AU' | 'IT' | 'ID' | 'TH' | 'JP' | 'NZ' | 'PH' | 'SG' | 'MY' | 'HK' | 'TR' | 'PT' | 'IL', required: Accent of the persona, defined using ISO 3166-1 alpha-2 country codes with optional variants
          - `age` 'CHILD' | 'TEENAGER' | 'ADULT' | 'ELDERLY', required: How old the caller sounds and behaves. Only ages the persona's accent has a voice for are accepted; defaults to ADULT, which every accent supports.
          - `gender` 'MALE' | 'FEMALE', required: Gender of the persona
          - `backgroundNoise` 'NONE' | 'AIRPORT' | 'CHILDREN_PLAYING' | 'CITY' | 'COFFEE_SHOP' | 'CONSTRUCTION' | 'CRYING_BABY' | 'DRIVING' | 'LIBRARY' | 'OFFICE' | 'THUNDERSTORM' | 'TRAIN', required: Background noise setting
          - `speechPace` 'SUPER_SLOW' | 'SLOW' | 'NORMAL' | 'FAST' | 'SUPER_FAST', required: Speech pace of the persona
          - `speechClarity` 'CLEAR' | 'VAGUE' | 'RAMBLING', required: Speech clarity of the persona
          - `hasDisfluencies` boolean, required: Whether the persona uses filler words like "um" and "uh"
          - `baseEmotion` 'NEUTRAL' | 'CHEERFUL' | 'CONFUSED' | 'FRUSTRATED' | 'SKEPTICAL' | 'RUSHED' | 'DISTRACTED' | 'ANGRY' | 'ANXIOUS' | 'SAD', required: Base emotional state of the persona
          - `intentClarity` 'CLEAR' | 'INDIRECT' | 'VAGUE', required: How clearly the persona expresses their intentions
          - `confirmationStyle` 'EXPLICIT' | 'VAGUE', required: How the persona confirms information
          - `memoryReliability` 'HIGH' | 'LOW', required: How reliable the persona's memory is
          - `responseTiming` 'RELAXED' | 'NORMAL' | 'QUICK' | 'BARGE_IN', required: Deprecated and inert: it no longer affects the call. It set how long the persona waited once the agent stopped talking, and measured across production simulations it moved the reply gap by less than the noise floor, because model and speech latency dominate it. Every persona now uses one voice-activity profile. Use `interruption` for a caller who talks over the agent. Still accepted and stored so existing clients keep working. BARGE_IN is stored as `responseTiming: QUICK` with `interruption: OCCASIONAL`.
          - `interruption` 'OFF' | 'BACKCHANNEL' | 'OCCASIONAL' | 'HEAVY', required: How much the persona talks over the agent while it is still speaking. OFF waits its turn. BACKCHANNEL makes listening noises ("mm-hm") over the agent without taking the floor, which tests whether the agent wrongly stops for them. OCCASIONAL adds cutting in on some long agent turns, HEAVY on most of them. Timing is randomised per turn, so two runs of the same persona do not interrupt at identical moments.
          - `backstoryPrompt` string, nullable: Background story and behavioral patterns for the persona
          - `idleMessages` string[], nullable, required: Messages the persona will say when the agent goes silent during a call. null = "Automatic": language-appropriate defaults are used at call time.
          - `idleTimeoutSeconds` integer, required: Seconds of silence before the persona sends an idle message
          - `idleMessageMaxSpokenCount` integer, required: Maximum number of idle messages the persona will send before giving up
          - `idleMessageResetCountOnUserSpeechEnabled` boolean, required: Whether the idle message counter resets when the agent speaks
          - `properties` object, required: Additional custom properties about the persona
          - `phoneNumber` string: The E.164 number every call with this persona uses, when Roark has pinned one for your project. Present only when set; read-only.
          - `createdAt` string, required: Creation timestamp
          - `updatedAt` string, required: Last update timestamp
        - `environment` EnvironmentResponse, required: A simulation environment: the ambient conditions a customer flow variant runs under. The list includes both your own and the ones Roark curates for every project.
          - `id` string, uuid, required
          - `name` string, required
          - `description` string, nullable
          - `backgroundNoise` 'NONE' | 'AIRPORT' | 'CHILDREN_PLAYING' | 'CITY' | 'COFFEE_SHOP' | 'CONSTRUCTION' | 'CRYING_BABY' | 'DRIVING' | 'LIBRARY' | 'OFFICE' | 'THUNDERSTORM' | 'TRAIN', required
          - `backgroundNoiseVolume` number, required
          - `createdAt` string, required: Creation timestamp in ISO 8601 format
          - `updatedAt` string, required: Last update timestamp in ISO 8601 format
        - `additionalExpectations` FlowExpectation[], required: Graded on top of the flow's own expectations, for this variant only.
          - `id` string, uuid, required
          - `prompt` string, required: What the agent under test is graded against.
        - `type` 'SCRIPTED', required
        - `steps` FlowStep[], required: The one path through the graph this variant runs, in order. Linear by construction, so these steps never nest.
          - union: One step in a scripted flow's conversation. `nodeId` is the identity contract: include it to update the existing step, omit it to create a new one. A step continues into `steps` (more than one child is a branch point) and/or `mergeIntoNodeIds`, which names steps elsewhere in the same request that this step rejoins. Branches that come back together are represented that way rather than by repeating the shared step, so reading a flow, editing it and writing it back preserves it exactly. A merge target is named by its `nodeId` when it already exists, or by `ref` when it is being created in the same request. `ref` is a label you choose, it is request-local, and it is never stored or returned. Put the shared step inline under the first branch that reaches it and point the others at it: a top-level step is a root wired straight from the start of the flow, so a merge target parked there would also be reachable directly. A `CUSTOMER_TURN` describes what the simulated customer says and the persona phrases it; a `CUSTOMER_VERBATIM_TURN` is said word for word, and one placed as a top-level step opens the call the moment it connects, before the agent speaks. `CUSTOMER_FIRST_MESSAGE` is the retired name for that opening case: still accepted, stored and returned as `CUSTOMER_VERBATIM_TURN`. The two DTMF types are mirror images and both require `dtmfDigits`. `CUSTOMER_DTMF` is keys the simulated caller presses while navigating your agent. `AGENT_DTMF` is keys your agent under test is expected to press while navigating a menu the simulation is playing, so its digits are an assertion the run is graded against rather than an instruction, and it counts as an agent turn for role alternation. A `CUSTOMER_HANDOFF` hands the phone to another persona (`handoffPersonaId`), or back to the flow's own persona when it is null. The steps after it are spoken by whoever holds the phone. It sits between turns like a silence, so it may follow either side. In a STRICT flow an `AGENT_TURN` may carry its own `offScriptPolicy`, which replaces the flow-level one at that step. Omit it (or send null) to follow the flow's policy. Use it where one missed step makes the rest of the call meaningless: an authentication menu, say, with `then: HANG_UP_INVALIDATE`. Any step may set `stage`, the named phase of the flow it belongs to: the IVR, the login, the scenario itself. Steps in one stage are drawn as a group in the editor, and the name is yours: the reports say it back to you. The stage does not have to exist first, it is created the first time a step names it, and a name already in the project is matched ignoring case. A stage with `required: true` is a gate. A run that ends without getting PAST it never reached what was being measured, so it stays in the report, labelled with the stage it never completed and the one it stopped in, and is left out of the run score and the success-criteria verdict: an agent that never clears the IVR does not fail every check in a scenario it never started. `required` belongs to the stage rather than to the step, so a read returns it on every step in that stage, and a write that sets it on any one of them marks the whole stage. A flow that requires no stage is scored exactly as before. A stage with `analyze: false` is not measured. Its turns are kept out of the core call metrics (talk time, speech pace, turn and response timings), which is what an IVR usually wants alongside `required: true`: the run has to get through the menu, but how the agent sounded reading it is not what is being tested. Coverage is being extended package by package, so the judged metrics (quality, compliance, repetition, escalation, tone) still read the whole call for now. Nothing is cut and nothing is re-timed, so a turn at 4:10 is still at 4:10 with an earlier stage excluded, and rate metrics divide by the part of the call that was measured rather than by the whole of it. Defaults to true, belongs to the stage the same way `required` does, and turning it off on any one step turns it off for the stage.
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
    - ImprovCustomerFlow: A flow whose conversation is not written out: each variant gives the simulated customer a brief and lets it improvise.
      - `id` string, uuid, required
      - `title` string, required
      - `description` string, nullable
      - `source` 'SYSTEM' | 'CUSTOM', required
      - `createdAt` string, required: Creation timestamp in ISO 8601 format
      - `updatedAt` string, required: Last update timestamp in ISO 8601 format
      - `agents` object[], required: The agents this flow is run against.
        - `id` string, uuid, required: Unique identifier of the agent
        - `name` string, required: Name of the agent
        - `description` string, nullable, required: Description of the agent
        - `customId` string, nullable, required: Custom identifier for the agent
        - `createdAt` string, required: Creation timestamp in ISO 8601 format
        - `updatedAt` string, required: Last update timestamp in ISO 8601 format
      - `agentExpectations` FlowExpectation[], required
        - `id` string, uuid, required
        - `prompt` string, required: What the agent under test is graded against.
      - `type` 'IMPROV', required
      - `happyPath` ImprovFlowHappyPath, required: One brief to run an improv flow with.
        - `id` string, uuid, required
        - `title` string, required
        - `systemKey` string, nullable
        - `precededByCustomerFlowId` string, uuid, nullable, required
        - `precededByCustomerFlowVariantId` string, uuid, nullable, required
        - `isGenerated` boolean, required
        - `includeFlowExpectations` boolean, required: Whether this variant is graded on the flow's agentExpectations as well as its additionalExpectations. False grades it on its additionalExpectations only. Always true on the happy path.
        - `createdAt` string, required: Creation timestamp in ISO 8601 format
        - `updatedAt` string, required: Last update timestamp in ISO 8601 format
        - `personaOverride` object, nullable, required: The persona this runs as instead of the happy path's. Null means it inherits.
          - `id` string, uuid, required: Unique identifier of the persona
          - `name` string, required: The name the agent will identify as during conversations
          - `displayName` string, nullable: Label shown in place of the name across the dashboard (e.g. a short descriptor like "Irate Escalator"). The persona still identifies as `name` on calls. Omit or set null to display the name itself.
          - `description` string, nullable: Human-readable description of the persona
          - `language` 'EN' | 'ES' | 'DE' | 'HI' | 'FR' | 'NL' | 'AR' | 'EL' | 'IT' | 'ID' | 'TH' | 'JA' | 'TL' | 'MS' | 'ZH' | 'TR' | 'PT' | 'HE', required: Primary language ISO 639-1 code for the persona
          - `secondaryLanguage` 'EN', nullable: Secondary language ISO 639-1 code for code-switching (e.g., Hinglish, Spanglish)
          - `understoodLanguages` string[], required: Languages the persona can understand. Multilingual combinations are limited by multilingual speech recognition support.
          - `accent` 'US' | 'US_X_SOUTH' | 'GB' | 'ES' | 'DE' | 'IN' | 'FR' | 'NL' | 'SA' | 'GR' | 'AU' | 'IT' | 'ID' | 'TH' | 'JP' | 'NZ' | 'PH' | 'SG' | 'MY' | 'HK' | 'TR' | 'PT' | 'IL', required: Accent of the persona, defined using ISO 3166-1 alpha-2 country codes with optional variants
          - `age` 'CHILD' | 'TEENAGER' | 'ADULT' | 'ELDERLY', required: How old the caller sounds and behaves. Only ages the persona's accent has a voice for are accepted; defaults to ADULT, which every accent supports.
          - `gender` 'MALE' | 'FEMALE', required: Gender of the persona
          - `backgroundNoise` 'NONE' | 'AIRPORT' | 'CHILDREN_PLAYING' | 'CITY' | 'COFFEE_SHOP' | 'CONSTRUCTION' | 'CRYING_BABY' | 'DRIVING' | 'LIBRARY' | 'OFFICE' | 'THUNDERSTORM' | 'TRAIN', required: Background noise setting
          - `speechPace` 'SUPER_SLOW' | 'SLOW' | 'NORMAL' | 'FAST' | 'SUPER_FAST', required: Speech pace of the persona
          - `speechClarity` 'CLEAR' | 'VAGUE' | 'RAMBLING', required: Speech clarity of the persona
          - `hasDisfluencies` boolean, required: Whether the persona uses filler words like "um" and "uh"
          - `baseEmotion` 'NEUTRAL' | 'CHEERFUL' | 'CONFUSED' | 'FRUSTRATED' | 'SKEPTICAL' | 'RUSHED' | 'DISTRACTED' | 'ANGRY' | 'ANXIOUS' | 'SAD', required: Base emotional state of the persona
          - `intentClarity` 'CLEAR' | 'INDIRECT' | 'VAGUE', required: How clearly the persona expresses their intentions
          - `confirmationStyle` 'EXPLICIT' | 'VAGUE', required: How the persona confirms information
          - `memoryReliability` 'HIGH' | 'LOW', required: How reliable the persona's memory is
          - `responseTiming` 'RELAXED' | 'NORMAL' | 'QUICK' | 'BARGE_IN', required: Deprecated and inert: it no longer affects the call. It set how long the persona waited once the agent stopped talking, and measured across production simulations it moved the reply gap by less than the noise floor, because model and speech latency dominate it. Every persona now uses one voice-activity profile. Use `interruption` for a caller who talks over the agent. Still accepted and stored so existing clients keep working. BARGE_IN is stored as `responseTiming: QUICK` with `interruption: OCCASIONAL`.
          - `interruption` 'OFF' | 'BACKCHANNEL' | 'OCCASIONAL' | 'HEAVY', required: How much the persona talks over the agent while it is still speaking. OFF waits its turn. BACKCHANNEL makes listening noises ("mm-hm") over the agent without taking the floor, which tests whether the agent wrongly stops for them. OCCASIONAL adds cutting in on some long agent turns, HEAVY on most of them. Timing is randomised per turn, so two runs of the same persona do not interrupt at identical moments.
          - `backstoryPrompt` string, nullable: Background story and behavioral patterns for the persona
          - `idleMessages` string[], nullable, required: Messages the persona will say when the agent goes silent during a call. null = "Automatic": language-appropriate defaults are used at call time.
          - `idleTimeoutSeconds` integer, required: Seconds of silence before the persona sends an idle message
          - `idleMessageMaxSpokenCount` integer, required: Maximum number of idle messages the persona will send before giving up
          - `idleMessageResetCountOnUserSpeechEnabled` boolean, required: Whether the idle message counter resets when the agent speaks
          - `properties` object, required: Additional custom properties about the persona
          - `phoneNumber` string: The E.164 number every call with this persona uses, when Roark has pinned one for your project. Present only when set; read-only.
          - `createdAt` string, required: Creation timestamp
          - `updatedAt` string, required: Last update timestamp
        - `environment` EnvironmentResponse, required: A simulation environment: the ambient conditions a customer flow variant runs under. The list includes both your own and the ones Roark curates for every project.
          - `id` string, uuid, required
          - `name` string, required
          - `description` string, nullable
          - `backgroundNoise` 'NONE' | 'AIRPORT' | 'CHILDREN_PLAYING' | 'CITY' | 'COFFEE_SHOP' | 'CONSTRUCTION' | 'CRYING_BABY' | 'DRIVING' | 'LIBRARY' | 'OFFICE' | 'THUNDERSTORM' | 'TRAIN', required
          - `backgroundNoiseVolume` number, required
          - `createdAt` string, required: Creation timestamp in ISO 8601 format
          - `updatedAt` string, required: Last update timestamp in ISO 8601 format
        - `additionalExpectations` FlowExpectation[], required: Graded on top of the flow's own expectations, for this variant only.
          - `id` string, uuid, required
          - `prompt` string, required: What the agent under test is graded against.
        - `type` 'IMPROV', required
        - `prompt` string, nullable: The brief the simulated customer improvises from. A `@{persona:<id>}` mention hands the phone to that persona mid-call; the sentences around the mention say when.
        - `handoffPersonas` object[], required: The personas this brief hands the phone to, in order of first mention. Empty when the brief inherits the happy path's: read the happy path's list.
          - `id` string, uuid, required: Unique identifier of the persona
          - `name` string, required: The name the agent will identify as during conversations
          - `displayName` string, nullable: Label shown in place of the name across the dashboard (e.g. a short descriptor like "Irate Escalator"). The persona still identifies as `name` on calls. Omit or set null to display the name itself.
          - `description` string, nullable: Human-readable description of the persona
          - `language` 'EN' | 'ES' | 'DE' | 'HI' | 'FR' | 'NL' | 'AR' | 'EL' | 'IT' | 'ID' | 'TH' | 'JA' | 'TL' | 'MS' | 'ZH' | 'TR' | 'PT' | 'HE', required: Primary language ISO 639-1 code for the persona
          - `secondaryLanguage` 'EN', nullable: Secondary language ISO 639-1 code for code-switching (e.g., Hinglish, Spanglish)
          - `understoodLanguages` string[], required: Languages the persona can understand. Multilingual combinations are limited by multilingual speech recognition support.
          - `accent` 'US' | 'US_X_SOUTH' | 'GB' | 'ES' | 'DE' | 'IN' | 'FR' | 'NL' | 'SA' | 'GR' | 'AU' | 'IT' | 'ID' | 'TH' | 'JP' | 'NZ' | 'PH' | 'SG' | 'MY' | 'HK' | 'TR' | 'PT' | 'IL', required: Accent of the persona, defined using ISO 3166-1 alpha-2 country codes with optional variants
          - `age` 'CHILD' | 'TEENAGER' | 'ADULT' | 'ELDERLY', required: How old the caller sounds and behaves. Only ages the persona's accent has a voice for are accepted; defaults to ADULT, which every accent supports.
          - `gender` 'MALE' | 'FEMALE', required: Gender of the persona
          - `backgroundNoise` 'NONE' | 'AIRPORT' | 'CHILDREN_PLAYING' | 'CITY' | 'COFFEE_SHOP' | 'CONSTRUCTION' | 'CRYING_BABY' | 'DRIVING' | 'LIBRARY' | 'OFFICE' | 'THUNDERSTORM' | 'TRAIN', required: Background noise setting
          - `speechPace` 'SUPER_SLOW' | 'SLOW' | 'NORMAL' | 'FAST' | 'SUPER_FAST', required: Speech pace of the persona
          - `speechClarity` 'CLEAR' | 'VAGUE' | 'RAMBLING', required: Speech clarity of the persona
          - `hasDisfluencies` boolean, required: Whether the persona uses filler words like "um" and "uh"
          - `baseEmotion` 'NEUTRAL' | 'CHEERFUL' | 'CONFUSED' | 'FRUSTRATED' | 'SKEPTICAL' | 'RUSHED' | 'DISTRACTED' | 'ANGRY' | 'ANXIOUS' | 'SAD', required: Base emotional state of the persona
          - `intentClarity` 'CLEAR' | 'INDIRECT' | 'VAGUE', required: How clearly the persona expresses their intentions
          - `confirmationStyle` 'EXPLICIT' | 'VAGUE', required: How the persona confirms information
          - `memoryReliability` 'HIGH' | 'LOW', required: How reliable the persona's memory is
          - `responseTiming` 'RELAXED' | 'NORMAL' | 'QUICK' | 'BARGE_IN', required: Deprecated and inert: it no longer affects the call. It set how long the persona waited once the agent stopped talking, and measured across production simulations it moved the reply gap by less than the noise floor, because model and speech latency dominate it. Every persona now uses one voice-activity profile. Use `interruption` for a caller who talks over the agent. Still accepted and stored so existing clients keep working. BARGE_IN is stored as `responseTiming: QUICK` with `interruption: OCCASIONAL`.
          - `interruption` 'OFF' | 'BACKCHANNEL' | 'OCCASIONAL' | 'HEAVY', required: How much the persona talks over the agent while it is still speaking. OFF waits its turn. BACKCHANNEL makes listening noises ("mm-hm") over the agent without taking the floor, which tests whether the agent wrongly stops for them. OCCASIONAL adds cutting in on some long agent turns, HEAVY on most of them. Timing is randomised per turn, so two runs of the same persona do not interrupt at identical moments.
          - `backstoryPrompt` string, nullable: Background story and behavioral patterns for the persona
          - `idleMessages` string[], nullable, required: Messages the persona will say when the agent goes silent during a call. null = "Automatic": language-appropriate defaults are used at call time.
          - `idleTimeoutSeconds` integer, required: Seconds of silence before the persona sends an idle message
          - `idleMessageMaxSpokenCount` integer, required: Maximum number of idle messages the persona will send before giving up
          - `idleMessageResetCountOnUserSpeechEnabled` boolean, required: Whether the idle message counter resets when the agent speaks
          - `properties` object, required: Additional custom properties about the persona
          - `phoneNumber` string: The E.164 number every call with this persona uses, when Roark has pinned one for your project. Present only when set; read-only.
          - `createdAt` string, required: Creation timestamp
          - `updatedAt` string, required: Last update timestamp
      - `edgeCases` ImprovFlowVariant[], required: Every other way of running this flow.
        - `id` string, uuid, required
        - `title` string, required
        - `systemKey` string, nullable
        - `precededByCustomerFlowId` string, uuid, nullable, required
        - `precededByCustomerFlowVariantId` string, uuid, nullable, required
        - `isGenerated` boolean, required
        - `includeFlowExpectations` boolean, required: Whether this variant is graded on the flow's agentExpectations as well as its additionalExpectations. False grades it on its additionalExpectations only. Always true on the happy path.
        - `createdAt` string, required: Creation timestamp in ISO 8601 format
        - `updatedAt` string, required: Last update timestamp in ISO 8601 format
        - `personaOverride` object, nullable, required: The persona this runs as instead of the happy path's. Null means it inherits.
          - `id` string, uuid, required: Unique identifier of the persona
          - `name` string, required: The name the agent will identify as during conversations
          - `displayName` string, nullable: Label shown in place of the name across the dashboard (e.g. a short descriptor like "Irate Escalator"). The persona still identifies as `name` on calls. Omit or set null to display the name itself.
          - `description` string, nullable: Human-readable description of the persona
          - `language` 'EN' | 'ES' | 'DE' | 'HI' | 'FR' | 'NL' | 'AR' | 'EL' | 'IT' | 'ID' | 'TH' | 'JA' | 'TL' | 'MS' | 'ZH' | 'TR' | 'PT' | 'HE', required: Primary language ISO 639-1 code for the persona
          - `secondaryLanguage` 'EN', nullable: Secondary language ISO 639-1 code for code-switching (e.g., Hinglish, Spanglish)
          - `understoodLanguages` string[], required: Languages the persona can understand. Multilingual combinations are limited by multilingual speech recognition support.
          - `accent` 'US' | 'US_X_SOUTH' | 'GB' | 'ES' | 'DE' | 'IN' | 'FR' | 'NL' | 'SA' | 'GR' | 'AU' | 'IT' | 'ID' | 'TH' | 'JP' | 'NZ' | 'PH' | 'SG' | 'MY' | 'HK' | 'TR' | 'PT' | 'IL', required: Accent of the persona, defined using ISO 3166-1 alpha-2 country codes with optional variants
          - `age` 'CHILD' | 'TEENAGER' | 'ADULT' | 'ELDERLY', required: How old the caller sounds and behaves. Only ages the persona's accent has a voice for are accepted; defaults to ADULT, which every accent supports.
          - `gender` 'MALE' | 'FEMALE', required: Gender of the persona
          - `backgroundNoise` 'NONE' | 'AIRPORT' | 'CHILDREN_PLAYING' | 'CITY' | 'COFFEE_SHOP' | 'CONSTRUCTION' | 'CRYING_BABY' | 'DRIVING' | 'LIBRARY' | 'OFFICE' | 'THUNDERSTORM' | 'TRAIN', required: Background noise setting
          - `speechPace` 'SUPER_SLOW' | 'SLOW' | 'NORMAL' | 'FAST' | 'SUPER_FAST', required: Speech pace of the persona
          - `speechClarity` 'CLEAR' | 'VAGUE' | 'RAMBLING', required: Speech clarity of the persona
          - `hasDisfluencies` boolean, required: Whether the persona uses filler words like "um" and "uh"
          - `baseEmotion` 'NEUTRAL' | 'CHEERFUL' | 'CONFUSED' | 'FRUSTRATED' | 'SKEPTICAL' | 'RUSHED' | 'DISTRACTED' | 'ANGRY' | 'ANXIOUS' | 'SAD', required: Base emotional state of the persona
          - `intentClarity` 'CLEAR' | 'INDIRECT' | 'VAGUE', required: How clearly the persona expresses their intentions
          - `confirmationStyle` 'EXPLICIT' | 'VAGUE', required: How the persona confirms information
          - `memoryReliability` 'HIGH' | 'LOW', required: How reliable the persona's memory is
          - `responseTiming` 'RELAXED' | 'NORMAL' | 'QUICK' | 'BARGE_IN', required: Deprecated and inert: it no longer affects the call. It set how long the persona waited once the agent stopped talking, and measured across production simulations it moved the reply gap by less than the noise floor, because model and speech latency dominate it. Every persona now uses one voice-activity profile. Use `interruption` for a caller who talks over the agent. Still accepted and stored so existing clients keep working. BARGE_IN is stored as `responseTiming: QUICK` with `interruption: OCCASIONAL`.
          - `interruption` 'OFF' | 'BACKCHANNEL' | 'OCCASIONAL' | 'HEAVY', required: How much the persona talks over the agent while it is still speaking. OFF waits its turn. BACKCHANNEL makes listening noises ("mm-hm") over the agent without taking the floor, which tests whether the agent wrongly stops for them. OCCASIONAL adds cutting in on some long agent turns, HEAVY on most of them. Timing is randomised per turn, so two runs of the same persona do not interrupt at identical moments.
          - `backstoryPrompt` string, nullable: Background story and behavioral patterns for the persona
          - `idleMessages` string[], nullable, required: Messages the persona will say when the agent goes silent during a call. null = "Automatic": language-appropriate defaults are used at call time.
          - `idleTimeoutSeconds` integer, required: Seconds of silence before the persona sends an idle message
          - `idleMessageMaxSpokenCount` integer, required: Maximum number of idle messages the persona will send before giving up
          - `idleMessageResetCountOnUserSpeechEnabled` boolean, required: Whether the idle message counter resets when the agent speaks
          - `properties` object, required: Additional custom properties about the persona
          - `phoneNumber` string: The E.164 number every call with this persona uses, when Roark has pinned one for your project. Present only when set; read-only.
          - `createdAt` string, required: Creation timestamp
          - `updatedAt` string, required: Last update timestamp
        - `environment` EnvironmentResponse, required: A simulation environment: the ambient conditions a customer flow variant runs under. The list includes both your own and the ones Roark curates for every project.
          - `id` string, uuid, required
          - `name` string, required
          - `description` string, nullable
          - `backgroundNoise` 'NONE' | 'AIRPORT' | 'CHILDREN_PLAYING' | 'CITY' | 'COFFEE_SHOP' | 'CONSTRUCTION' | 'CRYING_BABY' | 'DRIVING' | 'LIBRARY' | 'OFFICE' | 'THUNDERSTORM' | 'TRAIN', required
          - `backgroundNoiseVolume` number, required
          - `createdAt` string, required: Creation timestamp in ISO 8601 format
          - `updatedAt` string, required: Last update timestamp in ISO 8601 format
        - `additionalExpectations` FlowExpectation[], required: Graded on top of the flow's own expectations, for this variant only.
          - `id` string, uuid, required
          - `prompt` string, required: What the agent under test is graded against.
        - `type` 'IMPROV', required
        - `prompt` string, nullable: The brief the simulated customer improvises from. A `@{persona:<id>}` mention hands the phone to that persona mid-call; the sentences around the mention say when.
        - `handoffPersonas` object[], required: The personas this brief hands the phone to, in order of first mention. Empty when the brief inherits the happy path's: read the happy path's list.
          - `id` string, uuid, required: Unique identifier of the persona
          - `name` string, required: The name the agent will identify as during conversations
          - `displayName` string, nullable: Label shown in place of the name across the dashboard (e.g. a short descriptor like "Irate Escalator"). The persona still identifies as `name` on calls. Omit or set null to display the name itself.
          - `description` string, nullable: Human-readable description of the persona
          - `language` 'EN' | 'ES' | 'DE' | 'HI' | 'FR' | 'NL' | 'AR' | 'EL' | 'IT' | 'ID' | 'TH' | 'JA' | 'TL' | 'MS' | 'ZH' | 'TR' | 'PT' | 'HE', required: Primary language ISO 639-1 code for the persona
          - `secondaryLanguage` 'EN', nullable: Secondary language ISO 639-1 code for code-switching (e.g., Hinglish, Spanglish)
          - `understoodLanguages` string[], required: Languages the persona can understand. Multilingual combinations are limited by multilingual speech recognition support.
          - `accent` 'US' | 'US_X_SOUTH' | 'GB' | 'ES' | 'DE' | 'IN' | 'FR' | 'NL' | 'SA' | 'GR' | 'AU' | 'IT' | 'ID' | 'TH' | 'JP' | 'NZ' | 'PH' | 'SG' | 'MY' | 'HK' | 'TR' | 'PT' | 'IL', required: Accent of the persona, defined using ISO 3166-1 alpha-2 country codes with optional variants
          - `age` 'CHILD' | 'TEENAGER' | 'ADULT' | 'ELDERLY', required: How old the caller sounds and behaves. Only ages the persona's accent has a voice for are accepted; defaults to ADULT, which every accent supports.
          - `gender` 'MALE' | 'FEMALE', required: Gender of the persona
          - `backgroundNoise` 'NONE' | 'AIRPORT' | 'CHILDREN_PLAYING' | 'CITY' | 'COFFEE_SHOP' | 'CONSTRUCTION' | 'CRYING_BABY' | 'DRIVING' | 'LIBRARY' | 'OFFICE' | 'THUNDERSTORM' | 'TRAIN', required: Background noise setting
          - `speechPace` 'SUPER_SLOW' | 'SLOW' | 'NORMAL' | 'FAST' | 'SUPER_FAST', required: Speech pace of the persona
          - `speechClarity` 'CLEAR' | 'VAGUE' | 'RAMBLING', required: Speech clarity of the persona
          - `hasDisfluencies` boolean, required: Whether the persona uses filler words like "um" and "uh"
          - `baseEmotion` 'NEUTRAL' | 'CHEERFUL' | 'CONFUSED' | 'FRUSTRATED' | 'SKEPTICAL' | 'RUSHED' | 'DISTRACTED' | 'ANGRY' | 'ANXIOUS' | 'SAD', required: Base emotional state of the persona
          - `intentClarity` 'CLEAR' | 'INDIRECT' | 'VAGUE', required: How clearly the persona expresses their intentions
          - `confirmationStyle` 'EXPLICIT' | 'VAGUE', required: How the persona confirms information
          - `memoryReliability` 'HIGH' | 'LOW', required: How reliable the persona's memory is
          - `responseTiming` 'RELAXED' | 'NORMAL' | 'QUICK' | 'BARGE_IN', required: Deprecated and inert: it no longer affects the call. It set how long the persona waited once the agent stopped talking, and measured across production simulations it moved the reply gap by less than the noise floor, because model and speech latency dominate it. Every persona now uses one voice-activity profile. Use `interruption` for a caller who talks over the agent. Still accepted and stored so existing clients keep working. BARGE_IN is stored as `responseTiming: QUICK` with `interruption: OCCASIONAL`.
          - `interruption` 'OFF' | 'BACKCHANNEL' | 'OCCASIONAL' | 'HEAVY', required: How much the persona talks over the agent while it is still speaking. OFF waits its turn. BACKCHANNEL makes listening noises ("mm-hm") over the agent without taking the floor, which tests whether the agent wrongly stops for them. OCCASIONAL adds cutting in on some long agent turns, HEAVY on most of them. Timing is randomised per turn, so two runs of the same persona do not interrupt at identical moments.
          - `backstoryPrompt` string, nullable: Background story and behavioral patterns for the persona
          - `idleMessages` string[], nullable, required: Messages the persona will say when the agent goes silent during a call. null = "Automatic": language-appropriate defaults are used at call time.
          - `idleTimeoutSeconds` integer, required: Seconds of silence before the persona sends an idle message
          - `idleMessageMaxSpokenCount` integer, required: Maximum number of idle messages the persona will send before giving up
          - `idleMessageResetCountOnUserSpeechEnabled` boolean, required: Whether the idle message counter resets when the agent speaks
          - `properties` object, required: Additional custom properties about the persona
          - `phoneNumber` string: The E.164 number every call with this persona uses, when Roark has pinned one for your project. Present only when set; read-only.
          - `createdAt` string, required: Creation timestamp
          - `updatedAt` string, required: Last update timestamp
    - VoicemailCustomerFlow: A flow that leaves a voicemail. Curated by Roark, read-only.
      - `id` string, uuid, required
      - `title` string, required
      - `description` string, nullable
      - `source` 'SYSTEM' | 'CUSTOM', required
      - `createdAt` string, required: Creation timestamp in ISO 8601 format
      - `updatedAt` string, required: Last update timestamp in ISO 8601 format
      - `agents` object[], required: The agents this flow is run against.
        - `id` string, uuid, required: Unique identifier of the agent
        - `name` string, required: Name of the agent
        - `description` string, nullable, required: Description of the agent
        - `customId` string, nullable, required: Custom identifier for the agent
        - `createdAt` string, required: Creation timestamp in ISO 8601 format
        - `updatedAt` string, required: Last update timestamp in ISO 8601 format
      - `agentExpectations` FlowExpectation[], required
        - `id` string, uuid, required
        - `prompt` string, required: What the agent under test is graded against.
      - `type` 'VOICEMAIL', required
      - `happyPath` VoicemailFlowHappyPath, required: One voicemail greeting.
        - `id` string, uuid, required
        - `title` string, required
        - `systemKey` string, nullable
        - `precededByCustomerFlowId` string, uuid, nullable, required
        - `precededByCustomerFlowVariantId` string, uuid, nullable, required
        - `isGenerated` boolean, required
        - `includeFlowExpectations` boolean, required: Whether this variant is graded on the flow's agentExpectations as well as its additionalExpectations. False grades it on its additionalExpectations only. Always true on the happy path.
        - `createdAt` string, required: Creation timestamp in ISO 8601 format
        - `updatedAt` string, required: Last update timestamp in ISO 8601 format
        - `personaOverride` object, nullable, required: The persona this runs as instead of the happy path's. Null means it inherits.
          - `id` string, uuid, required: Unique identifier of the persona
          - `name` string, required: The name the agent will identify as during conversations
          - `displayName` string, nullable: Label shown in place of the name across the dashboard (e.g. a short descriptor like "Irate Escalator"). The persona still identifies as `name` on calls. Omit or set null to display the name itself.
          - `description` string, nullable: Human-readable description of the persona
          - `language` 'EN' | 'ES' | 'DE' | 'HI' | 'FR' | 'NL' | 'AR' | 'EL' | 'IT' | 'ID' | 'TH' | 'JA' | 'TL' | 'MS' | 'ZH' | 'TR' | 'PT' | 'HE', required: Primary language ISO 639-1 code for the persona
          - `secondaryLanguage` 'EN', nullable: Secondary language ISO 639-1 code for code-switching (e.g., Hinglish, Spanglish)
          - `understoodLanguages` string[], required: Languages the persona can understand. Multilingual combinations are limited by multilingual speech recognition support.
          - `accent` 'US' | 'US_X_SOUTH' | 'GB' | 'ES' | 'DE' | 'IN' | 'FR' | 'NL' | 'SA' | 'GR' | 'AU' | 'IT' | 'ID' | 'TH' | 'JP' | 'NZ' | 'PH' | 'SG' | 'MY' | 'HK' | 'TR' | 'PT' | 'IL', required: Accent of the persona, defined using ISO 3166-1 alpha-2 country codes with optional variants
          - `age` 'CHILD' | 'TEENAGER' | 'ADULT' | 'ELDERLY', required: How old the caller sounds and behaves. Only ages the persona's accent has a voice for are accepted; defaults to ADULT, which every accent supports.
          - `gender` 'MALE' | 'FEMALE', required: Gender of the persona
          - `backgroundNoise` 'NONE' | 'AIRPORT' | 'CHILDREN_PLAYING' | 'CITY' | 'COFFEE_SHOP' | 'CONSTRUCTION' | 'CRYING_BABY' | 'DRIVING' | 'LIBRARY' | 'OFFICE' | 'THUNDERSTORM' | 'TRAIN', required: Background noise setting
          - `speechPace` 'SUPER_SLOW' | 'SLOW' | 'NORMAL' | 'FAST' | 'SUPER_FAST', required: Speech pace of the persona
          - `speechClarity` 'CLEAR' | 'VAGUE' | 'RAMBLING', required: Speech clarity of the persona
          - `hasDisfluencies` boolean, required: Whether the persona uses filler words like "um" and "uh"
          - `baseEmotion` 'NEUTRAL' | 'CHEERFUL' | 'CONFUSED' | 'FRUSTRATED' | 'SKEPTICAL' | 'RUSHED' | 'DISTRACTED' | 'ANGRY' | 'ANXIOUS' | 'SAD', required: Base emotional state of the persona
          - `intentClarity` 'CLEAR' | 'INDIRECT' | 'VAGUE', required: How clearly the persona expresses their intentions
          - `confirmationStyle` 'EXPLICIT' | 'VAGUE', required: How the persona confirms information
          - `memoryReliability` 'HIGH' | 'LOW', required: How reliable the persona's memory is
          - `responseTiming` 'RELAXED' | 'NORMAL' | 'QUICK' | 'BARGE_IN', required: Deprecated and inert: it no longer affects the call. It set how long the persona waited once the agent stopped talking, and measured across production simulations it moved the reply gap by less than the noise floor, because model and speech latency dominate it. Every persona now uses one voice-activity profile. Use `interruption` for a caller who talks over the agent. Still accepted and stored so existing clients keep working. BARGE_IN is stored as `responseTiming: QUICK` with `interruption: OCCASIONAL`.
          - `interruption` 'OFF' | 'BACKCHANNEL' | 'OCCASIONAL' | 'HEAVY', required: How much the persona talks over the agent while it is still speaking. OFF waits its turn. BACKCHANNEL makes listening noises ("mm-hm") over the agent without taking the floor, which tests whether the agent wrongly stops for them. OCCASIONAL adds cutting in on some long agent turns, HEAVY on most of them. Timing is randomised per turn, so two runs of the same persona do not interrupt at identical moments.
          - `backstoryPrompt` string, nullable: Background story and behavioral patterns for the persona
          - `idleMessages` string[], nullable, required: Messages the persona will say when the agent goes silent during a call. null = "Automatic": language-appropriate defaults are used at call time.
          - `idleTimeoutSeconds` integer, required: Seconds of silence before the persona sends an idle message
          - `idleMessageMaxSpokenCount` integer, required: Maximum number of idle messages the persona will send before giving up
          - `idleMessageResetCountOnUserSpeechEnabled` boolean, required: Whether the idle message counter resets when the agent speaks
          - `properties` object, required: Additional custom properties about the persona
          - `phoneNumber` string: The E.164 number every call with this persona uses, when Roark has pinned one for your project. Present only when set; read-only.
          - `createdAt` string, required: Creation timestamp
          - `updatedAt` string, required: Last update timestamp
        - `environment` EnvironmentResponse, required: A simulation environment: the ambient conditions a customer flow variant runs under. The list includes both your own and the ones Roark curates for every project.
          - `id` string, uuid, required
          - `name` string, required
          - `description` string, nullable
          - `backgroundNoise` 'NONE' | 'AIRPORT' | 'CHILDREN_PLAYING' | 'CITY' | 'COFFEE_SHOP' | 'CONSTRUCTION' | 'CRYING_BABY' | 'DRIVING' | 'LIBRARY' | 'OFFICE' | 'THUNDERSTORM' | 'TRAIN', required
          - `backgroundNoiseVolume` number, required
          - `createdAt` string, required: Creation timestamp in ISO 8601 format
          - `updatedAt` string, required: Last update timestamp in ISO 8601 format
        - `additionalExpectations` FlowExpectation[], required: Graded on top of the flow's own expectations, for this variant only.
          - `id` string, uuid, required
          - `prompt` string, required: What the agent under test is graded against.
        - `type` 'VOICEMAIL', required
      - `edgeCases` VoicemailFlowVariant[], required: Every other way of running this flow.
        - `id` string, uuid, required
        - `title` string, required
        - `systemKey` string, nullable
        - `precededByCustomerFlowId` string, uuid, nullable, required
        - `precededByCustomerFlowVariantId` string, uuid, nullable, required
        - `isGenerated` boolean, required
        - `includeFlowExpectations` boolean, required: Whether this variant is graded on the flow's agentExpectations as well as its additionalExpectations. False grades it on its additionalExpectations only. Always true on the happy path.
        - `createdAt` string, required: Creation timestamp in ISO 8601 format
        - `updatedAt` string, required: Last update timestamp in ISO 8601 format
        - `personaOverride` object, nullable, required: The persona this runs as instead of the happy path's. Null means it inherits.
          - `id` string, uuid, required: Unique identifier of the persona
          - `name` string, required: The name the agent will identify as during conversations
          - `displayName` string, nullable: Label shown in place of the name across the dashboard (e.g. a short descriptor like "Irate Escalator"). The persona still identifies as `name` on calls. Omit or set null to display the name itself.
          - `description` string, nullable: Human-readable description of the persona
          - `language` 'EN' | 'ES' | 'DE' | 'HI' | 'FR' | 'NL' | 'AR' | 'EL' | 'IT' | 'ID' | 'TH' | 'JA' | 'TL' | 'MS' | 'ZH' | 'TR' | 'PT' | 'HE', required: Primary language ISO 639-1 code for the persona
          - `secondaryLanguage` 'EN', nullable: Secondary language ISO 639-1 code for code-switching (e.g., Hinglish, Spanglish)
          - `understoodLanguages` string[], required: Languages the persona can understand. Multilingual combinations are limited by multilingual speech recognition support.
          - `accent` 'US' | 'US_X_SOUTH' | 'GB' | 'ES' | 'DE' | 'IN' | 'FR' | 'NL' | 'SA' | 'GR' | 'AU' | 'IT' | 'ID' | 'TH' | 'JP' | 'NZ' | 'PH' | 'SG' | 'MY' | 'HK' | 'TR' | 'PT' | 'IL', required: Accent of the persona, defined using ISO 3166-1 alpha-2 country codes with optional variants
          - `age` 'CHILD' | 'TEENAGER' | 'ADULT' | 'ELDERLY', required: How old the caller sounds and behaves. Only ages the persona's accent has a voice for are accepted; defaults to ADULT, which every accent supports.
          - `gender` 'MALE' | 'FEMALE', required: Gender of the persona
          - `backgroundNoise` 'NONE' | 'AIRPORT' | 'CHILDREN_PLAYING' | 'CITY' | 'COFFEE_SHOP' | 'CONSTRUCTION' | 'CRYING_BABY' | 'DRIVING' | 'LIBRARY' | 'OFFICE' | 'THUNDERSTORM' | 'TRAIN', required: Background noise setting
          - `speechPace` 'SUPER_SLOW' | 'SLOW' | 'NORMAL' | 'FAST' | 'SUPER_FAST', required: Speech pace of the persona
          - `speechClarity` 'CLEAR' | 'VAGUE' | 'RAMBLING', required: Speech clarity of the persona
          - `hasDisfluencies` boolean, required: Whether the persona uses filler words like "um" and "uh"
          - `baseEmotion` 'NEUTRAL' | 'CHEERFUL' | 'CONFUSED' | 'FRUSTRATED' | 'SKEPTICAL' | 'RUSHED' | 'DISTRACTED' | 'ANGRY' | 'ANXIOUS' | 'SAD', required: Base emotional state of the persona
          - `intentClarity` 'CLEAR' | 'INDIRECT' | 'VAGUE', required: How clearly the persona expresses their intentions
          - `confirmationStyle` 'EXPLICIT' | 'VAGUE', required: How the persona confirms information
          - `memoryReliability` 'HIGH' | 'LOW', required: How reliable the persona's memory is
          - `responseTiming` 'RELAXED' | 'NORMAL' | 'QUICK' | 'BARGE_IN', required: Deprecated and inert: it no longer affects the call. It set how long the persona waited once the agent stopped talking, and measured across production simulations it moved the reply gap by less than the noise floor, because model and speech latency dominate it. Every persona now uses one voice-activity profile. Use `interruption` for a caller who talks over the agent. Still accepted and stored so existing clients keep working. BARGE_IN is stored as `responseTiming: QUICK` with `interruption: OCCASIONAL`.
          - `interruption` 'OFF' | 'BACKCHANNEL' | 'OCCASIONAL' | 'HEAVY', required: How much the persona talks over the agent while it is still speaking. OFF waits its turn. BACKCHANNEL makes listening noises ("mm-hm") over the agent without taking the floor, which tests whether the agent wrongly stops for them. OCCASIONAL adds cutting in on some long agent turns, HEAVY on most of them. Timing is randomised per turn, so two runs of the same persona do not interrupt at identical moments.
          - `backstoryPrompt` string, nullable: Background story and behavioral patterns for the persona
          - `idleMessages` string[], nullable, required: Messages the persona will say when the agent goes silent during a call. null = "Automatic": language-appropriate defaults are used at call time.
          - `idleTimeoutSeconds` integer, required: Seconds of silence before the persona sends an idle message
          - `idleMessageMaxSpokenCount` integer, required: Maximum number of idle messages the persona will send before giving up
          - `idleMessageResetCountOnUserSpeechEnabled` boolean, required: Whether the idle message counter resets when the agent speaks
          - `properties` object, required: Additional custom properties about the persona
          - `phoneNumber` string: The E.164 number every call with this persona uses, when Roark has pinned one for your project. Present only when set; read-only.
          - `createdAt` string, required: Creation timestamp
          - `updatedAt` string, required: Last update timestamp
        - `environment` EnvironmentResponse, required: A simulation environment: the ambient conditions a customer flow variant runs under. The list includes both your own and the ones Roark curates for every project.
          - `id` string, uuid, required
          - `name` string, required
          - `description` string, nullable
          - `backgroundNoise` 'NONE' | 'AIRPORT' | 'CHILDREN_PLAYING' | 'CITY' | 'COFFEE_SHOP' | 'CONSTRUCTION' | 'CRYING_BABY' | 'DRIVING' | 'LIBRARY' | 'OFFICE' | 'THUNDERSTORM' | 'TRAIN', required
          - `backgroundNoiseVolume` number, required
          - `createdAt` string, required: Creation timestamp in ISO 8601 format
          - `updatedAt` string, required: Last update timestamp in ISO 8601 format
        - `additionalExpectations` FlowExpectation[], required: Graded on top of the flow's own expectations, for this variant only.
          - `id` string, uuid, required
          - `prompt` string, required: What the agent under test is graded against.
        - `type` 'VOICEMAIL', required

## Other responses

- `400`: Bad Request
- `401`: Unauthorized
- `403`: Forbidden
- `404`: Not Found
- `429`: Too Many Requests
- `500`: Internal Server Error

## Changes

> 188 revisions in range; 1 not diffed.

- **2026-10-15** `f747fc3e482c`: 6 info
  - added the required property `data/oneOf[subschema #1: Scripted]/edgeCases/items/includeFlowExpectations` to the response with the `201` status
  - added the required property `data/oneOf[subschema #1: Scripted]/happyPath/oneOf[subschema #1: Scripted]/allOf[subschema #1: Scripted]/includeFlowExpectations` to the response with the `201` status
  - added the required property `data/oneOf[subschema #2: Improv]/edgeCases/items/includeFlowExpectations` to the response with the `201` status
  - added the required property `data/oneOf[subschema #2: Improv]/happyPath/oneOf[subschema #1: Improv]/allOf[subschema #1: Improv]/includeFlowExpectations` to the response with the `201` status
  - …2 more
- **2026-10-02** `e32b31fc8f98`: 8 info
  - added the optional property `data/oneOf[subschema #1: Scripted]/edgeCases/items/personaOverride/phoneNumber` to the response with the `201` status
  - added the optional property `data/oneOf[subschema #1: Scripted]/happyPath/oneOf[subschema #1: Scripted]/allOf[subschema #1: Scripted]/personaOverride/phoneNumber` to the response with the `201` status
  - added the optional property `data/oneOf[subschema #2: Improv]/edgeCases/items/handoffPersonas/items/phoneNumber` to the response with the `201` status
  - added the optional property `data/oneOf[subschema #2: Improv]/edgeCases/items/personaOverride/phoneNumber` to the response with the `201` status
  - …4 more
- …earlier changes not shown

[Operation history](https://skmtc.dev/roarkhq/apis/roark-analytics-api/changes/v1/customer-flow/:flowId/duplicate/post.md)

---

[API](https://skmtc.dev/roarkhq/apis/roark-analytics-api.md) - [All operations](https://skmtc.dev/roarkhq/apis/roark-analytics-api/llms.txt) - [OpenAPI document](https://skmtc.dev/roarkhq/apis/roark-analytics-api/revisions/efc3377249d1?raw)
