{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://schema.spbx.tel/v1/structs.realm.lib",
  "$ref": "#/$defs/structs.realm",
  "$defs": {
    "conditions.caller_number": {
      "properties": {
        "equals": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of caller numbers to match against."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "equals"
      ]
    },
    "conditions.caller_regex": {
      "properties": {
        "matches": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of regex patterns to match against the caller's number.",
          "x-validate": "min=1,dive,required"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "matches"
      ]
    },
    "conditions.dialed_internal": {
      "properties": {},
      "additionalProperties": false,
      "type": "object"
    },
    "conditions.dialed_number": {
      "properties": {
        "equals": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of dialed numbers to match against.",
          "x-validate": "min=1,dive,required"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "equals"
      ]
    },
    "conditions.dialed_regex": {
      "properties": {
        "matches": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of regex patterns to match against the dialed number.",
          "x-validate": "min=1,dive,required"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "matches"
      ]
    },
    "conditions.eval": {
      "properties": {
        "javascript": {
          "type": "string",
          "description": "Custom JavaScript expression to evaluate. Example: 'return true;'",
          "x-validate": "required"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "javascript"
      ]
    },
    "conditions.from_internal": {
      "properties": {},
      "additionalProperties": false,
      "type": "object"
    },
    "conditions.from_trunk": {
      "properties": {
        "trunk_id": {
          "type": "string",
          "description": "ID of the trunk to check against (e.g. \"trunks/provider\"). If omitted, any trunk will match.",
          "x-validate": "omitempty,validId=trunks."
        }
      },
      "additionalProperties": false,
      "type": "object"
    },
    "conditions.is_deafened": {
      "properties": {
        "call": {
          "type": "string",
          "description": "ID of the call to check (supports %variable% interpolation, e.g. %parent_call_id%). Omitted: the call evaluating this condition. True when the call is deafened client-side (re-INVITE a=sendonly/a=inactive) OR server-side (deafen flow action); false for unknown call IDs.",
          "x-validate": "omitempty"
        }
      },
      "additionalProperties": false,
      "type": "object"
    },
    "conditions.is_muted": {
      "properties": {
        "call": {
          "type": "string",
          "description": "ID of the call to check (supports %variable% interpolation, e.g. %parent_call_id%). Omitted: the call evaluating this condition. True when the call is muted client-side (re-INVITE a=recvonly/a=inactive) OR server-side (mute flow action); false for unknown call IDs.",
          "x-validate": "omitempty"
        }
      },
      "additionalProperties": false,
      "type": "object"
    },
    "conditions.time": {
      "properties": {
        "weekdays": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of weekdays when the condition is active (mon, tue, wed, thu, fri, sat, sun).",
          "x-validate": "omitempty,dive,oneof=mon tue wed thu fri sat sun"
        },
        "months": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of months when the condition is active (jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec).",
          "x-validate": "omitempty,dive,oneof=jan feb mar apr may jun jul aug sep oct nov dec"
        },
        "month_days": {
          "items": {
            "type": "integer"
          },
          "type": "array",
          "description": "List of days of the month when the condition is active (1-31).",
          "x-validate": "omitempty,dive,gte=1,lte=31"
        },
        "start_hour": {
          "type": "integer",
          "description": "Hour when the condition starts being active (0-23).",
          "x-validate": "omitempty,gte=0,lte=23"
        },
        "start_minute": {
          "type": "integer",
          "description": "Minute when the condition starts being active (0-59).",
          "x-validate": "omitempty,gte=0,lte=59"
        },
        "end_hour": {
          "type": "integer",
          "description": "Hour when the condition stops being active (0-23).",
          "x-validate": "omitempty,gte=0,lte=23"
        },
        "end_minute": {
          "type": "integer",
          "description": "Minute when the condition stops being active (0-59).",
          "x-validate": "omitempty,gte=0,lte=59"
        }
      },
      "additionalProperties": false,
      "type": "object"
    },
    "conditions.variable_equals": {
      "properties": {
        "variable": {
          "type": "string",
          "description": "Name of the variable to check.",
          "x-validate": "required"
        },
        "value": {
          "type": "string",
          "description": "Value to compare against the variable.",
          "x-validate": "required"
        },
        "global": {
          "type": "boolean",
          "description": "Whether the variable is global. If not specified, global variables are checked preferably.",
          "x-validate": "omitempty"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "variable",
        "value"
      ]
    },
    "conditions.variable_more_than": {
      "properties": {
        "variable": {
          "type": "string",
          "description": "Name of the variable to check.",
          "x-validate": "required"
        },
        "value": {
          "type": "number",
          "description": "Numeric value to compare against the variable.",
          "x-validate": "required"
        },
        "global": {
          "type": "boolean",
          "description": "Whether the variable is global. If not specified, global variables are checked preferably.",
          "x-validate": "omitempty"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "variable",
        "value"
      ]
    },
    "conditions.variable_regex": {
      "properties": {
        "variable": {
          "type": "string",
          "description": "Name of the variable to check.",
          "x-validate": "required"
        },
        "matches": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of regex patterns to match against the variable.",
          "x-validate": "min=1,dive,required"
        },
        "global": {
          "type": "boolean",
          "description": "Whether the variable is global. If not specified, global variables are checked preferably.",
          "x-validate": "omitempty"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "variable",
        "matches"
      ]
    },
    "flows.answer": {
      "properties": {},
      "additionalProperties": false,
      "type": "object"
    },
    "flows.copy_variable": {
      "properties": {
        "source_variable": {
          "type": "string",
          "description": "Name of the variable to copy from.",
          "x-validate": "required"
        },
        "destination_variable": {
          "type": "string",
          "description": "Name of the variable to copy to.",
          "x-validate": "required"
        },
        "source_global": {
          "type": "boolean",
          "description": "Whether the source variable is global. If not specified, global variables are checked preferably.",
          "x-validate": "omitempty"
        },
        "destination_global": {
          "type": "boolean",
          "description": "Whether the destination variable is global. If not specified, local is used.",
          "x-validate": "omitempty"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "source_variable",
        "destination_variable"
      ]
    },
    "flows.deafen": {
      "properties": {
        "call": {
          "type": "string",
          "description": "ID of the call to deafen (supports %variable% interpolation, e.g. %parent_call_id%). Omitted: the call executing this flow.",
          "x-validate": "omitempty"
        },
        "deafened": {
          "type": "boolean",
          "description": "Desired server-side deafen state. Omitted: true (deafen). Set false to lift the server-side deafen; a client-signaled deafen (re-INVITE a=sendonly/a=inactive) is unaffected. While deafened, no bridged audio reaches the call — its mixer runs empty, so a started silence_player takes over. Sounds played directly to the call are NOT cut.",
          "x-validate": "omitempty"
        }
      },
      "additionalProperties": false,
      "type": "object"
    },
    "flows.delete_file": {
      "properties": {
        "file_path": {
          "type": "string",
          "description": "Path to the file to delete from the realm storage.",
          "x-validate": "required,max=256"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "file_path"
      ]
    },
    "flows.detect_fax": {
      "properties": {
        "dial_plan": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "Flows to run INLINE when a fax CNG tone is detected (like sub_flow: the surrounding dial plan resumes after this list finishes — unless the list hangs the call up).",
          "x-validate": "required,min=1,dive,required,validId=flows.."
        },
        "timeout_seconds": {
          "type": "integer",
          "description": "Maximum time to listen for the tone, in seconds. detect_fax BLOCKS the dial plan while listening; on timeout it sets the call variable 'fax_detected' to 'false' and the dial plan continues. If not specified, it listens until the call ends. Wrap detect_fax in a sub_task for background (fire-and-forget) detection. detect_fax never touches audio playback: if a silence player is running, stop it in dial_plan (stop_audio_playback) — fax carriers tolerate no injected audio.",
          "x-validate": "omitempty,gte=1"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "dial_plan"
      ]
    },
    "flows.dial": {
      "properties": {
        "target": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "External numbers or identifiers to dial.",
          "x-validate": "omitempty,min=1,dive,required"
        },
        "internal_target": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of internal extension numbers to dial.",
          "x-validate": "omitempty,min=1,dive,required,validId=extensions."
        },
        "trunk": {
          "type": "string",
          "description": "Trunk to use for external dials (e.g. \"trunks/provider\").",
          "x-validate": "omitempty,validId=trunks."
        },
        "caller_id": {
          "type": "string",
          "description": "Caller ID used in the From header of dialed calls. Supports %variable% interpolation (e.g. %caller%). When unset, trunk dials fall back to the trunk's caller_id, then the trunk username.",
          "x-validate": "omitempty"
        },
        "dial_plan": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "Flow to execute when the call is answered. Entries may pass scoped call variables using call syntax, e.g. flows/sub_flow/my_flow(var1=\"val\", var2=\"%caller%\"): the named variables are set before the flow executes and restored afterwards; values must be double-quoted and support %variable% interpolation.",
          "x-validate": "min=1,dive,required,validId=flows.."
        },
        "wait_for_answer": {
          "type": "boolean",
          "description": "Whether to wait for the call to be answered before proceeding.",
          "x-validate": "omitempty"
        },
        "cancel_other_dials": {
          "type": "boolean",
          "description": "Whether to cancel other ongoing dial attempts when one is answered. Defaults to true.",
          "x-validate": "omitempty"
        },
        "skip_busy": {
          "type": "boolean",
          "description": "Whether to skip busy extensions (extensions with an active call). Defaults to false.",
          "x-validate": "omitempty"
        },
        "children": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "Other dial flows to import targets from. Other options of these flows will be ignored.",
          "x-validate": "omitempty,min=1,dive,required,validId=flows.dial."
        },
        "variables": {
          "additionalProperties": {
            "type": "string"
          },
          "type": "object",
          "description": "Extra variables set on every dialed leg, on top of the leg's built-ins (caller, dialed, trunk, parent_call_id — which win on collision). Values support %variable% interpolation evaluated in the DIALING call's context at dial time. This is how a task hands a new leg the call IDs its dial plan should bridge to, e.g. transfer_peer: \"%transfer_peer_call_ids%\".",
          "x-validate": "omitempty,dive,max=256"
        },
        "timeout_seconds": {
          "type": "integer",
          "description": "Maximum time in seconds to wait for the call to be answered.",
          "x-validate": "omitempty,gte=1"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "dial_plan"
      ]
    },
    "flows.dual_tone_multi_frequency": {
      "properties": {
        "sound": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "Sound to play while waiting for DTMF input.",
          "x-validate": "required,min=1,dive,required,validId=sounds.."
        },
        "stop_on_digit": {
          "type": "boolean",
          "description": "Whether to stop the sound when a DTMF digit is received.",
          "x-validate": "required"
        },
        "variable": {
          "type": "string",
          "description": "Variable name to store the received DTMF digits.",
          "x-validate": "required"
        },
        "global": {
          "type": "boolean",
          "description": "Whether the variable is global. If not specified, local is used.",
          "x-validate": "omitempty"
        },
        "digit_count": {
          "type": "integer",
          "description": "Number of DTMF digits to collect before proceeding.",
          "x-validate": "required,gte=1,lte=32"
        },
        "end_digit": {
          "type": "string",
          "description": "Optional digit that, when pressed, will end the DTMF input collection.",
          "x-validate": "omitempty,len=1"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "sound",
        "stop_on_digit",
        "variable"
      ]
    },
    "flows.eval": {
      "properties": {
        "javascript": {
          "type": "string",
          "description": "JavaScript code to execute. Can return null or a flow ID to redirect. Example: 'return null;' or 'return \"flows/dial/support\";'",
          "x-validate": "required"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "javascript"
      ]
    },
    "flows.for_each": {
      "properties": {
        "loop_flow": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of flows to execute in each iteration. Entries may pass scoped call variables using call syntax, e.g. flows/sub_flow/my_flow(var1=\"val\", var2=\"%caller%\"): the named variables are set before the flow executes and restored afterwards; values must be double-quoted and support %variable% interpolation.",
          "x-validate": "required,min=1,dive,required,validId=flows.."
        },
        "list_variable": {
          "type": "string",
          "description": "The variable containing the list (csv) to iterate over.",
          "x-validate": "required"
        },
        "item_variable": {
          "type": "string",
          "description": "The variable name to assign the current item to in each iteration.",
          "x-validate": "required"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "loop_flow",
        "list_variable",
        "item_variable"
      ]
    },
    "flows.get_call_transcription": {
      "properties": {
        "engine": {
          "type": "string",
          "description": "Name of the transcription task (the engine name used in set_transcription_task) whose transcript is read. Works while the task runs and after it was stopped.",
          "x-validate": "required"
        },
        "variable": {
          "type": "string",
          "description": "Call variable the transcript accumulated so far is written to. The variable changes ONLY when this flow runs — transcription never mutates variables in the background. Run unset_transcription_task first to flush pending results and get the complete text.",
          "x-validate": "required"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "engine",
        "variable"
      ]
    },
    "flows.get_calls_listening_to": {
      "properties": {
        "output_variable": {
          "type": "string",
          "description": "Variable name where the resulting CSV call ID list will be stored.",
          "x-validate": "required,max=64"
        },
        "call_id": {
          "type": "string",
          "description": "Call ID to find listening calls for. If not provided, uses the current call's ID.",
          "x-validate": "max=64"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "output_variable"
      ]
    },
    "flows.get_calls_sending_audio_to": {
      "properties": {
        "output_variable": {
          "type": "string",
          "description": "Variable name where the resulting CSV call ID list will be stored.",
          "x-validate": "required,max=64"
        },
        "call_id": {
          "type": "string",
          "description": "Call ID to find sending calls for. If not provided, uses the current call's ID.",
          "x-validate": "max=64"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "output_variable"
      ]
    },
    "flows.get_file_list": {
      "properties": {
        "output_variable": {
          "type": "string",
          "description": "Variable name where the resulting CSV file list will be stored.",
          "x-validate": "required,max=64"
        },
        "file_path_prefix": {
          "type": "string",
          "description": "If set, includes only files with paths that start with this prefix.",
          "x-validate": "omitempty,max=256"
        },
        "modified_after": {
          "type": "string",
          "description": "If set, includes only files modified after this RFC 3339 timestamp (e.g. 2026-07-10T08:00:00Z).",
          "x-validate": "omitempty,datetime=2006-01-02T15:04:05Z07:00"
        },
        "modified_before": {
          "type": "string",
          "description": "If set, includes only files modified before this RFC 3339 timestamp (e.g. 2026-07-10T08:00:00Z).",
          "x-validate": "omitempty,datetime=2006-01-02T15:04:05Z07:00"
        },
        "min_size_bytes": {
          "type": "integer",
          "description": "If set, includes only files at least this size in bytes (inclusive).",
          "x-validate": "omitempty,min=0"
        },
        "max_size_bytes": {
          "type": "integer",
          "description": "If set, includes only files at most this size in bytes (inclusive).",
          "x-validate": "omitempty,min=0"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "output_variable"
      ]
    },
    "flows.get_listening": {
      "properties": {
        "output_variable": {
          "type": "string",
          "description": "Variable name where the resulting CSV call ID list will be stored.",
          "x-validate": "required,max=64"
        },
        "call_id": {
          "type": "string",
          "description": "Call whose listening list to read (supports %variable% interpolation). If not provided, uses the current call's ID.",
          "x-validate": "max=64"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "output_variable"
      ]
    },
    "flows.get_sending": {
      "properties": {
        "output_variable": {
          "type": "string",
          "description": "Variable name where the resulting CSV call ID list will be stored.",
          "x-validate": "required,max=64"
        },
        "call_id": {
          "type": "string",
          "description": "Call whose send list to read (supports %variable% interpolation). If not provided, uses the current call's ID.",
          "x-validate": "max=64"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "output_variable"
      ]
    },
    "flows.hangup": {
      "properties": {
        "reason": {
          "type": "string",
          "description": "Optional reason for the hangup (max 256 characters).",
          "x-validate": "omitempty,max=256"
        },
        "code": {
          "type": "integer",
          "description": "Optional SIP hangup code (400-603).",
          "x-validate": "omitempty,gte=400,lte=603"
        }
      },
      "additionalProperties": false,
      "type": "object"
    },
    "flows.if": {
      "properties": {
        "condition": {
          "type": "string",
          "description": "Identifier of the condition to evaluate.",
          "x-validate": "required,validId=conditions.."
        },
        "then": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of flows to execute if the condition is true. Entries may pass scoped call variables using call syntax, e.g. flows/sub_flow/my_flow(var1=\"val\", var2=\"%caller%\"): the named variables are set before the flow executes and restored afterwards; values must be double-quoted and support %variable% interpolation.",
          "x-validate": "required,min=1,dive,required,validId=flows.."
        },
        "else": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of flows to execute if the condition is false. Entries may pass scoped call variables using call syntax, e.g. flows/sub_flow/my_flow(var1=\"val\", var2=\"%caller%\"): the named variables are set before the flow executes and restored afterwards; values must be double-quoted and support %variable% interpolation.",
          "x-validate": "omitempty,min=1,dive,required,validId=flows.."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "condition",
        "then"
      ]
    },
    "flows.listen_to_call": {
      "properties": {
        "call_ids": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of call IDs to listen to.",
          "x-validate": "min=1,dive,required"
        },
        "call": {
          "type": "string",
          "description": "ID of the call to act as (supports %variable% interpolation, e.g. %transfer_peer_call_ids%). Omitted: the call executing this flow. Lets a task bridge or un-bridge two OTHER calls, e.g. during transfers or conferences.",
          "x-validate": "omitempty,max=64"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "call_ids"
      ]
    },
    "flows.log": {
      "properties": {
        "message": {
          "type": "string",
          "description": "Log message to record (max 1024 characters).",
          "x-validate": "required,max=1024"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "message"
      ]
    },
    "flows.loop": {
      "properties": {
        "loop_flow": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of flows to execute in each iteration. Entries may pass scoped call variables using call syntax, e.g. flows/sub_flow/my_flow(var1=\"val\", var2=\"%caller%\"): the named variables are set before the flow executes and restored afterwards; values must be double-quoted and support %variable% interpolation.",
          "x-validate": "required,min=1,dive,required,validId=flows.."
        },
        "breakout_condition": {
          "type": "string",
          "description": "Condition evaluated at the end of each iteration. If true, the loop terminates.",
          "x-validate": "omitempty,validId=conditions.."
        },
        "breakout_flow": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of flows to execute when the breakout condition is met. Entries may pass scoped call variables using call syntax, e.g. flows/sub_flow/my_flow(var1=\"val\", var2=\"%caller%\"): the named variables are set before the flow executes and restored afterwards; values must be double-quoted and support %variable% interpolation.",
          "x-validate": "omitempty,min=1,dive,required,validId=flows.."
        },
        "max_loops": {
          "type": "integer",
          "description": "Maximum number of iterations. If not specified and no breakout condition is set, loops indefinitely.",
          "x-validate": "omitempty,gte=2"
        },
        "max_loop_flow": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of flows to execute when maximum iterations are reached. Entries may pass scoped call variables using call syntax, e.g. flows/sub_flow/my_flow(var1=\"val\", var2=\"%caller%\"): the named variables are set before the flow executes and restored afterwards; values must be double-quoted and support %variable% interpolation.",
          "x-validate": "omitempty,min=1,dive,required,validId=flows.."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "loop_flow"
      ]
    },
    "flows.mute": {
      "properties": {
        "call": {
          "type": "string",
          "description": "ID of the call to mute (supports %variable% interpolation, e.g. %parent_call_id%). Omitted: the call executing this flow.",
          "x-validate": "omitempty"
        },
        "muted": {
          "type": "boolean",
          "description": "Desired server-side mute state. Omitted: true (mute). Set false to lift the server-side mute; a client-signaled mute (re-INVITE a=recvonly/a=inactive) is unaffected and keeps the call muted until the client resumes. While muted, the call's audio reaches no other call.",
          "x-validate": "omitempty"
        }
      },
      "additionalProperties": false,
      "type": "object"
    },
    "flows.queue": {
      "properties": {
        "targets": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of dial flow IDs representing the targets to call.",
          "x-validate": "required,min=1,dive,required,validId=flows.dial."
        },
        "retry": {
          "type": "integer",
          "description": "Number of times to retry if no one answers. Defaults to 0.",
          "x-validate": "omitempty,gte=0"
        },
        "retry_wait_seconds": {
          "type": "integer",
          "description": "Number of seconds to wait between retry attempts. Defaults to 5.",
          "x-validate": "omitempty,gte=1"
        },
        "unanswered_seconds": {
          "type": "integer",
          "description": "If not answered within this time, the queue exits.",
          "x-validate": "omitempty,gte=1"
        },
        "unanswered_flow": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of flows to execute when not answered within the timeout. Entries may pass scoped call variables using call syntax, e.g. flows/sub_flow/my_flow(var1=\"val\", var2=\"%caller%\"): the named variables are set before the flow executes and restored afterwards; values must be double-quoted and support %variable% interpolation.",
          "x-validate": "omitempty,min=1,dive,required,validId=flows.."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "targets"
      ]
    },
    "flows.read_variable_from_file": {
      "properties": {
        "file_path": {
          "type": "string",
          "description": "Path to the file to read from the realm storage.",
          "x-validate": "required,max=256"
        },
        "output_variable": {
          "type": "string",
          "description": "Variable name where the file content will be stored.",
          "x-validate": "required,max=64"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "file_path",
        "output_variable"
      ]
    },
    "flows.receive_audio_from_call": {
      "properties": {
        "call_ids": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of call IDs to receive audio from.",
          "x-validate": "min=1,dive,required"
        },
        "call": {
          "type": "string",
          "description": "ID of the call to act as (supports %variable% interpolation, e.g. %transfer_peer_call_ids%). Omitted: the call executing this flow. Lets a task bridge or un-bridge two OTHER calls, e.g. during transfers or conferences.",
          "x-validate": "omitempty,max=64"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "call_ids"
      ]
    },
    "flows.receive_fax": {
      "properties": {
        "file": {
          "type": "string",
          "description": "Target document path in the realm storage (relative to the realm's files directory; subdirectories are created as needed) — the same location scheme as record_call. Supports %var% interpolation, plus %fax_id% — a unique id generated when the session starts. The resolved relative path is stored in the call variable 'fax_file' and the id in 'fax_id'. Give the file the extension matching 'format' (e.g. faxes/%fax_id%.pdf).",
          "x-validate": "required,max=256"
        },
        "format": {
          "type": "string",
          "description": "Output document format: 'tiff' (fax-native, CCITT-compressed) or 'pdf'. Defaults to 'tiff'.",
          "x-validate": "omitempty,oneof=tiff pdf"
        },
        "local_ident": {
          "type": "string",
          "description": "The T.30 station identity announced to the remote fax machine (max 20 characters, typically the fax number).",
          "x-validate": "omitempty,max=20"
        },
        "timeout_seconds": {
          "type": "integer",
          "description": "Maximum duration of the fax session in seconds; on expiry the session is terminated (pages already completed stay saved) and fax_result is 'failed'. Defaults to 600. receive_fax BLOCKS the dial plan until the fax completes or fails; results land in the call variables fax_result, fax_error, fax_file, fax_pages, fax_remote_ident. Delivery (e.g. email) is a separate flow: use send_email with attachment %fax_file%, exactly like the voicemail preset.",
          "x-validate": "omitempty,gte=1"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "file"
      ]
    },
    "flows.record_call": {
      "properties": {
        "file": {
          "type": "string",
          "description": "Target WAV path in the realm storage (relative to the realm's files directory; subdirectories are created as needed). Supports %var% interpolation, plus %recording_id% — a unique id generated when the recording starts. The resolved relative path is stored in the call variable 'recording_file' and the id in 'recording_id'. The recording is 8kHz 16-bit mono PCM of the feed selected by 'direction'; it is finalized by stop_recording or automatically when the call ends. One active recording per call.",
          "x-validate": "required,max=256"
        },
        "direction": {
          "type": "string",
          "description": "Which audio feed to record: 'incoming' (audio received from the remote party), 'outgoing' (audio the PBX sends), or 'both' (the two feeds mixed into one mono track). Defaults to 'both'.",
          "x-validate": "omitempty,oneof=incoming outgoing both"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "file"
      ]
    },
    "flows.run_flows_from_variable": {
      "properties": {
        "variable_name": {
          "type": "string",
          "description": "Variable name containing a CSV list of flow identifiers to execute.",
          "x-validate": "required,max=64"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "variable_name"
      ]
    },
    "flows.send_audio_to_call": {
      "properties": {
        "call_ids": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of call IDs to send audio to.",
          "x-validate": "min=1,dive,required"
        },
        "call": {
          "type": "string",
          "description": "ID of the call to act as (supports %variable% interpolation, e.g. %transfer_peer_call_ids%). Omitted: the call executing this flow. Lets a task bridge or un-bridge two OTHER calls, e.g. during transfers or conferences.",
          "x-validate": "omitempty,max=64"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "call_ids"
      ]
    },
    "flows.send_email": {
      "properties": {
        "to": {
          "type": "string",
          "description": "Recipient address(es), comma-separated. Supports %var% interpolation.",
          "x-validate": "required,max=512"
        },
        "subject": {
          "type": "string",
          "description": "Subject line (ASCII; %var% interpolation supported).",
          "x-validate": "required,max=256"
        },
        "body": {
          "type": "string",
          "description": "Plain-text body. Supports %var% interpolation.",
          "x-validate": "required"
        },
        "attachment": {
          "type": "string",
          "description": "Optional realm-storage path of a file to attach (e.g. %recording_file%). Sent base64-encoded in a multipart/mixed part; .wav files get the audio/wav content type.",
          "x-validate": "omitempty,max=256"
        },
        "smtp": {
          "$ref": "#/$defs/flows.smtp_config",
          "description": "Outgoing mail server for this flow entry. Omitting it disables the entry (it logs an error and sends nothing) — presets ship send_email entries without smtp so the importing realm supplies its own deployment settings.",
          "x-validate": "omitempty"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "to",
        "subject",
        "body"
      ]
    },
    "flows.send_fax": {
      "properties": {
        "file": {
          "type": "string",
          "description": "The document to transmit: a bilevel (black-and-white) TIFF in the realm storage (relative to the realm's files directory). Supports %var% interpolation. TIFF only — convert PDFs to fax TIFF outside the PBX.",
          "x-validate": "required,max=256"
        },
        "local_ident": {
          "type": "string",
          "description": "The T.30 station identity announced to the remote fax machine (max 20 characters, typically the fax number).",
          "x-validate": "omitempty,max=20"
        },
        "timeout_seconds": {
          "type": "integer",
          "description": "Maximum duration of the fax session in seconds; on expiry the session is terminated and fax_result is 'failed'. Defaults to 600. send_fax BLOCKS the dial plan until the transmission completes or fails; results land in the call variables fax_result, fax_error, fax_pages, fax_remote_ident. The PBX acts as the T.30 calling/transmitting party on the CURRENT call's audio — the call must be answered on plain G.711.",
          "x-validate": "omitempty,gte=1"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "file"
      ]
    },
    "flows.set_dtmf_task": {
      "properties": {
        "sequence": {
          "type": "string",
          "description": "DTMF digit sequence to watch for, e.g. *9. Allowed characters: 0-9, *, #, A-D. Matching slides over the digits the caller sends; digits more than 4 seconds apart no longer combine into one sequence, and after a match the window resets.",
          "x-validate": "required,min=1,max=16,dtmf"
        },
        "dial_plan": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of flows to run as a detached background task each time the sequence is observed, without blocking the dial plan. The task runs on a snapshot of the call's variables taken when set_dtmf_task executed — variables the task sets are visible only inside the task and never leak back. Registering the same sequence again replaces the previous dial plan; fires are traced in the call log under dtmf_task/\u003csequence\u003e.",
          "x-validate": "required,min=1,dive,required,validId=flows.."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "sequence",
        "dial_plan"
      ]
    },
    "flows.set_hangup_task": {
      "properties": {
        "dial_plan": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "Flows to run after this call ends (hangup by either side or dial-plan completion). They execute with a snapshot of the call variables taken at registration time, under a fresh 60-second context — the call's media is gone by then, so only non-media flows (stop_recording, set_message_waiting, send_email, log, delete_file, ...) are useful here. Multiple registrations run in order. Registered on the incoming call leg.",
          "x-validate": "required,min=1,dive,required,validId=flows.."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "dial_plan"
      ]
    },
    "flows.set_hold_task": {
      "properties": {
        "on_hold": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "Flows to run (detached, non-blocking) when the peer puts this call on hold via re-INVITE (a=sendonly or a=inactive). Typical use: start hold music with a silence_player flow.",
          "x-validate": "omitempty,dive,required,validId=flows.."
        },
        "on_unhold": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "Flows to run (detached, non-blocking) when the peer resumes the call (back to a=sendrecv or a=recvonly). Typical use: stop the silence_player again.",
          "x-validate": "omitempty,dive,required,validId=flows.."
        }
      },
      "additionalProperties": false,
      "type": "object"
    },
    "flows.set_message_waiting": {
      "properties": {
        "extension": {
          "type": "string",
          "description": "Extension whose message-waiting indicator is updated. Supports %variable% interpolation.",
          "x-validate": "required"
        },
        "new_messages": {
          "type": "string",
          "description": "Number of new (unread) voice messages. Supports %variable% interpolation; must resolve to a non-negative integer.",
          "x-validate": "required"
        },
        "old_messages": {
          "type": "string",
          "description": "Number of old (read) voice messages. Supports %variable% interpolation; must resolve to a non-negative integer.",
          "x-validate": "required"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "extension",
        "new_messages",
        "old_messages"
      ]
    },
    "flows.set_transcription_task": {
      "properties": {
        "engine": {
          "type": "string",
          "description": "Name of a transcription engine defined in the realm's transcription block. The selected audio feed is streamed to it live; the task is registered under this name for unset_transcription_task and get_call_transcription.",
          "x-validate": "required"
        },
        "direction": {
          "type": "string",
          "description": "Which audio feed to transcribe: 'incoming' (audio received from the remote party), 'outgoing' (audio the PBX sends), or 'both' (the two feeds mixed). Defaults to 'outgoing'.",
          "x-validate": "omitempty,oneof=incoming outgoing both"
        },
        "webhook_url": {
          "type": "string",
          "description": "When set, each final transcription result is POSTed as JSON {text, is_final, direction, timestamp} to this URL. Supports %variable% interpolation (resolved when the task starts).",
          "x-validate": "omitempty,url"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "engine"
      ]
    },
    "flows.set_transfer_task": {
      "properties": {
        "dial_plan": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "Flows to run as a background task when this call's device sends an in-dialog REFER (asks the PBX to transfer the call). The task runs on a snapshot of the call's variables taken when set_transfer_task executed, seeded with the transfer_* variables describing the REFER (transfer_mode, transfer_target, transfer_target_uri, transfer_referred_by, transferor_call_id, transfer_peer_call_ids, transfer_replaces_call_id), and survives the transferor's hangup. Report the outcome by setting transfer_status to a SIP status line (e.g. set_variable with \"%dial_status% %dial_reason%\"); unset means success (200 OK) — it is delivered to the transferor as a message/sipfrag NOTIFY. Registering again replaces the previous task. Without a registered task, REFER is rejected with 403: what a transfer does is entirely defined by this dial plan.",
          "x-validate": "required,min=1,dive,required,validId=flows.."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "dial_plan"
      ]
    },
    "flows.set_variable": {
      "properties": {
        "variable_name": {
          "type": "string",
          "description": "Name of the variable to set.",
          "x-validate": "required,max=64"
        },
        "variable_value": {
          "type": "string",
          "description": "Value to assign to the variable.",
          "x-validate": "required,max=256"
        },
        "global": {
          "type": "boolean",
          "description": "Whether the variable is global. Defaults to local.",
          "x-validate": "omitempty"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "variable_name",
        "variable_value"
      ]
    },
    "flows.silence_player": {
      "properties": {
        "sounds": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "Sounds to use as the fallback source (e.g. hold music), played back-to-back in the given order; the playlist plays only when nothing else is broadcasting to the call. When omitted, generated pink comfort noise (~-60 dBm0) is used.",
          "x-validate": "omitempty,dive,validId=sounds.."
        },
        "on_interrupt": {
          "type": "string",
          "description": "What happens to the fallback audio while other audio plays: 'pause' resumes where the playlist left off, 'restart' starts from the beginning of the playlist again. Defaults to pause.",
          "x-validate": "omitempty,oneof=pause restart"
        },
        "repeat": {
          "type": "boolean",
          "description": "Whether the fallback playlist repeats. When false it stops after the first complete play-through (for comfort noise: after its duration_ms). Defaults to true.",
          "x-validate": "omitempty"
        }
      },
      "additionalProperties": false,
      "type": "object"
    },
    "flows.smtp_config": {
      "properties": {
        "host": {
          "type": "string",
          "description": "SMTP server hostname or IP.",
          "x-validate": "required,min=1"
        },
        "port": {
          "type": "integer",
          "description": "SMTP server port (25 or 587; STARTTLS is used when offered, implicit-TLS 465 is not supported).",
          "x-validate": "required,min=1,max=65535"
        },
        "username": {
          "type": "string",
          "description": "AUTH PLAIN username; omit to send without authentication. Authentication over a connection without STARTTLS is refused (except to localhost).",
          "x-validate": "omitempty,max=256"
        },
        "password": {
          "type": "string",
          "description": "AUTH PLAIN password.",
          "x-validate": "omitempty,max=256"
        },
        "from": {
          "type": "string",
          "description": "From address for mail sent by this flow.",
          "x-validate": "required,min=3"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "host",
        "port",
        "from"
      ]
    },
    "flows.sound": {
      "properties": {
        "sound": {
          "type": "string",
          "description": "Identifier of a single sound to play. Exactly one of sound/sounds must be set.",
          "x-validate": "required_without=Sounds,excluded_with=Sounds,omitempty,validId=sounds.."
        },
        "sounds": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "Identifiers of sounds to play back-to-back in the given order. Exactly one of sound/sounds must be set.",
          "x-validate": "omitempty,min=1,dive,validId=sounds.."
        },
        "wait": {
          "type": "boolean",
          "description": "Whether to wait for the sound (or the whole sounds sequence) to finish before proceeding. Defaults to true.",
          "x-validate": "omitempty"
        }
      },
      "additionalProperties": false,
      "type": "object"
    },
    "flows.stop_audio_playback": {
      "properties": {
        "silence_player": {
          "type": "boolean",
          "description": "Stop the call's silence player fallback source (started by the silence_player flow action).",
          "x-validate": "omitempty"
        },
        "audio_player": {
          "type": "boolean",
          "description": "Interrupt every currently playing 'sound' flow playback on the call (foreground and background).",
          "x-validate": "omitempty"
        }
      },
      "additionalProperties": false,
      "type": "object"
    },
    "flows.stop_listening_to_call": {
      "properties": {
        "call_ids": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of call IDs to stop listening to. If empty, stops listening to all calls.",
          "x-validate": "dive,required"
        },
        "call": {
          "type": "string",
          "description": "ID of the call to act as (supports %variable% interpolation, e.g. %transfer_peer_call_ids%). Omitted: the call executing this flow. Lets a task bridge or un-bridge two OTHER calls, e.g. during transfers or conferences.",
          "x-validate": "omitempty,max=64"
        }
      },
      "additionalProperties": false,
      "type": "object"
    },
    "flows.stop_recording": {
      "properties": {},
      "additionalProperties": false,
      "type": "object"
    },
    "flows.stop_sending_audio_to_call": {
      "properties": {
        "call_ids": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of call IDs to stop sending audio to. If empty, stops sending to all calls.",
          "x-validate": "dive,required"
        },
        "call": {
          "type": "string",
          "description": "ID of the call to act as (supports %variable% interpolation, e.g. %transfer_peer_call_ids%). Omitted: the call executing this flow. Lets a task bridge or un-bridge two OTHER calls, e.g. during transfers or conferences.",
          "x-validate": "omitempty,max=64"
        }
      },
      "additionalProperties": false,
      "type": "object"
    },
    "flows.stop_transcribe": {
      "properties": {},
      "additionalProperties": false,
      "type": "object"
    },
    "flows.sub_flow": {
      "properties": {
        "dial_plan": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of flows to execute. Entries may pass scoped call variables using call syntax, e.g. flows/sub_flow/my_flow(var1=\"val\", var2=\"%caller%\"): the named variables are set before the flow executes and restored afterwards; values must be double-quoted and support %variable% interpolation.",
          "x-validate": "required,min=1,dive,required,validId=flows.."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "dial_plan"
      ]
    },
    "flows.sub_task": {
      "properties": {
        "dial_plan": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of flows to run as a detached background task: the list starts on its own goroutine and the calling dial plan continues immediately. The task runs on a snapshot of the call's variables taken at launch — variables the task sets or changes are visible only inside the task and never leak back to the main dial plan. Unless detached is true, the task stops when the call ends; its completion is traced in the call log under sub_task/\u003cflow-id\u003e.",
          "x-validate": "required,min=1,dive,required,validId=flows.."
        },
        "detached": {
          "type": "boolean",
          "description": "Default false: the task is stopped when the call ends. Set true to untie the task from the call's lifetime — it keeps running after the call ends, until its flows finish or the server shuts down. WARNING: a detached task outlives its call. It keeps consuming server resources after hangup, anything after the call ends is NOT traced (the call log closes at hangup and all later entries, including the task's completion, are silently dropped), and flows that act on the call (audio playback, hangup, DTMF) will fail or do nothing once the call is gone. Use only for genuinely call-independent work, and make sure the flow list always terminates — a never-ending detached task runs until server shutdown."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "dial_plan"
      ]
    },
    "flows.switch": {
      "properties": {
        "conditions": {
          "additionalProperties": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "type": "object",
          "description": "Map of condition IDs to flows. The first true condition's flows are executed. Entries may pass scoped call variables using call syntax, e.g. flows/sub_flow/my_flow(var1=\"val\", var2=\"%caller%\"): the named variables are set before the flow executes and restored afterwards; values must be double-quoted and support %variable% interpolation.",
          "x-validate": "required,dive,keys,validId=conditions..,endkeys,required,min=1,dive,required,validId=flows.."
        },
        "default_flow": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of flows to execute if no conditions are met. Entries may pass scoped call variables using call syntax, e.g. flows/sub_flow/my_flow(var1=\"val\", var2=\"%caller%\"): the named variables are set before the flow executes and restored afterwards; values must be double-quoted and support %variable% interpolation.",
          "x-validate": "omitempty,min=1,dive,required,validId=flows.."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "conditions"
      ]
    },
    "flows.unset_dtmf_task": {
      "properties": {
        "sequence": {
          "type": "string",
          "description": "Previously registered DTMF sequence to stop watching (see set_dtmf_task). Unknown sequences are a no-op.",
          "x-validate": "required,min=1,max=16,dtmf"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "sequence"
      ]
    },
    "flows.unset_hold_task": {
      "properties": {},
      "additionalProperties": false,
      "type": "object"
    },
    "flows.unset_transcription_task": {
      "properties": {
        "engine": {
          "type": "string",
          "description": "Name of the running transcription task (its engine name) to stop. Blocks until pending results are flushed, so a following get_call_transcription sees the complete transcript.",
          "x-validate": "required"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "engine"
      ]
    },
    "flows.unset_transfer_task": {
      "properties": {},
      "additionalProperties": false,
      "type": "object"
    },
    "flows.wait": {
      "properties": {
        "milliseconds": {
          "type": "integer",
          "description": "Duration to wait in milliseconds.",
          "x-validate": "required,gte=1"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "milliseconds"
      ]
    },
    "flows.wait_for_listener_to_start": {
      "properties": {
        "timeout_seconds": {
          "type": "integer",
          "description": "Maximum time to wait in seconds. If not specified, waits indefinitely.",
          "x-validate": "omitempty,gte=1"
        },
        "timeout_on_dtmf": {
          "type": "boolean",
          "description": "Whether to timeout if DTMF is received."
        }
      },
      "additionalProperties": false,
      "type": "object"
    },
    "flows.wait_for_listener_to_stop": {
      "properties": {
        "call_ids": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "Call IDs to monitor. If empty, monitors all calls bridged to this call.",
          "x-validate": "omitempty,dive,required"
        },
        "timeout_seconds": {
          "type": "integer",
          "description": "Maximum time to wait in seconds. If not specified, waits indefinitely.",
          "x-validate": "omitempty,gte=1"
        },
        "timeout_on_dtmf": {
          "type": "boolean",
          "description": "Whether to timeout if DTMF is received."
        }
      },
      "additionalProperties": false,
      "type": "object"
    },
    "flows.wait_for_sending_to_stop": {
      "properties": {
        "call_ids": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "Call IDs to monitor. If empty, monitors all calls bridged to this call.",
          "x-validate": "omitempty,dive,required"
        },
        "timeout_seconds": {
          "type": "integer",
          "description": "Maximum time to wait in seconds. If not specified, waits indefinitely.",
          "x-validate": "omitempty,gte=1"
        },
        "timeout_on_dtmf": {
          "type": "boolean",
          "description": "Whether to timeout if DTMF is received."
        }
      },
      "additionalProperties": false,
      "type": "object"
    },
    "flows.write_to_file": {
      "properties": {
        "file_path": {
          "type": "string",
          "description": "Path to the file where data will be written in the realm storage.",
          "x-validate": "required,max=256"
        },
        "data": {
          "type": "string",
          "description": "String data to write. Use %var_name% syntax for variable interpolation.",
          "x-validate": "required"
        },
        "base64_decode": {
          "type": "boolean",
          "description": "Whether the data is base64 encoded and should be decoded before writing."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "file_path",
        "data"
      ]
    },
    "gates.and": {
      "properties": {
        "conditions": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of condition IDs that must all evaluate to true.",
          "x-validate": "required,min=2,dive,required,validId=conditions.."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "conditions"
      ]
    },
    "gates.false": {
      "properties": {},
      "additionalProperties": false,
      "type": "object"
    },
    "gates.nand": {
      "properties": {
        "conditions": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of condition IDs, not all of which must evaluate to true.",
          "x-validate": "required,min=2,dive,required,validId=conditions.."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "conditions"
      ]
    },
    "gates.nor": {
      "properties": {
        "conditions": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of condition IDs, none of which must evaluate to true.",
          "x-validate": "required,min=2,dive,required,validId=conditions.."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "conditions"
      ]
    },
    "gates.not": {
      "properties": {
        "condition": {
          "type": "string",
          "description": "ID of the condition to negate.",
          "x-validate": "required,validId=conditions.."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "condition"
      ]
    },
    "gates.or": {
      "properties": {
        "conditions": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of condition IDs, at least one of which must evaluate to true.",
          "x-validate": "required,min=2,dive,required,validId=conditions.."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "conditions"
      ]
    },
    "gates.true": {
      "properties": {},
      "additionalProperties": false,
      "type": "object"
    },
    "gates.xnor": {
      "properties": {
        "conditions": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of condition IDs, all or none of which must evaluate to true.",
          "x-validate": "required,min=2,dive,required,validId=conditions.."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "conditions"
      ]
    },
    "gates.xor": {
      "properties": {
        "conditions": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of condition IDs, exactly one of which must evaluate to true.",
          "x-validate": "required,min=2,dive,required,validId=conditions.."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "conditions"
      ]
    },
    "sounds.analog_signaling_generator": {
      "properties": {
        "frequency_hz": {
          "type": "number",
          "description": "Frequency of the signal in Hertz.",
          "x-validate": "required,gt=0"
        },
        "duration_ms": {
          "type": "integer",
          "description": "Duration of the signal in milliseconds.",
          "x-validate": "required,gt=0"
        },
        "amplitude": {
          "type": "number",
          "description": "Amplitude of the signal (0.0 to 1.0).",
          "x-validate": "required,gte=0,lte=1"
        },
        "pause_ms": {
          "type": "integer",
          "description": "Pause duration after the signal in milliseconds (silent period).",
          "x-validate": "required,gte=0"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "frequency_hz",
        "duration_ms",
        "amplitude",
        "pause_ms"
      ]
    },
    "sounds.dual_tone_multi_frequency": {
      "properties": {
        "digits": {
          "type": "string",
          "description": "String of DTMF digits to play. Unsupported characters are skipped.",
          "x-validate": "required,min=1,max=32"
        },
        "wait_ms_between_digits": {
          "type": "integer",
          "description": "Wait time in milliseconds between playing each digit.",
          "x-validate": "min=0,max=10000"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "digits",
        "wait_ms_between_digits"
      ]
    },
    "sounds.overlaid_sounds": {
      "properties": {
        "primary_sound": {
          "type": "string",
          "description": "ID of the primary sound to play.",
          "x-validate": "required,validId=sounds.."
        },
        "secondary_sound": {
          "type": "string",
          "description": "ID of the secondary sound to play.",
          "x-validate": "required,validId=sounds.."
        },
        "primary_volume": {
          "type": "integer",
          "description": "Volume level for the primary sound (0-100). Defaults to 100.",
          "x-validate": "omitempty,gte=0,lte=100"
        },
        "secondary_volume": {
          "type": "integer",
          "description": "Volume level for the secondary sound (0-100). Defaults to 100.",
          "x-validate": "omitempty,gte=0,lte=100"
        },
        "repeat_to_match_duration": {
          "type": "boolean",
          "description": "Whether to repeat the secondary sound to match the primary sound's duration.",
          "x-validate": "omitempty"
        },
        "dont_cut_secondary": {
          "type": "boolean",
          "description": "Whether to avoid cutting the secondary sound when the primary sound ends. If true, the secondary sound will continue playing until it finishes.",
          "x-validate": "omitempty"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "primary_sound",
        "secondary_sound"
      ]
    },
    "sounds.text_to_speech": {
      "properties": {
        "text": {
          "type": "string",
          "description": "Text to convert to speech (max 1024 characters).",
          "x-validate": "required,max=1024"
        },
        "engine": {
          "type": "string",
          "description": "Text-to-speech engine to use.",
          "x-validate": "required,validId=tts.."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "text",
        "engine"
      ]
    },
    "sounds.wav_file": {
      "properties": {
        "file_name": {
          "type": "string",
          "description": "Name of the WAV file.",
          "x-validate": "required,max=128"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "file_name"
      ]
    },
    "structs.conditions": {
      "properties": {
        "and": {
          "additionalProperties": {
            "$ref": "#/$defs/gates.and"
          },
          "type": "object",
          "description": "Requires all specified conditions to be true.",
          "x-validate": "omitempty,dive"
        },
        "false": {
          "additionalProperties": {
            "$ref": "#/$defs/gates.false"
          },
          "type": "object",
          "description": "Always evaluates to false.",
          "x-validate": "omitempty,dive"
        },
        "nand": {
          "additionalProperties": {
            "$ref": "#/$defs/gates.nand"
          },
          "type": "object",
          "description": "Requires not all of the specified conditions to be true.",
          "x-validate": "omitempty,dive"
        },
        "nor": {
          "additionalProperties": {
            "$ref": "#/$defs/gates.nor"
          },
          "type": "object",
          "description": "Requires none of the specified conditions to be true.",
          "x-validate": "omitempty,dive"
        },
        "not": {
          "additionalProperties": {
            "$ref": "#/$defs/gates.not"
          },
          "type": "object",
          "description": "Negates the result of another condition.",
          "x-validate": "omitempty,dive"
        },
        "or": {
          "additionalProperties": {
            "$ref": "#/$defs/gates.or"
          },
          "type": "object",
          "description": "Requires at least one of the specified conditions to be true.",
          "x-validate": "omitempty,dive"
        },
        "true": {
          "additionalProperties": {
            "$ref": "#/$defs/gates.true"
          },
          "type": "object",
          "description": "Always evaluates to true.",
          "x-validate": "omitempty,dive"
        },
        "xnor": {
          "additionalProperties": {
            "$ref": "#/$defs/gates.xnor"
          },
          "type": "object",
          "description": "Requires all or none of the specified conditions to be true.",
          "x-validate": "omitempty,dive"
        },
        "xor": {
          "additionalProperties": {
            "$ref": "#/$defs/gates.xor"
          },
          "type": "object",
          "description": "Requires exactly one of the specified conditions to be true.",
          "x-validate": "omitempty,dive"
        },
        "caller_number": {
          "additionalProperties": {
            "$ref": "#/$defs/conditions.caller_number"
          },
          "type": "object",
          "description": "Checks if the caller's number matches any of the specified numbers.",
          "x-validate": "omitempty,dive"
        },
        "caller_regex": {
          "additionalProperties": {
            "$ref": "#/$defs/conditions.caller_regex"
          },
          "type": "object",
          "description": "Checks if the caller's number matches any of the specified regex patterns.",
          "x-validate": "omitempty,dive"
        },
        "dialed_number": {
          "additionalProperties": {
            "$ref": "#/$defs/conditions.dialed_number"
          },
          "type": "object",
          "description": "Checks if the dialed number matches any of the specified numbers.",
          "x-validate": "omitempty,dive"
        },
        "dialed_internal": {
          "additionalProperties": {
            "$ref": "#/$defs/conditions.dialed_internal"
          },
          "type": "object",
          "description": "Checks if the dialed number is an internal extension.",
          "x-validate": "omitempty,dive"
        },
        "dialed_regex": {
          "additionalProperties": {
            "$ref": "#/$defs/conditions.dialed_regex"
          },
          "type": "object",
          "description": "Checks if the dialed number matches any of the specified regex patterns.",
          "x-validate": "omitempty,dive"
        },
        "eval": {
          "additionalProperties": {
            "$ref": "#/$defs/conditions.eval"
          },
          "type": "object",
          "description": "Evaluates a custom JavaScript expression.",
          "x-validate": "omitempty,dive"
        },
        "from_internal": {
          "additionalProperties": {
            "$ref": "#/$defs/conditions.from_internal"
          },
          "type": "object",
          "description": "Checks if the call is from an internal extension.",
          "x-validate": "omitempty,dive"
        },
        "from_trunk": {
          "additionalProperties": {
            "$ref": "#/$defs/conditions.from_trunk"
          },
          "type": "object",
          "description": "Checks if the call is from a specific trunk.",
          "x-validate": "omitempty,dive"
        },
        "is_deafened": {
          "additionalProperties": {
            "$ref": "#/$defs/conditions.is_deafened"
          },
          "type": "object",
          "description": "Checks whether a call (default: the current one) is deafened — no bridged audio reaches it — either client-signaled or via the deafen flow action.",
          "x-validate": "omitempty,dive"
        },
        "is_muted": {
          "additionalProperties": {
            "$ref": "#/$defs/conditions.is_muted"
          },
          "type": "object",
          "description": "Checks whether a call (default: the current one) is muted — its audio reaches no other call — either client-signaled or via the mute flow action.",
          "x-validate": "omitempty,dive"
        },
        "time": {
          "additionalProperties": {
            "$ref": "#/$defs/conditions.time"
          },
          "type": "object",
          "description": "Checks if the current time falls within specified parameters.",
          "x-validate": "omitempty,dive"
        },
        "variable_equals": {
          "additionalProperties": {
            "$ref": "#/$defs/conditions.variable_equals"
          },
          "type": "object",
          "description": "Checks if a specified variable equals a given value.",
          "x-validate": "omitempty,dive"
        },
        "variable_more_than": {
          "additionalProperties": {
            "$ref": "#/$defs/conditions.variable_more_than"
          },
          "type": "object",
          "description": "Checks if a specified variable is greater than a given value.",
          "x-validate": "omitempty,dive"
        },
        "variable_regex": {
          "additionalProperties": {
            "$ref": "#/$defs/conditions.variable_regex"
          },
          "type": "object",
          "description": "Checks if a specified variable matches any of the given regex patterns.",
          "x-validate": "omitempty,dive"
        }
      },
      "additionalProperties": false,
      "type": "object"
    },
    "structs.extension": {
      "properties": {
        "password": {
          "type": "string",
          "description": "Password for the extension authentication.",
          "x-validate": "required,min=3,max=64"
        },
        "max_simultaneous_registrations": {
          "type": "integer",
          "description": "Maximum number of simultaneous registrations allowed for this extension.",
          "x-validate": "omitempty,gte=1"
        },
        "max_simultaneous_calls": {
          "type": "integer",
          "description": "Maximum number of simultaneous calls allowed for this extension.",
          "x-validate": "omitempty,gte=1"
        },
        "max_registration_seconds": {
          "type": "integer",
          "description": "Maximum registration duration in seconds (60-86400).",
          "x-validate": "omitempty,gte=60,lte=86400"
        },
        "disabled_codecs": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "Codecs never negotiated with this extension, in addition to the server-wide disabled_codecs list. Entries match an engine's unique name (e.g. PCMA, OPUS_48000) or codec name (e.g. opus, disabling every rate), case-insensitively.",
          "x-validate": "omitempty,dive,required"
        },
        "srtp": {
          "type": "string",
          "description": "Per-extension SDES-SRTP mode overriding the server-wide srtp setting for calls to/from this extension. Unset means use the server default. Same values as the server setting: 'disabled', 'optional', 'required'. In 'optional' mode an outbound leg first offers best-effort crypto on RTP/AVP and, if the phone rejects that with 488, retries with strict RTP/SAVP — so phones that only accept the SAVP profile still connect.",
          "x-validate": "omitempty,oneof=disabled optional required"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "password"
      ]
    },
    "structs.flows": {
      "properties": {
        "answer": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.answer"
          },
          "type": "object",
          "description": "Answers the incoming call. All flows that require media must answer the call first.",
          "x-validate": "omitempty,dive"
        },
        "copy_variable": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.copy_variable"
          },
          "type": "object",
          "description": "Copies the value from one variable to another.",
          "x-validate": "omitempty,dive"
        },
        "deafen": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.deafen"
          },
          "type": "object",
          "description": "Sets or clears the server-side deafen on a call (default: the flow's call): while deafened (server- or client-side) no bridged audio reaches it and the silence player can take over.",
          "x-validate": "omitempty,dive"
        },
        "delete_file": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.delete_file"
          },
          "type": "object",
          "description": "Deletes a file from the realm storage.",
          "x-validate": "omitempty,dive"
        },
        "detect_fax": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.detect_fax"
          },
          "type": "object",
          "description": "Blocks the dial plan listening for a fax CNG (1100 Hz) tone; on detection enters G.711 pass-through and runs a dial plan inline. Sets the call variable 'fax_detected'.",
          "x-validate": "omitempty,dive"
        },
        "dial": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.dial"
          },
          "type": "object",
          "description": "Dials a target destination (internal extension or external number).",
          "x-validate": "omitempty,dive"
        },
        "dual_tone_multi_frequency": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.dual_tone_multi_frequency"
          },
          "type": "object",
          "description": "Receives DTMF input from the caller.",
          "x-validate": "omitempty,dive"
        },
        "eval": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.eval"
          },
          "type": "object",
          "description": "Evaluates JavaScript code.",
          "x-validate": "omitempty,dive"
        },
        "for_each": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.for_each"
          },
          "type": "object",
          "description": "Iterates over a variable (containing csv), executing a set of flows for each item.",
          "x-validate": "omitempty,dive"
        },
        "get_call_transcription": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.get_call_transcription"
          },
          "type": "object",
          "description": "Writes the transcript a transcription task has accumulated so far into a call variable (see set_transcription_task).",
          "x-validate": "omitempty,dive"
        },
        "get_calls_listening_to": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.get_calls_listening_to"
          },
          "type": "object",
          "description": "Retrieves a list of call IDs currently listening to a specified call.",
          "x-validate": "omitempty,dive"
        },
        "get_calls_sending_audio_to": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.get_calls_sending_audio_to"
          },
          "type": "object",
          "description": "Retrieves a list of call IDs that a specified call is sending audio to.",
          "x-validate": "omitempty,dive"
        },
        "get_file_list": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.get_file_list"
          },
          "type": "object",
          "description": "Retrieves a list of files from the realm storage with specified filters.",
          "x-validate": "omitempty,dive"
        },
        "get_listening": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.get_listening"
          },
          "type": "object",
          "description": "Stores the CSV list of call IDs a call is currently listening to (whose audio reaches it) in a variable. Subject-perspective complement of get_calls_sending_audio_to.",
          "x-validate": "omitempty,dive"
        },
        "get_sending": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.get_sending"
          },
          "type": "object",
          "description": "Stores the CSV list of call IDs a call is currently sending audio to in a variable. Subject-perspective complement of get_calls_listening_to.",
          "x-validate": "omitempty,dive"
        },
        "hangup": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.hangup"
          },
          "type": "object",
          "description": "Terminates the call immediately.",
          "x-validate": "omitempty,dive"
        },
        "if": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.if"
          },
          "type": "object",
          "description": "Evaluates a condition and executes different flows based on the result.",
          "x-validate": "omitempty,dive"
        },
        "listen_to_call": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.listen_to_call"
          },
          "type": "object",
          "description": "Listens to audio from other calls, bridging them together.",
          "x-validate": "omitempty,dive"
        },
        "log": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.log"
          },
          "type": "object",
          "description": "Logs a message to the system logs.",
          "x-validate": "omitempty,dive"
        },
        "loop": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.loop"
          },
          "type": "object",
          "description": "Repeatedly executes a set of flows until a condition is met or maximum iterations reached.",
          "x-validate": "omitempty,dive"
        },
        "mute": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.mute"
          },
          "type": "object",
          "description": "Sets or clears the server-side mute on a call (default: the flow's call): while muted (server- or client-side) its audio reaches no other call.",
          "x-validate": "omitempty,dive"
        },
        "queue": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.queue"
          },
          "type": "object",
          "description": "Places the call into a queue to be answered by specified targets.",
          "x-validate": "omitempty,dive"
        },
        "read_variable_from_file": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.read_variable_from_file"
          },
          "type": "object",
          "description": "Reads file content and stores it in a variable.",
          "x-validate": "omitempty,dive"
        },
        "receive_audio_from_call": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.receive_audio_from_call"
          },
          "type": "object",
          "description": "Receives audio from other calls, bridging them together.",
          "x-validate": "omitempty,dive"
        },
        "receive_fax": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.receive_fax"
          },
          "type": "object",
          "description": "Receives a fax in-band (audio T.30, G.711 calls only) and writes it to the realm storage as TIFF or PDF. Blocks the dial plan until the session ends.",
          "x-validate": "omitempty,dive"
        },
        "record_call": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.record_call"
          },
          "type": "object",
          "description": "Forks the call's decoded audio (incoming, outgoing, or both mixed — see 'direction'; 8kHz 16-bit mono WAV) into a file in the realm storage.",
          "x-validate": "omitempty,dive"
        },
        "run_flows_from_variable": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.run_flows_from_variable"
          },
          "type": "object",
          "description": "Runs a list of flows defined in a variable.",
          "x-validate": "omitempty,dive"
        },
        "send_audio_to_call": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.send_audio_to_call"
          },
          "type": "object",
          "description": "Sends audio to other calls, bridging them together.",
          "x-validate": "omitempty,dive"
        },
        "send_email": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.send_email"
          },
          "type": "object",
          "description": "Sends an email via the smtp server configured on the flow entry, optionally attaching a file from the realm storage.",
          "x-validate": "omitempty,dive"
        },
        "send_fax": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.send_fax"
          },
          "type": "object",
          "description": "Transmits a TIFF from the realm storage as an in-band fax (audio T.30, G.711 calls only). Blocks the dial plan until the session ends.",
          "x-validate": "omitempty,dive"
        },
        "set_dtmf_task": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.set_dtmf_task"
          },
          "type": "object",
          "description": "Registers a background task that runs a list of flows whenever a DTMF digit sequence is received mid-call, without blocking the dial plan.",
          "x-validate": "omitempty,dive"
        },
        "set_hangup_task": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.set_hangup_task"
          },
          "type": "object",
          "description": "Registers flows to run after the call ends, with a snapshot of the current call variables.",
          "x-validate": "omitempty,dive"
        },
        "set_hold_task": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.set_hold_task"
          },
          "type": "object",
          "description": "Registers per-call flow lists that run when the peer puts the call on hold (on_hold) or resumes it (on_unhold). Each hold-state flip runs the matching list exactly once, detached from the main dial plan.",
          "x-validate": "omitempty,dive"
        },
        "set_message_waiting": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.set_message_waiting"
          },
          "type": "object",
          "description": "Publishes voicemail message counts (MWI), notifying phones subscribed to the extension's message-summary events.",
          "x-validate": "omitempty,dive"
        },
        "set_transcription_task": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.set_transcription_task"
          },
          "type": "object",
          "description": "Starts live speech-to-text on the call's audio (incoming, outgoing, or both mixed — see 'direction'; default outgoing) using a configured engine; read the transcript with get_call_transcription and/or receive results via webhook.",
          "x-validate": "omitempty,dive"
        },
        "set_transfer_task": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.set_transfer_task"
          },
          "type": "object",
          "description": "Registers the per-call flows that run when this call's device sends a REFER (call transfer). The PBX ships no built-in transfer behavior: without a registered transfer task, REFER is rejected.",
          "x-validate": "omitempty,dive"
        },
        "set_variable": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.set_variable"
          },
          "type": "object",
          "description": "Sets a variable for the call.",
          "x-validate": "omitempty,dive"
        },
        "silence_player": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.silence_player"
          },
          "type": "object",
          "description": "Starts a fallback audio source (comfort noise or a sound playlist) that plays only when nothing else is broadcasting to the call.",
          "x-validate": "omitempty,dive"
        },
        "sound": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.sound"
          },
          "type": "object",
          "description": "Plays a sound to the caller.",
          "x-validate": "omitempty,dive"
        },
        "stop_audio_playback": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.stop_audio_playback"
          },
          "type": "object",
          "description": "Stops the silence player and/or interrupts active sound playback on the call.",
          "x-validate": "omitempty,dive"
        },
        "stop_listening_to_call": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.stop_listening_to_call"
          },
          "type": "object",
          "description": "Stops listening to audio from other calls, un-bridging them.",
          "x-validate": "omitempty,dive"
        },
        "stop_recording": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.stop_recording"
          },
          "type": "object",
          "description": "Finalizes the call's active recording started by record_call (patches the WAV sizes and releases the file).",
          "x-validate": "omitempty,dive"
        },
        "stop_sending_audio_to_call": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.stop_sending_audio_to_call"
          },
          "type": "object",
          "description": "Stops sending audio to other calls, un-bridging them.",
          "x-validate": "omitempty,dive"
        },
        "stop_transcribe": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.stop_transcribe"
          },
          "type": "object",
          "description": "Stops all running transcription tasks on the call, flushing their pending results. Transcripts remain readable via get_call_transcription.",
          "x-validate": "omitempty,dive"
        },
        "sub_flow": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.sub_flow"
          },
          "type": "object",
          "description": "Executes a list of other flows. Useful for structuring complex dial plans.",
          "x-validate": "omitempty,dive"
        },
        "sub_task": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.sub_task"
          },
          "type": "object",
          "description": "Runs a list of flows as a detached background task on a snapshot of the call's variables (changes stay inside the task); the dial plan continues immediately. A task with detached: true keeps running after the call ends (see the warning on that option).",
          "x-validate": "omitempty,dive"
        },
        "switch": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.switch"
          },
          "type": "object",
          "description": "Evaluates multiple conditions and executes the corresponding flow for the first true condition.",
          "x-validate": "omitempty,dive"
        },
        "unset_dtmf_task": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.unset_dtmf_task"
          },
          "type": "object",
          "description": "Removes a background DTMF task registered by set_dtmf_task.",
          "x-validate": "omitempty,dive"
        },
        "unset_hold_task": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.unset_hold_task"
          },
          "type": "object",
          "description": "Clears the hold task lists registered by set_hold_task for this call.",
          "x-validate": "omitempty,dive"
        },
        "unset_transcription_task": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.unset_transcription_task"
          },
          "type": "object",
          "description": "Stops a named transcription task, flushing pending results. The transcript remains readable via get_call_transcription.",
          "x-validate": "omitempty,dive"
        },
        "unset_transfer_task": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.unset_transfer_task"
          },
          "type": "object",
          "description": "Clears the transfer task registered by set_transfer_task; subsequent REFERs on this call are rejected again.",
          "x-validate": "omitempty,dive"
        },
        "wait": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.wait"
          },
          "type": "object",
          "description": "Pauses the call for a specified duration.",
          "x-validate": "omitempty,dive"
        },
        "wait_for_listener_to_start": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.wait_for_listener_to_start"
          },
          "type": "object",
          "description": "Waits until a listener has started receiving audio.",
          "x-validate": "omitempty,dive"
        },
        "wait_for_listener_to_stop": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.wait_for_listener_to_stop"
          },
          "type": "object",
          "description": "Waits until listeners have stopped receiving audio from this call.",
          "x-validate": "omitempty,dive"
        },
        "wait_for_sending_to_stop": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.wait_for_sending_to_stop"
          },
          "type": "object",
          "description": "Waits until calls have stopped sending audio to this call.",
          "x-validate": "omitempty,dive"
        },
        "write_to_file": {
          "additionalProperties": {
            "$ref": "#/$defs/flows.write_to_file"
          },
          "type": "object",
          "description": "Writes data to a specified file in the realm storage.",
          "x-validate": "omitempty,dive"
        }
      },
      "additionalProperties": false,
      "type": "object"
    },
    "structs.realm": {
      "properties": {
        "name": {
          "type": "string",
          "description": "Name of the realm.",
          "x-validate": "required,min=3,max=64"
        },
        "imports": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "Configs merged into this realm before validation: preset://\u003cname\u003e (built into the server), an https:// URL (fetched at load, 10s timeout, 1 MiB cap), or a filesystem path (relative paths resolve against this file's directory). Imported flows/sounds/conditions are added under their own names; this realm's own definitions win on name collision. Imports must not define name, trunks, extensions, dial_plan, tts, early_media or debug_dumps.",
          "x-validate": "omitempty,dive,required"
        },
        "trunks": {
          "additionalProperties": {
            "$ref": "#/$defs/structs.trunk"
          },
          "type": "object",
          "description": "Map of trunk configurations.",
          "x-validate": "omitempty,dive"
        },
        "extensions": {
          "additionalProperties": {
            "$ref": "#/$defs/structs.extension"
          },
          "type": "object",
          "description": "Map of extension configurations keyed by extension number.",
          "x-validate": "omitempty,dive,keys,number,endkeys"
        },
        "tts": {
          "$ref": "#/$defs/structs.t_t_s",
          "description": "Text-to-speech engine configurations.",
          "x-validate": "omitempty"
        },
        "sounds": {
          "$ref": "#/$defs/structs.sounds",
          "description": "Sound configurations.",
          "x-validate": "omitempty"
        },
        "conditions": {
          "$ref": "#/$defs/structs.conditions",
          "description": "Condition configurations.",
          "x-validate": "omitempty"
        },
        "transcription": {
          "additionalProperties": {
            "$ref": "#/$defs/structs.transcription_engine"
          },
          "type": "object",
          "description": "Named speech-to-text engine configurations referenced by the set_transcription_task flow action.",
          "x-validate": "omitempty,dive"
        },
        "flows": {
          "$ref": "#/$defs/structs.flows",
          "description": "Flow configurations.",
          "x-validate": "required"
        },
        "early_media": {
          "type": "boolean",
          "description": "Flag to enable or disable early media handling."
        },
        "debug_dumps": {
          "type": "boolean",
          "description": "Flag to enable or disable debug dumps of handeled DialPlans."
        },
        "dial_plan": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of flow IDs that constitute the dial plan. Entries may pass scoped call variables using call syntax, e.g. flows/sub_flow/my_flow(var1=\"val\", var2=\"%caller%\"): the named variables are set before the flow executes and restored afterwards; values must be double-quoted and support %variable% interpolation.",
          "x-validate": "required,min=1,dive,required,validId=flows.."
        },
        "$schema": {
          "type": "string"
        }
      },
      "additionalProperties": false,
      "type": "object"
    },
    "structs.sounds": {
      "properties": {
        "overlaid_sounds": {
          "additionalProperties": {
            "$ref": "#/$defs/sounds.overlaid_sounds"
          },
          "type": "object",
          "description": "A sound composed of two overlaid sounds.",
          "x-validate": "omitempty,dive"
        },
        "analog_signaling_generator": {
          "additionalProperties": {
            "$ref": "#/$defs/sounds.analog_signaling_generator"
          },
          "type": "object",
          "description": "An analog signaling generator sound.",
          "x-validate": "omitempty,dive"
        },
        "dual_tone_multi_frequency": {
          "additionalProperties": {
            "$ref": "#/$defs/sounds.dual_tone_multi_frequency"
          },
          "type": "object",
          "description": "A dual-tone multi-frequency (DTMF) sound.",
          "x-validate": "omitempty,dive"
        },
        "text_to_speech": {
          "additionalProperties": {
            "$ref": "#/$defs/sounds.text_to_speech"
          },
          "type": "object",
          "description": "A text-to-speech sound configuration.",
          "x-validate": "omitempty,dive"
        },
        "wav_file": {
          "additionalProperties": {
            "$ref": "#/$defs/sounds.wav_file"
          },
          "type": "object",
          "description": "A WAV file sound configuration.",
          "x-validate": "omitempty,dive"
        }
      },
      "additionalProperties": false,
      "type": "object"
    },
    "structs.t_t_s": {
      "properties": {
        "eleven_labs": {
          "additionalProperties": {
            "$ref": "#/$defs/tts.eleven_labs"
          },
          "type": "object",
          "description": "ElevenLabs text-to-speech engine configuration.",
          "x-validate": "omitempty,dive"
        },
        "aws_polly": {
          "additionalProperties": {
            "$ref": "#/$defs/tts.aws_polly"
          },
          "type": "object",
          "description": "AWS Polly text-to-speech synthesis configuration.",
          "x-validate": "omitempty,dive"
        }
      },
      "additionalProperties": false,
      "type": "object"
    },
    "structs.transcription_engine": {
      "properties": {
        "type": {
          "type": "string",
          "description": "Backend type: 'http' streams PCM to an HTTP transcription service (Deepgram-style), 'command' runs a configurable local command (e.g. whisper-cli) in batch.",
          "x-validate": "required,oneof=http command"
        },
        "url": {
          "type": "string",
          "description": "HTTP engine only: the endpoint PCM is POSTed to. sample_rate and encoding are appended as query parameters.",
          "x-validate": "required_if=Type http,omitempty,url"
        },
        "headers": {
          "additionalProperties": {
            "type": "string"
          },
          "type": "object",
          "description": "HTTP engine only: extra request headers (e.g. Authorization) sent with the streaming POST."
        },
        "command": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "Command engine only: argv template run once per capture. Placeholders {model}, {file} and {format} are substituted at run time. Not locked to any specific CLI.",
          "x-validate": "required_if=Type command,omitempty,min=1,dive,required"
        },
        "model": {
          "type": "string",
          "description": "Command engine only: value substituted for the {model} placeholder in the command argv."
        },
        "sample_rate": {
          "type": "integer",
          "description": "Target PCM sample rate in Hz the call audio is resampled to before it is sent to the engine. Defaults to 16000.",
          "x-validate": "omitempty,min=8000,max=48000"
        },
        "output_format": {
          "type": "string",
          "description": "Command engine only: how captured audio is written for the command — 'wav' (default) or raw 'pcm'. Substituted for the {format} placeholder.",
          "x-validate": "omitempty,oneof=wav pcm"
        },
        "output_path": {
          "type": "string",
          "description": "Command engine only: absolute path the command writes its JSON transcription to. Empty means the JSON is read from the command's stdout."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "type"
      ]
    },
    "structs.trunk": {
      "properties": {
        "transport": {
          "type": "string",
          "description": "Transport protocol to use (udp, tcp, tls, ws, wss).",
          "x-validate": "required,oneof=udp tcp tls ws wss"
        },
        "username": {
          "type": "string",
          "description": "Username for trunk authentication.",
          "x-validate": "required,max=64"
        },
        "password": {
          "type": "string",
          "description": "Password for trunk authentication.",
          "x-validate": "omitempty,max=64"
        },
        "server": {
          "type": "string",
          "description": "Server address for the trunk.",
          "x-validate": "required,min=3,max=256"
        },
        "expires": {
          "type": "integer",
          "description": "Registration lifetime in seconds requested from the trunk. Defaults to 300. The trunk may grant a different value.",
          "x-validate": "omitempty,min=1,max=86400"
        },
        "caller_id": {
          "type": "string",
          "description": "Caller ID used in the From header of outbound calls through this trunk. Overridden by a dial flow's caller_id; defaults to the trunk username.",
          "x-validate": "omitempty,max=64"
        },
        "reachable_ip": {
          "type": "string",
          "description": "IP address the trunk can reach this PBX at (advertised in SDP/Via/Contact toward this trunk). Overrides both STUN discovery and the address the trunk reported during registration. Needed when the PBX is behind NAT and automatic discovery is unavailable.",
          "x-validate": "omitempty,ip"
        },
        "disabled_codecs": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "Codecs never negotiated with this trunk, in addition to the server-wide disabled_codecs list. Entries match an engine's unique name (e.g. PCMA, OPUS_48000) or codec name (e.g. opus, disabling every rate), case-insensitively.",
          "x-validate": "omitempty,dive,required"
        },
        "srtp": {
          "type": "string",
          "description": "Per-trunk SDES-SRTP mode overriding the server-wide srtp setting for calls to/from this trunk. Unset means use the server default. Same values as the server setting: 'disabled', 'optional', 'required'. In 'optional' mode an outbound leg first offers best-effort crypto on RTP/AVP and, if the provider rejects that with 488, retries with strict RTP/SAVP.",
          "x-validate": "omitempty,oneof=disabled optional required"
        },
        "plain_sdp": {
          "type": "boolean",
          "description": "When true, SDP offered to and answered toward this trunk omits the ICE-lite attributes (a=ice-lite, ice-ufrag, ice-pwd, candidate) and a=rtcp-mux, sending a plain RTP/AVP(/SAVP) description instead. Legacy SIP gateways and provider SBCs that do not implement ICE can mis-target or gate their return media on connectivity checks that never traverse NAT, causing one-way audio on outbound calls; a plain offer avoids that. Defaults to false (full offer with ICE-lite + rtcp-mux)."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "transport",
        "username",
        "server"
      ]
    },
    "tts.aws_polly": {
      "properties": {
        "voice_id": {
          "type": "string",
          "description": "Identifier of the voice to use.",
          "x-validate": "required,awsPollyVoiceId"
        },
        "language": {
          "type": "string",
          "description": "Language code for the voice.",
          "x-validate": "required,awsPollyLanguage"
        },
        "text_type": {
          "type": "string",
          "description": "Type of input text: 'text' or 'ssml'.",
          "x-validate": "omitempty,oneof=text ssml"
        },
        "engine": {
          "type": "string",
          "description": "Speech synthesis engine: 'standard' or 'neural'.",
          "x-validate": "omitempty,oneof=standard neural"
        },
        "sample_rate": {
          "type": "integer",
          "description": "Audio sample rate in Hz (8000, 16000, 22050, 24000, or 48000).",
          "x-validate": "omitempty,oneof=8000 16000 22050 24000 48000"
        },
        "access_key": {
          "type": "string",
          "description": "AWS access key for authentication.",
          "x-validate": "required,max=128"
        },
        "secret_key": {
          "type": "string",
          "description": "AWS secret key for authentication.",
          "x-validate": "required,max=128"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "voice_id",
        "language",
        "text_type",
        "engine",
        "sample_rate",
        "access_key",
        "secret_key"
      ]
    },
    "tts.eleven_labs": {
      "properties": {
        "voice_id": {
          "type": "string",
          "description": "Identifier of the voice to use.",
          "x-validate": "required,elevenLabsVoiceId"
        },
        "model_id": {
          "type": "string",
          "description": "Identifier of the model to use.",
          "x-validate": "omitempty,elevenLabsModelId"
        },
        "stability": {
          "type": "integer",
          "description": "Stability setting for the voice (0-100).",
          "x-validate": "omitempty,gte=0,lte=100"
        },
        "similarity_boost": {
          "type": "integer",
          "description": "Similarity boost setting for the voice (0-100).",
          "x-validate": "omitempty,gte=0,lte=100"
        },
        "api_key": {
          "type": "string",
          "description": "API key for authentication.",
          "x-validate": "required,max=128"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "voice_id",
        "model_id",
        "stability",
        "similarity_boost",
        "api_key"
      ]
    }
  }
}
