{
  "openapi": "3.0.3",
  "info": {
    "title": "TelAPI",
    "description": "TelAPI receives provider webhooks under /api/v1/webhooks and exposes a health probe at /health.",
    "version": "0.9.14"
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT"
      },
      "apiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Team API key. Required scopes are documented on each authenticated endpoint."
      }
    },
    "schemas": {
      "ErrorResponse": {
        "type": "object",
        "description": "Standard TelAPI error envelope.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine-readable error code.",
                "example": "VALIDATION_ERROR"
              },
              "message": {
                "type": "string",
                "description": "Human-readable error summary.",
                "example": "Request validation failed"
              },
              "details": {
                "description": "Optional structured details, usually validation issues."
              },
              "requestId": {
                "type": "string",
                "description": "Fastify request id for log correlation when available."
              }
            }
          }
        }
      },
      "TobiBotControlEvent": {
        "type": "object",
        "required": [
          "name"
        ],
        "description": "Vodafone bot-originated control event embedded in a spoken message. This is the supported way to request hangup, transfer, or dynamic DTMF/STT/TTS/bot-delay config changes from TOBi callbacks.",
        "properties": {
          "name": {
            "type": "string",
            "enum": [
              "hangup",
              "transfer",
              "handover",
              "config"
            ],
            "description": "Control event name. handover is treated as transfer; config can update DTMF/STT/TTS/bot-delay sessionParams."
          },
          "value": {
            "type": "object",
            "additionalProperties": true,
            "description": "Payload for a Vodafone custom control event (`messages[].content.event.custom`). TelAPI maps hangup, transfer/handover, DTMF config, bot-delay config, and bounded STT/TTS config events to runtime commands for TelPhi.",
            "properties": {
              "sessionParams": {
                "type": "object",
                "additionalProperties": false,
                "description": "Dynamic runtime settings sent by TOBi in a config custom event. Missing keys keep the current call value. DTMF fields update digit handling; STT/TTS fields update bounded timeout/retry settings or select a provider from the configured fallback chain; bot-delay fields update comfort prompts and final no-input timing for the current call.",
                "properties": {
                  "sendDTMF": {
                    "type": "boolean",
                    "description": "Enables forwarding caller DTMF digits to TOBi."
                  },
                  "bargeInOnDTMF": {
                    "type": "boolean",
                    "description": "Allows DTMF input to interrupt current bot playback."
                  },
                  "bargeIn": {
                    "type": "boolean",
                    "description": "General bot barge-in flag. DTMF playback interruption requires this and bargeInOnDTMF to be true."
                  },
                  "bargeInMinWordCount": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Minimum word count threshold associated with bot barge-in configuration."
                  },
                  "dtmfCollect": {
                    "type": "boolean",
                    "description": "Collects multiple DTMF digits before forwarding them to TOBi."
                  },
                  "dtmfDigits": {
                    "type": "string",
                    "description": "Allowed DTMF symbols. Digits outside this set are ignored before collection or forwarding.",
                    "example": "0123456789#*"
                  },
                  "dtmfCollectInterDigitTimeoutMS": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Milliseconds to wait for another digit before forwarding the collected input."
                  },
                  "dtmfCollectMaxDigits": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Maximum digits to collect before forwarding. 0 disables the max-digits trigger."
                  },
                  "dtmfCollectSubmitDigit": {
                    "type": "string",
                    "maxLength": 1,
                    "description": "Submit digit that ends collection. Empty string disables submit-digit termination."
                  },
                  "sttProvider": {
                    "type": "string",
                    "description": "Selects an STT provider from the Delphi-configured primary/fallback chain. Does not create providers or change credentials.",
                    "example": "azure_stt"
                  },
                  "sttConnectionTimeoutMS": {
                    "type": "integer",
                    "minimum": 10,
                    "maximum": 600000,
                    "description": "Maximum wait for STT provider connection/request response."
                  },
                  "sttRetries": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10,
                    "description": "Same-provider STT retries before fallback/failover."
                  },
                  "ttsProvider": {
                    "type": "string",
                    "description": "Selects a TTS provider from the Delphi-configured primary/fallback chain. Does not create providers or change credentials.",
                    "example": "openai_tts"
                  },
                  "ttsConnectionTimeoutMS": {
                    "type": "integer",
                    "minimum": 10,
                    "maximum": 600000,
                    "description": "Maximum wait for TTS provider response."
                  },
                  "ttsRetries": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10,
                    "description": "Same-provider TTS retries before fallback/failover."
                  },
                  "botNoInputGiveUpTimeoutMS": {
                    "type": "integer",
                    "minimum": 1000,
                    "maximum": 120000,
                    "description": "Final no-input timeout in milliseconds before bot_timeout failover. Alias botNoInputGiveUpTimeoutMs is also accepted."
                  },
                  "botNoInputGiveUpTimeoutMs": {
                    "type": "integer",
                    "minimum": 1000,
                    "maximum": 120000,
                    "description": "Camel-case alias for botNoInputGiveUpTimeoutMS. Final no-input timeout before bot_timeout failover."
                  },
                  "botNoInputTimeoutMs": {
                    "type": "integer",
                    "minimum": 500,
                    "maximum": 60000,
                    "description": "Milliseconds of bot silence before playing the comfort prompt. Omit to keep the current value."
                  },
                  "botNoInputSpeech": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "TTS text for the comfort prompt while waiting for the bot response.",
                    "example": "One moment while I check that."
                  },
                  "botNoInputUrl": {
                    "type": "string",
                    "description": "Audio URL for the comfort prompt. Used as an alternative to generated speech.",
                    "example": "https://example.com/comfort.wav"
                  },
                  "botNoInputRetries": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10,
                    "description": "How many times to repeat the comfort prompt while the bot remains silent."
                  },
                  "abortPlayOnBotMessage": {
                    "type": "boolean",
                    "description": "Stops comfort playback as soon as the bot responds."
                  },
                  "resumeRecognitionTimeoutMs": {
                    "type": "integer",
                    "minimum": 1000,
                    "maximum": 60000,
                    "description": "Milliseconds before resuming STT recognition after comfort playback."
                  },
                  "resumeRecognitionSpeech": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "TTS text to play when recognition is resumed.",
                    "example": "Are you still there?"
                  }
                }
              },
              "transferTarget": {
                "type": "string",
                "description": "Transfer destination. May be a phone number, tel: URI, sip: URI, or sips: URI."
              },
              "target": {
                "type": "string",
                "description": "Alternative name for transferTarget."
              },
              "transferMethod": {
                "type": "string",
                "enum": [
                  "sip_refer",
                  "sip_refer_replace",
                  "sip_invite",
                  "sip_invite_bridge",
                  "sip_invite_reinvite_p2p",
                  "sip_bye"
                ],
                "description": "Transfer method. sip_bye can omit transferTarget and reroute by BYE headers."
              },
              "transferReferOutcomeMode": {
                "type": "string",
                "enum": [
                  "blind",
                  "notify"
                ],
                "description": "REFER outcome behavior. blind requests no REFER NOTIFY result; notify requests provider REFER NOTIFY result when supported."
              },
              "transferReferredByURL": {
                "type": "string",
                "description": "SIP Referred-By value for transfer methods that use it."
              },
              "transferNotifications": {
                "type": "boolean",
                "description": "Whether the integration requests transfer outcome notifications."
              },
              "transferNotificationsHangupMS": {
                "type": "number",
                "minimum": 0,
                "maximum": 10000,
                "description": "Grace period in milliseconds before disconnecting after transfer."
              },
              "transferRoutingPool": {
                "type": "string",
                "description": "Routing pool selector for number targets. Ignored for full SIP URI targets."
              },
              "transferSipHeaders": {
                "type": "array",
                "maxItems": 100,
                "items": {
                  "type": "object",
                  "required": [
                    "name",
                    "value"
                  ],
                  "properties": {
                    "name": {
                      "type": "string",
                      "description": "SIP header name."
                    },
                    "value": {
                      "type": "string",
                      "description": "SIP header value."
                    }
                  }
                },
                "description": "SIP headers added to the transfer request. Total header name/value length must stay below 12000 characters."
              },
              "transferSipQueryParams": {
                "oneOf": [
                  {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  },
                  {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "name",
                        "value"
                      ],
                      "properties": {
                        "name": {
                          "type": "string",
                          "description": "SIP header name."
                        },
                        "value": {
                          "type": "string",
                          "description": "SIP header value."
                        }
                      }
                    }
                  }
                ],
                "description": "Refer-To / target URI query parameters. Accepts either an object map or an array of {name, value} pairs."
              },
              "sipQueryParams": {
                "oneOf": [
                  {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  },
                  {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "name",
                        "value"
                      ],
                      "properties": {
                        "name": {
                          "type": "string",
                          "description": "SIP header name."
                        },
                        "value": {
                          "type": "string",
                          "description": "SIP header value."
                        }
                      }
                    }
                  }
                ],
                "description": "Refer-To / target URI query parameters. Accepts either an object map or an array of {name, value} pairs."
              },
              "handoverReason": {
                "type": "string",
                "description": "Reason used by sip_bye disconnect-reconnect transfers."
              },
              "hangupReason": {
                "type": "string",
                "description": "Hangup reason, or fallback transfer reason for sip_bye."
              },
              "hangupSipHeaders": {
                "type": "array",
                "maxItems": 100,
                "items": {
                  "type": "object",
                  "required": [
                    "name",
                    "value"
                  ],
                  "properties": {
                    "name": {
                      "type": "string",
                      "description": "SIP header name."
                    },
                    "value": {
                      "type": "string",
                      "description": "SIP header value."
                    }
                  }
                },
                "description": "SIP headers added to the BYE message for hangup actions."
              },
              "botOperationResult": {
                "type": "string",
                "enum": [
                  "success",
                  "failure"
                ],
                "description": "Bot outcome recorded for CDR / call-status consumers."
              },
              "botOperationData": {
                "type": "object",
                "additionalProperties": true,
                "description": "Additional structured bot outcome data."
              }
            }
          }
        }
      },
      "TobiBotControlEventValue": {
        "type": "object",
        "additionalProperties": true,
        "description": "Payload for a Vodafone custom control event (`messages[].content.event.custom`). TelAPI maps hangup, transfer/handover, DTMF config, bot-delay config, and bounded STT/TTS config events to runtime commands for TelPhi.",
        "properties": {
          "sessionParams": {
            "type": "object",
            "additionalProperties": false,
            "description": "Dynamic runtime settings sent by TOBi in a config custom event. Missing keys keep the current call value. DTMF fields update digit handling; STT/TTS fields update bounded timeout/retry settings or select a provider from the configured fallback chain; bot-delay fields update comfort prompts and final no-input timing for the current call.",
            "properties": {
              "sendDTMF": {
                "type": "boolean",
                "description": "Enables forwarding caller DTMF digits to TOBi."
              },
              "bargeInOnDTMF": {
                "type": "boolean",
                "description": "Allows DTMF input to interrupt current bot playback."
              },
              "bargeIn": {
                "type": "boolean",
                "description": "General bot barge-in flag. DTMF playback interruption requires this and bargeInOnDTMF to be true."
              },
              "bargeInMinWordCount": {
                "type": "integer",
                "minimum": 0,
                "description": "Minimum word count threshold associated with bot barge-in configuration."
              },
              "dtmfCollect": {
                "type": "boolean",
                "description": "Collects multiple DTMF digits before forwarding them to TOBi."
              },
              "dtmfDigits": {
                "type": "string",
                "description": "Allowed DTMF symbols. Digits outside this set are ignored before collection or forwarding.",
                "example": "0123456789#*"
              },
              "dtmfCollectInterDigitTimeoutMS": {
                "type": "integer",
                "minimum": 0,
                "description": "Milliseconds to wait for another digit before forwarding the collected input."
              },
              "dtmfCollectMaxDigits": {
                "type": "integer",
                "minimum": 0,
                "description": "Maximum digits to collect before forwarding. 0 disables the max-digits trigger."
              },
              "dtmfCollectSubmitDigit": {
                "type": "string",
                "maxLength": 1,
                "description": "Submit digit that ends collection. Empty string disables submit-digit termination."
              },
              "sttProvider": {
                "type": "string",
                "description": "Selects an STT provider from the Delphi-configured primary/fallback chain. Does not create providers or change credentials.",
                "example": "azure_stt"
              },
              "sttConnectionTimeoutMS": {
                "type": "integer",
                "minimum": 10,
                "maximum": 600000,
                "description": "Maximum wait for STT provider connection/request response."
              },
              "sttRetries": {
                "type": "integer",
                "minimum": 0,
                "maximum": 10,
                "description": "Same-provider STT retries before fallback/failover."
              },
              "ttsProvider": {
                "type": "string",
                "description": "Selects a TTS provider from the Delphi-configured primary/fallback chain. Does not create providers or change credentials.",
                "example": "openai_tts"
              },
              "ttsConnectionTimeoutMS": {
                "type": "integer",
                "minimum": 10,
                "maximum": 600000,
                "description": "Maximum wait for TTS provider response."
              },
              "ttsRetries": {
                "type": "integer",
                "minimum": 0,
                "maximum": 10,
                "description": "Same-provider TTS retries before fallback/failover."
              },
              "botNoInputGiveUpTimeoutMS": {
                "type": "integer",
                "minimum": 1000,
                "maximum": 120000,
                "description": "Final no-input timeout in milliseconds before bot_timeout failover. Alias botNoInputGiveUpTimeoutMs is also accepted."
              },
              "botNoInputGiveUpTimeoutMs": {
                "type": "integer",
                "minimum": 1000,
                "maximum": 120000,
                "description": "Camel-case alias for botNoInputGiveUpTimeoutMS. Final no-input timeout before bot_timeout failover."
              },
              "botNoInputTimeoutMs": {
                "type": "integer",
                "minimum": 500,
                "maximum": 60000,
                "description": "Milliseconds of bot silence before playing the comfort prompt. Omit to keep the current value."
              },
              "botNoInputSpeech": {
                "type": "string",
                "maxLength": 500,
                "description": "TTS text for the comfort prompt while waiting for the bot response.",
                "example": "One moment while I check that."
              },
              "botNoInputUrl": {
                "type": "string",
                "description": "Audio URL for the comfort prompt. Used as an alternative to generated speech.",
                "example": "https://example.com/comfort.wav"
              },
              "botNoInputRetries": {
                "type": "integer",
                "minimum": 0,
                "maximum": 10,
                "description": "How many times to repeat the comfort prompt while the bot remains silent."
              },
              "abortPlayOnBotMessage": {
                "type": "boolean",
                "description": "Stops comfort playback as soon as the bot responds."
              },
              "resumeRecognitionTimeoutMs": {
                "type": "integer",
                "minimum": 1000,
                "maximum": 60000,
                "description": "Milliseconds before resuming STT recognition after comfort playback."
              },
              "resumeRecognitionSpeech": {
                "type": "string",
                "maxLength": 500,
                "description": "TTS text to play when recognition is resumed.",
                "example": "Are you still there?"
              }
            }
          },
          "transferTarget": {
            "type": "string",
            "description": "Transfer destination. May be a phone number, tel: URI, sip: URI, or sips: URI."
          },
          "target": {
            "type": "string",
            "description": "Alternative name for transferTarget."
          },
          "transferMethod": {
            "type": "string",
            "enum": [
              "sip_refer",
              "sip_refer_replace",
              "sip_invite",
              "sip_invite_bridge",
              "sip_invite_reinvite_p2p",
              "sip_bye"
            ],
            "description": "Transfer method. sip_bye can omit transferTarget and reroute by BYE headers."
          },
          "transferReferOutcomeMode": {
            "type": "string",
            "enum": [
              "blind",
              "notify"
            ],
            "description": "REFER outcome behavior. blind requests no REFER NOTIFY result; notify requests provider REFER NOTIFY result when supported."
          },
          "transferReferredByURL": {
            "type": "string",
            "description": "SIP Referred-By value for transfer methods that use it."
          },
          "transferNotifications": {
            "type": "boolean",
            "description": "Whether the integration requests transfer outcome notifications."
          },
          "transferNotificationsHangupMS": {
            "type": "number",
            "minimum": 0,
            "maximum": 10000,
            "description": "Grace period in milliseconds before disconnecting after transfer."
          },
          "transferRoutingPool": {
            "type": "string",
            "description": "Routing pool selector for number targets. Ignored for full SIP URI targets."
          },
          "transferSipHeaders": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "type": "object",
              "required": [
                "name",
                "value"
              ],
              "properties": {
                "name": {
                  "type": "string",
                  "description": "SIP header name."
                },
                "value": {
                  "type": "string",
                  "description": "SIP header value."
                }
              }
            },
            "description": "SIP headers added to the transfer request. Total header name/value length must stay below 12000 characters."
          },
          "transferSipQueryParams": {
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "name",
                    "value"
                  ],
                  "properties": {
                    "name": {
                      "type": "string",
                      "description": "SIP header name."
                    },
                    "value": {
                      "type": "string",
                      "description": "SIP header value."
                    }
                  }
                }
              }
            ],
            "description": "Refer-To / target URI query parameters. Accepts either an object map or an array of {name, value} pairs."
          },
          "sipQueryParams": {
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "name",
                    "value"
                  ],
                  "properties": {
                    "name": {
                      "type": "string",
                      "description": "SIP header name."
                    },
                    "value": {
                      "type": "string",
                      "description": "SIP header value."
                    }
                  }
                }
              }
            ],
            "description": "Refer-To / target URI query parameters. Accepts either an object map or an array of {name, value} pairs."
          },
          "handoverReason": {
            "type": "string",
            "description": "Reason used by sip_bye disconnect-reconnect transfers."
          },
          "hangupReason": {
            "type": "string",
            "description": "Hangup reason, or fallback transfer reason for sip_bye."
          },
          "hangupSipHeaders": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "type": "object",
              "required": [
                "name",
                "value"
              ],
              "properties": {
                "name": {
                  "type": "string",
                  "description": "SIP header name."
                },
                "value": {
                  "type": "string",
                  "description": "SIP header value."
                }
              }
            },
            "description": "SIP headers added to the BYE message for hangup actions."
          },
          "botOperationResult": {
            "type": "string",
            "enum": [
              "success",
              "failure"
            ],
            "description": "Bot outcome recorded for CDR / call-status consumers."
          },
          "botOperationData": {
            "type": "object",
            "additionalProperties": true,
            "description": "Additional structured bot outcome data."
          }
        }
      },
      "TobiCallbackBody": {
        "type": "object",
        "description": "TOBi callback payload sent by Vodafone (SendMessageResponse). TelAPI requires conversation.identifier.id so the callback can be correlated to the TOBi channel.",
        "properties": {
          "conversation": {
            "type": "object",
            "properties": {
              "identifier": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "TOBi conversation identifier. Required by TelAPI."
                  },
                  "name": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "from": {
            "type": "object",
            "properties": {
              "identifier": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  }
                }
              },
              "avatar": {
                "type": "string"
              }
            }
          },
          "messages": {
            "type": "object",
            "properties": {
              "timestamp": {
                "type": "string",
                "format": "date-time",
                "description": "Message timestamp as an RFC 3339 / ISO 8601 date-time with timezone.",
                "example": "2026-05-06T10:15:30.000Z"
              },
              "message": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "content"
                  ],
                  "properties": {
                    "messageId": {
                      "type": "number"
                    },
                    "relatesTo": {
                      "type": "number"
                    },
                    "sequence": {
                      "type": "number"
                    },
                    "content": {
                      "type": "object",
                      "properties": {
                        "textMessage": {
                          "type": "object",
                          "properties": {
                            "textPlain": {
                              "type": "string"
                            },
                            "textHtml": {
                              "type": "string"
                            },
                            "textMarkdown": {
                              "type": "string"
                            }
                          }
                        },
                        "voiceMessage": {
                          "type": "string"
                        },
                        "event": {
                          "type": "object",
                          "properties": {
                            "typing": {
                              "type": "object",
                              "required": [
                                "enable"
                              ],
                              "properties": {
                                "enable": {
                                  "type": "boolean"
                                },
                                "duration": {
                                  "type": "number"
                                }
                              }
                            },
                            "pause": {
                              "type": "number"
                            },
                            "messageRead": {
                              "type": "string"
                            },
                            "noInputExpected": {
                              "type": "boolean"
                            },
                            "custom": {
                              "type": "object",
                              "required": [
                                "name"
                              ],
                              "description": "Vodafone bot-originated control event embedded in a spoken message. This is the supported way to request hangup, transfer, or dynamic DTMF/STT/TTS/bot-delay config changes from TOBi callbacks.",
                              "properties": {
                                "name": {
                                  "type": "string",
                                  "enum": [
                                    "hangup",
                                    "transfer",
                                    "handover",
                                    "config"
                                  ],
                                  "description": "Control event name. handover is treated as transfer; config can update DTMF/STT/TTS/bot-delay sessionParams."
                                },
                                "value": {
                                  "type": "object",
                                  "additionalProperties": true,
                                  "description": "Payload for a Vodafone custom control event (`messages[].content.event.custom`). TelAPI maps hangup, transfer/handover, DTMF config, bot-delay config, and bounded STT/TTS config events to runtime commands for TelPhi.",
                                  "properties": {
                                    "sessionParams": {
                                      "type": "object",
                                      "additionalProperties": false,
                                      "description": "Dynamic runtime settings sent by TOBi in a config custom event. Missing keys keep the current call value. DTMF fields update digit handling; STT/TTS fields update bounded timeout/retry settings or select a provider from the configured fallback chain; bot-delay fields update comfort prompts and final no-input timing for the current call.",
                                      "properties": {
                                        "sendDTMF": {
                                          "type": "boolean",
                                          "description": "Enables forwarding caller DTMF digits to TOBi."
                                        },
                                        "bargeInOnDTMF": {
                                          "type": "boolean",
                                          "description": "Allows DTMF input to interrupt current bot playback."
                                        },
                                        "bargeIn": {
                                          "type": "boolean",
                                          "description": "General bot barge-in flag. DTMF playback interruption requires this and bargeInOnDTMF to be true."
                                        },
                                        "bargeInMinWordCount": {
                                          "type": "integer",
                                          "minimum": 0,
                                          "description": "Minimum word count threshold associated with bot barge-in configuration."
                                        },
                                        "dtmfCollect": {
                                          "type": "boolean",
                                          "description": "Collects multiple DTMF digits before forwarding them to TOBi."
                                        },
                                        "dtmfDigits": {
                                          "type": "string",
                                          "description": "Allowed DTMF symbols. Digits outside this set are ignored before collection or forwarding.",
                                          "example": "0123456789#*"
                                        },
                                        "dtmfCollectInterDigitTimeoutMS": {
                                          "type": "integer",
                                          "minimum": 0,
                                          "description": "Milliseconds to wait for another digit before forwarding the collected input."
                                        },
                                        "dtmfCollectMaxDigits": {
                                          "type": "integer",
                                          "minimum": 0,
                                          "description": "Maximum digits to collect before forwarding. 0 disables the max-digits trigger."
                                        },
                                        "dtmfCollectSubmitDigit": {
                                          "type": "string",
                                          "maxLength": 1,
                                          "description": "Submit digit that ends collection. Empty string disables submit-digit termination."
                                        },
                                        "sttProvider": {
                                          "type": "string",
                                          "description": "Selects an STT provider from the Delphi-configured primary/fallback chain. Does not create providers or change credentials.",
                                          "example": "azure_stt"
                                        },
                                        "sttConnectionTimeoutMS": {
                                          "type": "integer",
                                          "minimum": 10,
                                          "maximum": 600000,
                                          "description": "Maximum wait for STT provider connection/request response."
                                        },
                                        "sttRetries": {
                                          "type": "integer",
                                          "minimum": 0,
                                          "maximum": 10,
                                          "description": "Same-provider STT retries before fallback/failover."
                                        },
                                        "ttsProvider": {
                                          "type": "string",
                                          "description": "Selects a TTS provider from the Delphi-configured primary/fallback chain. Does not create providers or change credentials.",
                                          "example": "openai_tts"
                                        },
                                        "ttsConnectionTimeoutMS": {
                                          "type": "integer",
                                          "minimum": 10,
                                          "maximum": 600000,
                                          "description": "Maximum wait for TTS provider response."
                                        },
                                        "ttsRetries": {
                                          "type": "integer",
                                          "minimum": 0,
                                          "maximum": 10,
                                          "description": "Same-provider TTS retries before fallback/failover."
                                        },
                                        "botNoInputGiveUpTimeoutMS": {
                                          "type": "integer",
                                          "minimum": 1000,
                                          "maximum": 120000,
                                          "description": "Final no-input timeout in milliseconds before bot_timeout failover. Alias botNoInputGiveUpTimeoutMs is also accepted."
                                        },
                                        "botNoInputGiveUpTimeoutMs": {
                                          "type": "integer",
                                          "minimum": 1000,
                                          "maximum": 120000,
                                          "description": "Camel-case alias for botNoInputGiveUpTimeoutMS. Final no-input timeout before bot_timeout failover."
                                        },
                                        "botNoInputTimeoutMs": {
                                          "type": "integer",
                                          "minimum": 500,
                                          "maximum": 60000,
                                          "description": "Milliseconds of bot silence before playing the comfort prompt. Omit to keep the current value."
                                        },
                                        "botNoInputSpeech": {
                                          "type": "string",
                                          "maxLength": 500,
                                          "description": "TTS text for the comfort prompt while waiting for the bot response.",
                                          "example": "One moment while I check that."
                                        },
                                        "botNoInputUrl": {
                                          "type": "string",
                                          "description": "Audio URL for the comfort prompt. Used as an alternative to generated speech.",
                                          "example": "https://example.com/comfort.wav"
                                        },
                                        "botNoInputRetries": {
                                          "type": "integer",
                                          "minimum": 0,
                                          "maximum": 10,
                                          "description": "How many times to repeat the comfort prompt while the bot remains silent."
                                        },
                                        "abortPlayOnBotMessage": {
                                          "type": "boolean",
                                          "description": "Stops comfort playback as soon as the bot responds."
                                        },
                                        "resumeRecognitionTimeoutMs": {
                                          "type": "integer",
                                          "minimum": 1000,
                                          "maximum": 60000,
                                          "description": "Milliseconds before resuming STT recognition after comfort playback."
                                        },
                                        "resumeRecognitionSpeech": {
                                          "type": "string",
                                          "maxLength": 500,
                                          "description": "TTS text to play when recognition is resumed.",
                                          "example": "Are you still there?"
                                        }
                                      }
                                    },
                                    "transferTarget": {
                                      "type": "string",
                                      "description": "Transfer destination. May be a phone number, tel: URI, sip: URI, or sips: URI."
                                    },
                                    "target": {
                                      "type": "string",
                                      "description": "Alternative name for transferTarget."
                                    },
                                    "transferMethod": {
                                      "type": "string",
                                      "enum": [
                                        "sip_refer",
                                        "sip_refer_replace",
                                        "sip_invite",
                                        "sip_invite_bridge",
                                        "sip_invite_reinvite_p2p",
                                        "sip_bye"
                                      ],
                                      "description": "Transfer method. sip_bye can omit transferTarget and reroute by BYE headers."
                                    },
                                    "transferReferOutcomeMode": {
                                      "type": "string",
                                      "enum": [
                                        "blind",
                                        "notify"
                                      ],
                                      "description": "REFER outcome behavior. blind requests no REFER NOTIFY result; notify requests provider REFER NOTIFY result when supported."
                                    },
                                    "transferReferredByURL": {
                                      "type": "string",
                                      "description": "SIP Referred-By value for transfer methods that use it."
                                    },
                                    "transferNotifications": {
                                      "type": "boolean",
                                      "description": "Whether the integration requests transfer outcome notifications."
                                    },
                                    "transferNotificationsHangupMS": {
                                      "type": "number",
                                      "minimum": 0,
                                      "maximum": 10000,
                                      "description": "Grace period in milliseconds before disconnecting after transfer."
                                    },
                                    "transferRoutingPool": {
                                      "type": "string",
                                      "description": "Routing pool selector for number targets. Ignored for full SIP URI targets."
                                    },
                                    "transferSipHeaders": {
                                      "type": "array",
                                      "maxItems": 100,
                                      "items": {
                                        "type": "object",
                                        "required": [
                                          "name",
                                          "value"
                                        ],
                                        "properties": {
                                          "name": {
                                            "type": "string",
                                            "description": "SIP header name."
                                          },
                                          "value": {
                                            "type": "string",
                                            "description": "SIP header value."
                                          }
                                        }
                                      },
                                      "description": "SIP headers added to the transfer request. Total header name/value length must stay below 12000 characters."
                                    },
                                    "transferSipQueryParams": {
                                      "oneOf": [
                                        {
                                          "type": "object",
                                          "additionalProperties": {
                                            "type": "string"
                                          }
                                        },
                                        {
                                          "type": "array",
                                          "items": {
                                            "type": "object",
                                            "required": [
                                              "name",
                                              "value"
                                            ],
                                            "properties": {
                                              "name": {
                                                "type": "string",
                                                "description": "SIP header name."
                                              },
                                              "value": {
                                                "type": "string",
                                                "description": "SIP header value."
                                              }
                                            }
                                          }
                                        }
                                      ],
                                      "description": "Refer-To / target URI query parameters. Accepts either an object map or an array of {name, value} pairs."
                                    },
                                    "sipQueryParams": {
                                      "oneOf": [
                                        {
                                          "type": "object",
                                          "additionalProperties": {
                                            "type": "string"
                                          }
                                        },
                                        {
                                          "type": "array",
                                          "items": {
                                            "type": "object",
                                            "required": [
                                              "name",
                                              "value"
                                            ],
                                            "properties": {
                                              "name": {
                                                "type": "string",
                                                "description": "SIP header name."
                                              },
                                              "value": {
                                                "type": "string",
                                                "description": "SIP header value."
                                              }
                                            }
                                          }
                                        }
                                      ],
                                      "description": "Refer-To / target URI query parameters. Accepts either an object map or an array of {name, value} pairs."
                                    },
                                    "handoverReason": {
                                      "type": "string",
                                      "description": "Reason used by sip_bye disconnect-reconnect transfers."
                                    },
                                    "hangupReason": {
                                      "type": "string",
                                      "description": "Hangup reason, or fallback transfer reason for sip_bye."
                                    },
                                    "hangupSipHeaders": {
                                      "type": "array",
                                      "maxItems": 100,
                                      "items": {
                                        "type": "object",
                                        "required": [
                                          "name",
                                          "value"
                                        ],
                                        "properties": {
                                          "name": {
                                            "type": "string",
                                            "description": "SIP header name."
                                          },
                                          "value": {
                                            "type": "string",
                                            "description": "SIP header value."
                                          }
                                        }
                                      },
                                      "description": "SIP headers added to the BYE message for hangup actions."
                                    },
                                    "botOperationResult": {
                                      "type": "string",
                                      "enum": [
                                        "success",
                                        "failure"
                                      ],
                                      "description": "Bot outcome recorded for CDR / call-status consumers."
                                    },
                                    "botOperationData": {
                                      "type": "object",
                                      "additionalProperties": true,
                                      "description": "Additional structured bot outcome data."
                                    }
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Legacy provider metadata. Vodafone recommends channelData instead, but TelAPI forwards metadata when present."
          },
          "channelData": {
            "type": "array",
            "description": "Outgoing channel data returned by TOBi on callbacks (Vodafone OutgoingChannelData). This is bot-to-platform context such as scenario flags or routing hints.\n\nDo not confuse this with Delphi inbound channel-data forwarding configured in TelWeb (Flow Builder → channelDataRules), which maps SIP INVITE headers into channelData on create-conversation requests sent to TOBi. Callback channelData travels in the opposite direction and is forwarded opaquely on the TelPhi Redis stream for observability.",
            "items": {
              "type": "object",
              "required": [
                "key",
                "value"
              ],
              "description": "Single TOBi channel-data variable. Matches Vodafone ChannelDataEntry on bot callbacks.",
              "properties": {
                "key": {
                  "type": "string",
                  "description": "Variable name exposed by the bot or channel platform.",
                  "example": "scenarioTriggered"
                },
                "value": {
                  "description": "Variable value. May be a string, number, boolean, object, or array depending on the bot journey."
                },
                "sensitiveData": {
                  "type": "boolean",
                  "description": "When true, marks the value as sensitive for provider-side logging and storage policies."
                }
              }
            },
            "example": [
              {
                "key": "scenarioTriggered",
                "value": "Billing information"
              },
              {
                "key": "phoneCallbackRequired",
                "value": true
              }
            ]
          },
          "nluData": {
            "type": "object",
            "properties": {
              "intents": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              },
              "entities": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              },
              "sentiment": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        }
      },
      "TobiCallbackSuccessResponse": {
        "type": "object",
        "description": "Acknowledgement that TelAPI accepted the callback and published it to the TelPhi consumer stream.",
        "required": [
          "received",
          "streamId"
        ],
        "properties": {
          "received": {
            "type": "boolean",
            "enum": [
              true
            ],
            "description": "Always true when the callback payload is accepted."
          },
          "streamId": {
            "type": "string",
            "description": "Redis stream entry id for the forwarded TOBi payload. The same acknowledgement shape is returned for message, hangup, and transfer callbacks.",
            "example": "1778022191343-0"
          }
        }
      },
      "TobiChannelDataEntry": {
        "type": "object",
        "required": [
          "key",
          "value"
        ],
        "description": "Single TOBi channel-data variable. Matches Vodafone ChannelDataEntry on bot callbacks.",
        "properties": {
          "key": {
            "type": "string",
            "description": "Variable name exposed by the bot or channel platform.",
            "example": "scenarioTriggered"
          },
          "value": {
            "description": "Variable value. May be a string, number, boolean, object, or array depending on the bot journey."
          },
          "sensitiveData": {
            "type": "boolean",
            "description": "When true, marks the value as sensitive for provider-side logging and storage policies."
          }
        }
      },
      "TobiOutgoingChannelData": {
        "type": "array",
        "description": "Outgoing channel data returned by TOBi on callbacks (Vodafone OutgoingChannelData). This is bot-to-platform context such as scenario flags or routing hints.\n\nDo not confuse this with Delphi inbound channel-data forwarding configured in TelWeb (Flow Builder → channelDataRules), which maps SIP INVITE headers into channelData on create-conversation requests sent to TOBi. Callback channelData travels in the opposite direction and is forwarded opaquely on the TelPhi Redis stream for observability.",
        "items": {
          "type": "object",
          "required": [
            "key",
            "value"
          ],
          "description": "Single TOBi channel-data variable. Matches Vodafone ChannelDataEntry on bot callbacks.",
          "properties": {
            "key": {
              "type": "string",
              "description": "Variable name exposed by the bot or channel platform.",
              "example": "scenarioTriggered"
            },
            "value": {
              "description": "Variable value. May be a string, number, boolean, object, or array depending on the bot journey."
            },
            "sensitiveData": {
              "type": "boolean",
              "description": "When true, marks the value as sensitive for provider-side logging and storage policies."
            }
          }
        },
        "example": [
          {
            "key": "scenarioTriggered",
            "value": "Billing information"
          },
          {
            "key": "phoneCallbackRequired",
            "value": true
          }
        ]
      }
    }
  },
  "paths": {
    "/health": {
      "get": {
        "summary": "Service health",
        "tags": [
          "Health"
        ],
        "description": "Returns 200 when required dependencies (Redis) are reachable; otherwise 503. Minimal body — monitor detail via OpenTelemetry.",
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status"
                  ],
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "ok"
                      ]
                    }
                  }
                },
                "example": {
                  "status": "ok"
                }
              }
            }
          },
          "503": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status"
                  ],
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "error"
                      ]
                    }
                  }
                },
                "example": {
                  "status": "error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/webhooks/tobi/{channelId}": {
      "post": {
        "summary": "Receive TOBi chatbot callback",
        "tags": [
          "Webhooks"
        ],
        "description": "Receives message callbacks from a TOBi chatbot and forwards them to TelPhi. Call/trace correlation is loaded from the Redis callback session written by TelPhi. The callback URL only needs `?token=`; legacy trace/call query params are ignored when a session exists. Each callback must include the per-conversation callback token in the token header.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "TOBi callback payload sent by Vodafone (SendMessageResponse). TelAPI requires conversation.identifier.id so the callback can be correlated to the TOBi channel.",
                "properties": {
                  "conversation": {
                    "type": "object",
                    "properties": {
                      "identifier": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "TOBi conversation identifier. Required by TelAPI."
                          },
                          "name": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "from": {
                    "type": "object",
                    "properties": {
                      "identifier": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          }
                        }
                      },
                      "avatar": {
                        "type": "string"
                      }
                    }
                  },
                  "messages": {
                    "type": "object",
                    "properties": {
                      "timestamp": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Message timestamp as an RFC 3339 / ISO 8601 date-time with timezone.",
                        "example": "2026-05-06T10:15:30.000Z"
                      },
                      "message": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "required": [
                            "content"
                          ],
                          "properties": {
                            "messageId": {
                              "type": "number"
                            },
                            "relatesTo": {
                              "type": "number"
                            },
                            "sequence": {
                              "type": "number"
                            },
                            "content": {
                              "type": "object",
                              "properties": {
                                "textMessage": {
                                  "type": "object",
                                  "properties": {
                                    "textPlain": {
                                      "type": "string"
                                    },
                                    "textHtml": {
                                      "type": "string"
                                    },
                                    "textMarkdown": {
                                      "type": "string"
                                    }
                                  }
                                },
                                "voiceMessage": {
                                  "type": "string"
                                },
                                "event": {
                                  "type": "object",
                                  "properties": {
                                    "typing": {
                                      "type": "object",
                                      "required": [
                                        "enable"
                                      ],
                                      "properties": {
                                        "enable": {
                                          "type": "boolean"
                                        },
                                        "duration": {
                                          "type": "number"
                                        }
                                      }
                                    },
                                    "pause": {
                                      "type": "number"
                                    },
                                    "messageRead": {
                                      "type": "string"
                                    },
                                    "noInputExpected": {
                                      "type": "boolean"
                                    },
                                    "custom": {
                                      "type": "object",
                                      "required": [
                                        "name"
                                      ],
                                      "description": "Vodafone bot-originated control event embedded in a spoken message. This is the supported way to request hangup, transfer, or dynamic DTMF/STT/TTS/bot-delay config changes from TOBi callbacks.",
                                      "properties": {
                                        "name": {
                                          "type": "string",
                                          "enum": [
                                            "hangup",
                                            "transfer",
                                            "handover",
                                            "config"
                                          ],
                                          "description": "Control event name. handover is treated as transfer; config can update DTMF/STT/TTS/bot-delay sessionParams."
                                        },
                                        "value": {
                                          "type": "object",
                                          "additionalProperties": true,
                                          "description": "Payload for a Vodafone custom control event (`messages[].content.event.custom`). TelAPI maps hangup, transfer/handover, DTMF config, bot-delay config, and bounded STT/TTS config events to runtime commands for TelPhi.",
                                          "properties": {
                                            "sessionParams": {
                                              "type": "object",
                                              "additionalProperties": false,
                                              "description": "Dynamic runtime settings sent by TOBi in a config custom event. Missing keys keep the current call value. DTMF fields update digit handling; STT/TTS fields update bounded timeout/retry settings or select a provider from the configured fallback chain; bot-delay fields update comfort prompts and final no-input timing for the current call.",
                                              "properties": {
                                                "sendDTMF": {
                                                  "type": "boolean",
                                                  "description": "Enables forwarding caller DTMF digits to TOBi."
                                                },
                                                "bargeInOnDTMF": {
                                                  "type": "boolean",
                                                  "description": "Allows DTMF input to interrupt current bot playback."
                                                },
                                                "bargeIn": {
                                                  "type": "boolean",
                                                  "description": "General bot barge-in flag. DTMF playback interruption requires this and bargeInOnDTMF to be true."
                                                },
                                                "bargeInMinWordCount": {
                                                  "type": "integer",
                                                  "minimum": 0,
                                                  "description": "Minimum word count threshold associated with bot barge-in configuration."
                                                },
                                                "dtmfCollect": {
                                                  "type": "boolean",
                                                  "description": "Collects multiple DTMF digits before forwarding them to TOBi."
                                                },
                                                "dtmfDigits": {
                                                  "type": "string",
                                                  "description": "Allowed DTMF symbols. Digits outside this set are ignored before collection or forwarding.",
                                                  "example": "0123456789#*"
                                                },
                                                "dtmfCollectInterDigitTimeoutMS": {
                                                  "type": "integer",
                                                  "minimum": 0,
                                                  "description": "Milliseconds to wait for another digit before forwarding the collected input."
                                                },
                                                "dtmfCollectMaxDigits": {
                                                  "type": "integer",
                                                  "minimum": 0,
                                                  "description": "Maximum digits to collect before forwarding. 0 disables the max-digits trigger."
                                                },
                                                "dtmfCollectSubmitDigit": {
                                                  "type": "string",
                                                  "maxLength": 1,
                                                  "description": "Submit digit that ends collection. Empty string disables submit-digit termination."
                                                },
                                                "sttProvider": {
                                                  "type": "string",
                                                  "description": "Selects an STT provider from the Delphi-configured primary/fallback chain. Does not create providers or change credentials.",
                                                  "example": "azure_stt"
                                                },
                                                "sttConnectionTimeoutMS": {
                                                  "type": "integer",
                                                  "minimum": 10,
                                                  "maximum": 600000,
                                                  "description": "Maximum wait for STT provider connection/request response."
                                                },
                                                "sttRetries": {
                                                  "type": "integer",
                                                  "minimum": 0,
                                                  "maximum": 10,
                                                  "description": "Same-provider STT retries before fallback/failover."
                                                },
                                                "ttsProvider": {
                                                  "type": "string",
                                                  "description": "Selects a TTS provider from the Delphi-configured primary/fallback chain. Does not create providers or change credentials.",
                                                  "example": "openai_tts"
                                                },
                                                "ttsConnectionTimeoutMS": {
                                                  "type": "integer",
                                                  "minimum": 10,
                                                  "maximum": 600000,
                                                  "description": "Maximum wait for TTS provider response."
                                                },
                                                "ttsRetries": {
                                                  "type": "integer",
                                                  "minimum": 0,
                                                  "maximum": 10,
                                                  "description": "Same-provider TTS retries before fallback/failover."
                                                },
                                                "botNoInputGiveUpTimeoutMS": {
                                                  "type": "integer",
                                                  "minimum": 1000,
                                                  "maximum": 120000,
                                                  "description": "Final no-input timeout in milliseconds before bot_timeout failover. Alias botNoInputGiveUpTimeoutMs is also accepted."
                                                },
                                                "botNoInputGiveUpTimeoutMs": {
                                                  "type": "integer",
                                                  "minimum": 1000,
                                                  "maximum": 120000,
                                                  "description": "Camel-case alias for botNoInputGiveUpTimeoutMS. Final no-input timeout before bot_timeout failover."
                                                },
                                                "botNoInputTimeoutMs": {
                                                  "type": "integer",
                                                  "minimum": 500,
                                                  "maximum": 60000,
                                                  "description": "Milliseconds of bot silence before playing the comfort prompt. Omit to keep the current value."
                                                },
                                                "botNoInputSpeech": {
                                                  "type": "string",
                                                  "maxLength": 500,
                                                  "description": "TTS text for the comfort prompt while waiting for the bot response.",
                                                  "example": "One moment while I check that."
                                                },
                                                "botNoInputUrl": {
                                                  "type": "string",
                                                  "description": "Audio URL for the comfort prompt. Used as an alternative to generated speech.",
                                                  "example": "https://example.com/comfort.wav"
                                                },
                                                "botNoInputRetries": {
                                                  "type": "integer",
                                                  "minimum": 0,
                                                  "maximum": 10,
                                                  "description": "How many times to repeat the comfort prompt while the bot remains silent."
                                                },
                                                "abortPlayOnBotMessage": {
                                                  "type": "boolean",
                                                  "description": "Stops comfort playback as soon as the bot responds."
                                                },
                                                "resumeRecognitionTimeoutMs": {
                                                  "type": "integer",
                                                  "minimum": 1000,
                                                  "maximum": 60000,
                                                  "description": "Milliseconds before resuming STT recognition after comfort playback."
                                                },
                                                "resumeRecognitionSpeech": {
                                                  "type": "string",
                                                  "maxLength": 500,
                                                  "description": "TTS text to play when recognition is resumed.",
                                                  "example": "Are you still there?"
                                                }
                                              }
                                            },
                                            "transferTarget": {
                                              "type": "string",
                                              "description": "Transfer destination. May be a phone number, tel: URI, sip: URI, or sips: URI."
                                            },
                                            "target": {
                                              "type": "string",
                                              "description": "Alternative name for transferTarget."
                                            },
                                            "transferMethod": {
                                              "type": "string",
                                              "enum": [
                                                "sip_refer",
                                                "sip_refer_replace",
                                                "sip_invite",
                                                "sip_invite_bridge",
                                                "sip_invite_reinvite_p2p",
                                                "sip_bye"
                                              ],
                                              "description": "Transfer method. sip_bye can omit transferTarget and reroute by BYE headers."
                                            },
                                            "transferReferOutcomeMode": {
                                              "type": "string",
                                              "enum": [
                                                "blind",
                                                "notify"
                                              ],
                                              "description": "REFER outcome behavior. blind requests no REFER NOTIFY result; notify requests provider REFER NOTIFY result when supported."
                                            },
                                            "transferReferredByURL": {
                                              "type": "string",
                                              "description": "SIP Referred-By value for transfer methods that use it."
                                            },
                                            "transferNotifications": {
                                              "type": "boolean",
                                              "description": "Whether the integration requests transfer outcome notifications."
                                            },
                                            "transferNotificationsHangupMS": {
                                              "type": "number",
                                              "minimum": 0,
                                              "maximum": 10000,
                                              "description": "Grace period in milliseconds before disconnecting after transfer."
                                            },
                                            "transferRoutingPool": {
                                              "type": "string",
                                              "description": "Routing pool selector for number targets. Ignored for full SIP URI targets."
                                            },
                                            "transferSipHeaders": {
                                              "type": "array",
                                              "maxItems": 100,
                                              "items": {
                                                "type": "object",
                                                "required": [
                                                  "name",
                                                  "value"
                                                ],
                                                "properties": {
                                                  "name": {
                                                    "type": "string",
                                                    "description": "SIP header name."
                                                  },
                                                  "value": {
                                                    "type": "string",
                                                    "description": "SIP header value."
                                                  }
                                                }
                                              },
                                              "description": "SIP headers added to the transfer request. Total header name/value length must stay below 12000 characters."
                                            },
                                            "transferSipQueryParams": {
                                              "oneOf": [
                                                {
                                                  "type": "object",
                                                  "additionalProperties": {
                                                    "type": "string"
                                                  }
                                                },
                                                {
                                                  "type": "array",
                                                  "items": {
                                                    "type": "object",
                                                    "required": [
                                                      "name",
                                                      "value"
                                                    ],
                                                    "properties": {
                                                      "name": {
                                                        "type": "string",
                                                        "description": "SIP header name."
                                                      },
                                                      "value": {
                                                        "type": "string",
                                                        "description": "SIP header value."
                                                      }
                                                    }
                                                  }
                                                }
                                              ],
                                              "description": "Refer-To / target URI query parameters. Accepts either an object map or an array of {name, value} pairs."
                                            },
                                            "sipQueryParams": {
                                              "oneOf": [
                                                {
                                                  "type": "object",
                                                  "additionalProperties": {
                                                    "type": "string"
                                                  }
                                                },
                                                {
                                                  "type": "array",
                                                  "items": {
                                                    "type": "object",
                                                    "required": [
                                                      "name",
                                                      "value"
                                                    ],
                                                    "properties": {
                                                      "name": {
                                                        "type": "string",
                                                        "description": "SIP header name."
                                                      },
                                                      "value": {
                                                        "type": "string",
                                                        "description": "SIP header value."
                                                      }
                                                    }
                                                  }
                                                }
                                              ],
                                              "description": "Refer-To / target URI query parameters. Accepts either an object map or an array of {name, value} pairs."
                                            },
                                            "handoverReason": {
                                              "type": "string",
                                              "description": "Reason used by sip_bye disconnect-reconnect transfers."
                                            },
                                            "hangupReason": {
                                              "type": "string",
                                              "description": "Hangup reason, or fallback transfer reason for sip_bye."
                                            },
                                            "hangupSipHeaders": {
                                              "type": "array",
                                              "maxItems": 100,
                                              "items": {
                                                "type": "object",
                                                "required": [
                                                  "name",
                                                  "value"
                                                ],
                                                "properties": {
                                                  "name": {
                                                    "type": "string",
                                                    "description": "SIP header name."
                                                  },
                                                  "value": {
                                                    "type": "string",
                                                    "description": "SIP header value."
                                                  }
                                                }
                                              },
                                              "description": "SIP headers added to the BYE message for hangup actions."
                                            },
                                            "botOperationResult": {
                                              "type": "string",
                                              "enum": [
                                                "success",
                                                "failure"
                                              ],
                                              "description": "Bot outcome recorded for CDR / call-status consumers."
                                            },
                                            "botOperationData": {
                                              "type": "object",
                                              "additionalProperties": true,
                                              "description": "Additional structured bot outcome data."
                                            }
                                          }
                                        }
                                      }
                                    }
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Legacy provider metadata. Vodafone recommends channelData instead, but TelAPI forwards metadata when present."
                  },
                  "channelData": {
                    "type": "array",
                    "description": "Outgoing channel data returned by TOBi on callbacks (Vodafone OutgoingChannelData). This is bot-to-platform context such as scenario flags or routing hints.\n\nDo not confuse this with Delphi inbound channel-data forwarding configured in TelWeb (Flow Builder → channelDataRules), which maps SIP INVITE headers into channelData on create-conversation requests sent to TOBi. Callback channelData travels in the opposite direction and is forwarded opaquely on the TelPhi Redis stream for observability.",
                    "items": {
                      "type": "object",
                      "required": [
                        "key",
                        "value"
                      ],
                      "description": "Single TOBi channel-data variable. Matches Vodafone ChannelDataEntry on bot callbacks.",
                      "properties": {
                        "key": {
                          "type": "string",
                          "description": "Variable name exposed by the bot or channel platform.",
                          "example": "scenarioTriggered"
                        },
                        "value": {
                          "description": "Variable value. May be a string, number, boolean, object, or array depending on the bot journey."
                        },
                        "sensitiveData": {
                          "type": "boolean",
                          "description": "When true, marks the value as sensitive for provider-side logging and storage policies."
                        }
                      }
                    },
                    "example": [
                      {
                        "key": "scenarioTriggered",
                        "value": "Billing information"
                      },
                      {
                        "key": "phoneCallbackRequired",
                        "value": true
                      }
                    ]
                  },
                  "nluData": {
                    "type": "object",
                    "properties": {
                      "intents": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "additionalProperties": true
                        }
                      },
                      "entities": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "additionalProperties": true
                        }
                      },
                      "sentiment": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              },
              "examples": {
                "bot_message": {
                  "summary": "Bot text response",
                  "description": "Spoken or written bot content only. TelAPI extracts text from messages and forwards it to TelPhi.",
                  "value": {
                    "conversation": {
                      "identifier": {
                        "id": "tobi-conversation-123",
                        "name": "Customer conversation"
                      }
                    },
                    "messages": {
                      "timestamp": "2026-05-06T10:15:30.000Z",
                      "message": [
                        {
                          "messageId": 1,
                          "sequence": 1,
                          "content": {
                            "textMessage": {
                              "textPlain": "How can I help you today?"
                            }
                          }
                        }
                      ]
                    },
                    "channelData": [
                      {
                        "key": "scenarioTriggered",
                        "value": "General inquiry"
                      }
                    ],
                    "metadata": {
                      "locale": "de-DE"
                    }
                  }
                },
                "bot_hangup": {
                  "summary": "Bot-initiated hangup",
                  "description": "Hangup is sent as a Vodafone custom event on the final spoken message. TelAPI maps it to a runtime hangup command for TelPhi.",
                  "value": {
                    "conversation": {
                      "identifier": {
                        "id": "tobi-conversation-123",
                        "name": "Customer conversation"
                      }
                    },
                    "messages": {
                      "timestamp": "2026-05-06T10:18:45.000Z",
                      "message": [
                        {
                          "messageId": 4,
                          "sequence": 4,
                          "content": {
                            "textMessage": {
                              "textPlain": "Thank you for calling. Goodbye."
                            },
                            "event": {
                              "custom": {
                                "name": "hangup",
                                "value": {
                                  "hangupReason": "conversation_complete",
                                  "botOperationResult": "success"
                                }
                              }
                            }
                          }
                        }
                      ]
                    }
                  }
                },
                "bot_transfer": {
                  "summary": "Bot-initiated transfer",
                  "description": "Transfer destination and SIP options are carried in the same custom event model. TelAPI preserves spoken text and control-event order for TelPhi.",
                  "value": {
                    "conversation": {
                      "identifier": {
                        "id": "tobi-conversation-123",
                        "name": "Customer conversation"
                      }
                    },
                    "messages": {
                      "timestamp": "2026-05-06T10:17:10.000Z",
                      "message": [
                        {
                          "messageId": 3,
                          "sequence": 3,
                          "content": {
                            "textMessage": {
                              "textPlain": "I will connect you to a specialist now."
                            },
                            "event": {
                              "custom": {
                                "name": "transfer",
                                "value": {
                                  "transferTarget": "tel:+491701234567",
                                  "transferMethod": "sip_refer",
                                  "transferSipHeaders": [
                                    {
                                      "name": "X-Queue",
                                      "value": "care"
                                    }
                                  ],
                                  "transferNotifications": true,
                                  "transferNotificationsHangupMS": 3000
                                }
                              }
                            }
                          }
                        }
                      ]
                    }
                  }
                },
                "bot_config": {
                  "summary": "Bot-updated DTMF and speech config",
                  "description": "TOBi can update DTMF handling or bounded STT/TTS timeout/retry settings during an active call with a config custom event. Send separate config events when updating both DTMF and speech settings. Speech provider selection is limited to the Delphi-configured fallback chain.",
                  "value": {
                    "conversation": {
                      "identifier": {
                        "id": "tobi-conversation-123",
                        "name": "Customer conversation"
                      }
                    },
                    "messages": {
                      "timestamp": "2026-05-06T10:19:05.000Z",
                      "message": [
                        {
                          "messageId": 5,
                          "sequence": 5,
                          "content": {
                            "event": {
                              "custom": {
                                "name": "config",
                                "value": {
                                  "sessionParams": {
                                    "sendDTMF": true,
                                    "bargeInOnDTMF": true,
                                    "bargeIn": true,
                                    "bargeInMinWordCount": 1,
                                    "dtmfCollect": true,
                                    "dtmfDigits": "0123456789#*",
                                    "dtmfCollectInterDigitTimeoutMS": 3000,
                                    "dtmfCollectMaxDigits": 15,
                                    "dtmfCollectSubmitDigit": "#"
                                  }
                                }
                              }
                            }
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "description": "TOBi callback payload sent by Vodafone (SendMessageResponse). TelAPI requires conversation.identifier.id so the callback can be correlated to the TOBi channel."
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "query",
            "name": "token",
            "required": false,
            "description": "Optional legacy auth token echo from Vodafone callback URLs. Prefer the token header."
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "channelId",
            "required": true,
            "description": "The channel ID this callback belongs to (TOBi conversation ID)"
          },
          {
            "schema": {
              "type": "string"
            },
            "example": "<token>",
            "in": "header",
            "name": "token",
            "required": false,
            "description": "Per-conversation TOBi callback token. Vodafone submits this as the literal token header value."
          }
        ],
        "responses": {
          "200": {
            "description": "Acknowledgement that TelAPI accepted the callback and published it to the TelPhi consumer stream.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Acknowledgement that TelAPI accepted the callback and published it to the TelPhi consumer stream.",
                  "required": [
                    "received",
                    "streamId"
                  ],
                  "properties": {
                    "received": {
                      "type": "boolean",
                      "enum": [
                        true
                      ],
                      "description": "Always true when the callback payload is accepted."
                    },
                    "streamId": {
                      "type": "string",
                      "description": "Redis stream entry id for the forwarded TOBi payload. The same acknowledgement shape is returned for message, hangup, and transfer callbacks.",
                      "example": "1778022191343-0"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid TOBi payload or missing conversation identifier",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Invalid TOBi payload or missing conversation identifier",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "object",
                      "required": [
                        "code",
                        "message"
                      ],
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable machine-readable error code.",
                          "example": "VALIDATION_ERROR"
                        },
                        "message": {
                          "type": "string",
                          "description": "Human-readable error summary.",
                          "example": "Request validation failed"
                        },
                        "details": {
                          "description": "Optional structured details, usually validation issues."
                        },
                        "requestId": {
                          "type": "string",
                          "description": "Fastify request id for log correlation when available."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "error": {
                    "code": "INVALID_PAYLOAD",
                    "message": "Invalid TOBi payload or missing conversation identifier"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, expired, or invalid TOBi callback token",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Missing, expired, or invalid TOBi callback token",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "object",
                      "required": [
                        "code",
                        "message"
                      ],
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable machine-readable error code.",
                          "example": "VALIDATION_ERROR"
                        },
                        "message": {
                          "type": "string",
                          "description": "Human-readable error summary.",
                          "example": "Request validation failed"
                        },
                        "details": {
                          "description": "Optional structured details, usually validation issues."
                        },
                        "requestId": {
                          "type": "string",
                          "description": "Fastify request id for log correlation when available."
                        }
                      }
                    }
                  }
                },
                "examples": {
                  "example1": {
                    "value": {
                      "error": {
                        "code": "TOBI_CALLBACK_TOKEN_MISSING",
                        "message": "Missing TOBi callback token. Provide the token header with the callback token value."
                      }
                    }
                  },
                  "example2": {
                    "value": {
                      "error": {
                        "code": "TOBI_CALLBACK_TOKEN_NOT_REGISTERED",
                        "message": "No active TOBi callback security token found for this webhook channel. The conversation may not be initialized or the session token may have expired."
                      }
                    }
                  },
                  "example3": {
                    "value": {
                      "error": {
                        "code": "TOBI_CALLBACK_TOKEN_INVALID",
                        "message": "Invalid TOBi callback token. The provided token header value does not match the active session token."
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Unexpected server error",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "object",
                      "required": [
                        "code",
                        "message"
                      ],
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable machine-readable error code.",
                          "example": "VALIDATION_ERROR"
                        },
                        "message": {
                          "type": "string",
                          "description": "Human-readable error summary.",
                          "example": "Request validation failed"
                        },
                        "details": {
                          "description": "Optional structured details, usually validation issues."
                        },
                        "requestId": {
                          "type": "string",
                          "description": "Fastify request id for log correlation when available."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "error": {
                    "code": "INTERNAL_ERROR",
                    "message": "Unexpected server error"
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "servers": [
    {
      "url": "http://localhost:3001",
      "description": "Development server"
    }
  ],
  "tags": [
    {
      "name": "Health",
      "description": "GET /health — minimal probe (Redis); detail via OpenTelemetry"
    },
    {
      "name": "Webhooks",
      "description": "Webhook receivers"
    }
  ]
}
