openapi: 3.1.0
info:
  title: オトコル Webhook API
  version: "1.7"
  summary: 外部システムから連絡先を送信して自動発信・SMS 送信・タグ付与を実行し、その結果を取得する API
  description: |
    外部システムから連絡先を送信して自動発信・SMS 送信・タグ付与を実行し、その結果を取得するための API です。
    **連絡先の管理（登録・取得・更新・削除）**、**連絡先の送信（発火）** と **結果の取得（発信 / プレディクティブコール / SMS / AI通話）** のエンドポイントで構成されます。

    発火 API (`POST /webhooks/{webhook_key}/fires`) に連絡先を送信すると、オトコルが電話帳に取り込み、Webhook ごとに設定された動作（自動発信・SMS 送信・タグ付与・プレディクティブコールへの追加）を実行します。
    1 回のリクエストで送信した連絡先はひとつの通話グループ / SMS 送信グループにまとまり、レスポンスでその ID が返ります。
    その ID を使って結果を取得します。

    [API連携・データ管理ガイド](/api-docs/integration-guide.html) もご覧ください。

    ## 権限とデータ管理

    CSVを介さず、必要な項目をHTTPS/JSONで直接連携できます。電話帳の管理APIは発信・SMS・タグ動作を実行しません。
    APIキーは用途ごとに発行し、次の権限を必要な分だけ選択します。

    | 権限 | 許可する操作 |
    |---|---|
    | `fires:write` | 連絡先の取り込みと設定済み動作の実行 |
    | `results:read` | 発信・SMS・AI通話・プレディクティブコールの結果取得 |
    | `contacts:read` | 電話帳の一覧・詳細取得 |
    | `contacts:write` | 電話帳の登録・更新（発信なし） |
    | `contacts:delete` | 電話帳の個人情報削除 |

    - 既存キーは従来の発火・結果取得権限を維持します。連絡先管理の権限は自動追加されません。
    - キーの発行画面では権限と有効期限（30日・90日・365日・期限なし）を選択できます。既定は90日です。
    - 期限切れ・失効済みは401、必要な権限がない場合は403です。交代用キーを発行して連携先を切り替えてから旧キーを失効できます。
    - APIキーはサーバー側で管理し、URLやブラウザのコードに埋め込まないでください。レスポンスには `Cache-Control: no-store` を設定します。
    - 発信APIの編集画面で「受信本文・氏名・電話番号を保存する」をOFFにすると、以後の発火ログから受信本文・氏名・電話番号・タイトル・エラー詳細を除外します。日時・結果・件数・対象IDは残ります。発火通知も同じ範囲になります。
    - 新しく画面から作る発信APIはこの保存設定がOFFです。既存設定は変更されません。過去のログの消去、電話帳・通話・SMS記録の非保存を意味しません。
    - 電話帳の削除は氏名・電話番号・メモ1〜3・任意項目を消去し、タグ紐付けを外します。通話・SMSの履歴、録音、文字起こし、AI抽出結果、過去の発火ログ、外部同期の対応表、除外リスト、外部通知先・バックアップのデータは対象外です。全保存先からの完全消去APIではありません。
    - 削除は発信キャンセルや今後の再取り込み禁止ではありません。送信元の同期・予約・実行中処理を止めてから削除してください。再送すると新たな連絡先が作られます。
    - キーID・操作・結果をアクセス監査に記録します。監査にはリクエスト本文やキーの値を含めません。

    ## 利用開始までの流れ

    オトコルの管理画面で「設定 > 発信API」を開き、新規作成した Webhook に対して以下を設定します。

    1. **名前をつける** — 「Stripe 支払い失敗 → 督促架電」のように送信元と用途がわかる名前にします
    2. **受信内容の項目マッピング（任意）** — 送信元の項目名を連絡先の項目に対応づけます。未設定なら `name` / `phone_number` / `memo` をそのまま使用します
    3. **API キーを発行する** — 発行した直後にのみ全体が表示されます。控えを取ってから画面を閉じてください
    4. **Webhook URL を控える** — 「外部システムに登録する送信先」に表示される URL を送信元に登録します
    5. **受信時の動作を設定する** — 自動発信・SMS 送信・タグ付与・プレディクティブコールへの追加を設定します。**発信を使用する場合は発信元の電話番号を必ず選択してください**（未選択の場合、発信は実行されません）

    ## プレディクティブコール（オペレーター接続）

    受信時の動作で **「プレディクティブコールに追加する」** をオンにすると、送信した連絡先がプレディクティブコール
    （相手が出た通話だけをオペレーターにつなぐ発信）の対象になります。発信元の電話番号・オペレーター・架電倍率・呼び出し時間・待ち時間は Webhook に設定します。

    - 連絡先は、その Webhook の **受付中（まだ完了していない）のプレディクティブコール 1 本** に追加されます。無ければ「開始前」の状態で新しく作られます。
      そのため、続けて送信するとレスポンスの `predictive_call_groups[].id` は同じ値になります。
      そのプレディクティブコールが完了したあとに送信すると、新しいプレディクティブコールが作られ、別の ID が返ります
    - ID を控え損ねた場合や、まとめて確認したい場合は「プレディクティブコールの一覧を取得する」で引き直せます
    - **発信は、オペレーターが画面の「プレディクティブコール」で開始したときに始まります**（API からは開始しません。オペレーターが待機していないと、相手が出てもつなげないためです）。
      すでに進行中のプレディクティブコールに追加した連絡先は、そのまま続きの発信対象になります
    - オペレーターは、ブラウザ（オトコルの画面）で通話するか、オペレーターに登録した電話番号への転送で通話します
    - 相手が出たときに空いているオペレーターがいなければ、案内を流して待機音で待っていただき、空いたオペレーターにつなぎます。待ち時間（「待たない」または 20〜120 秒）を過ぎたら案内を流して終話します
    - `scheduled_at` はプレディクティブコールには効きません（受信時にすぐ追加します）
    - 同じプレディクティブコールですでに発信した連絡先は、もう一度送信しても再度の発信対象にはなりません（完了後に作られる次のプレディクティブコールでは対象になります）
    - プレディクティブコールのご契約（Business Outbound、またはオプション）が必要です。利用できないとき・対応できるオペレーターがいないときは、
      連絡先の取り込みだけを行い、レスポンスの `predictive_call_skipped_reason` で理由を返します
    - 結果は「プレディクティブコールの結果を取得する」で取得します。通話ごとに、対応したオペレーターと、連絡先の `memo` が返ります（同じ電話番号を再度送信した場合は更新後の内容になります）

    ## 折り返しの着信

    発信した相手から折り返しの電話があると、発信元の番号に設定した着信の動作（音声案内・転送など）で応答します。
    結果取得の `calls[].called_back_at` には、**その発信のあと、同じ相手（電話番号）からお客様の電話番号へ最初に着信した日時** が入ります
    （着信した時点から反映されます）。着信の記録から求めた値で、次の点にご注意ください。

    - 折り返しかどうかの判定は行いません。発信後に同じ番号から着信があれば、用件にかかわらず入ります（期限もありません）
    - お客様のアカウントのどの電話番号への着信でも対象です（発信に使った番号に限りません）
    - 同じ相手に複数回発信していると、着信より前の発信すべてに同じ日時が入ります。**いちばん新しい発信の値**を使ってください
    - 通話の `status`（`called_back` = 折り返しあり）とは連動しません（「通話結果を取得する」を参照）
    着信と同時に受け取りたい場合は、「設定 > 通知」で通知タイミング **「着信時すべて」** を購読してください（全プランで利用できます）。

    ## 共通仕様

    | 項目 | 内容 |
    |---|---|
    | 認証 | `Authorization: Bearer ack_xxxx`（API キー。アカウント単位で発行） |
    | Content-Type | `application/json`（POST 時は必須） |
    | 文字コード | UTF-8 |
    | 日時形式 | ISO 8601（例 `2026-09-01T10:00:00+09:00`）。レスポンスは JST で返します |
    | 電話番号 | 送信時はハイフン有無どちらも可。発信結果は E.164（`+819012345678`）、SMS 結果は国内表記（`090-1234-5678`）で返します |
    | 対象プラン | Business 以上（無料トライアル中も利用できます） |

    ## エラー形式

    エラー時は HTTP ステータスコードと `{ "error": "エラー内容の説明" }` を返します。

    | コード | 意味 | 主な原因 |
    |---|---|---|
    | 200 | 正常 | 取得・更新・発火（重複判定も200） |
    | 201 | 作成 | 連絡先を登録 |
    | 204 | 削除完了 | 電話帳の個人情報を削除（本文なし） |
    | 409 | 重複 | 同じ電話番号の連絡先が登録済み |
    | 401 | 認証エラー | API キーが未指定・誤り・失効済み・期限切れ |
    | 403 | 利用不可 | 必要なキー権限がない、Business 未満のプラン、または解約済み |
    | 404 | 対象なし | webhook_key の誤り、Webhook のアーカイブ、対象外のグループ |
    | 422 | 内容エラー | 必須項目の不足、件数超過、予約日時の指定誤り、発信除外リストの番号 |
    | 429 | 流量制限 | 1 分あたりの送信回数の上限超過 |

    ## 発火結果の通知

    オトコルの画面「設定 > 通知」で通知先（Slack / Chatwork / カスタム Webhook）を登録し、通知タイミングに
    **「発信APIの発火」** / **「発信APIの失敗」** を選ぶと、発火の結果が通知されます（Business 以上）。

    | タイミング | 対象 | 通知の内容 |
    |---|---|---|
    | 発信APIの発火 | 受け付けて実行 / 予約を作成した発火（1 リクエスト = 1 通） | Webhook 名、取り込んだ連絡先（代表 1 件と件数）、作成した発信グループ、SMS の件数、実行（即時 / 予約時刻）、受信時刻、発火ログへのリンク |
    | 発信APIの失敗 | 流量制限（429）/ プラン外（403）/ 無効な内容（422）/ 取り込み失敗（422） | Webhook 名、結果、理由（`error` と同じ文言）、連絡先、受信時刻、発火ログへのリンク |

    - 重複（`status: "duplicate"`）は正常な動作なので、どちらにも通知されません
    - 失敗の通知は **同じ Webhook × 同じ失敗について 10 分に 1 回** に抑えます（発火ログには全件残ります）
    - 通知は非同期に送られます。API のレスポンスは通知の成否に影響されません
    - 自社システムで受け取るときは通知先に **カスタム（Webhook）** を選びます。届く JSON は下の Webhooks「通知（カスタム Webhook）」にあります

    ## 制限一覧

    | 項目 | 上限 |
    |---|---|
    | 1 リクエストの送信件数 | 最大 200 件 |
    | 送信のレート制限 | 60 リクエスト / 分（Webhook ごと） |
    | 予約発信の指定可能期間 | 受信から 30 日以内 |
    | 連絡先一覧の 1 ページ | 既定100件・最大200件 |
    | 結果取得の 1 ページ | 既定 100 件・最大 500 件 |
    | 発火ログの保存期間 | 90 日 |
    | 発信可能件数 | ご契約の残数を超える分は発信されません |

    お問い合わせ: support_ac@graham.jp（平日 9:00 - 18:00）
  contact:
    name: オトコル サポート
    email: support_ac@graham.jp
    url: https://autocall.app

servers:
  - url: https://backend.autocall.app
    description: 本番

security:
  - bearerAuth: []

tags:
  - name: 連絡先の管理
    description: 発信を伴わず電話帳を登録・取得・更新・削除します。
  - name: 連絡先の送信
    description: 連絡先を取り込み、Webhook に設定された動作（自動発信・SMS 送信・タグ付与）を実行します。
  - name: 発信結果の取得
    description: 発火で作られた通話グループの集計と通話明細を取得します。
  - name: プレディクティブコールの結果
    description: 連絡先を追加したプレディクティブコール（オペレーター接続）の集計と通話明細を取得します。
  - name: SMS 結果の取得
    description: 発火で作られた SMS 送信グループの集計と送信明細を取得します。
  - name: AI通話の結果
    description: |
      AI通話（AI スクリプトで発信）1 件の結果を取得します。発火のレスポンス `ai_calls[].id` を使います。
      取りに来る代わりに、通話が終わった時点で受け取ることもできます。Slack / Chatwork / カスタム Webhook で受けるなら、画面の「設定 > 通知」で通知タイミングに「AI架電の結果」「AI受電の結果」を選びます（相手と話した通話だけ。AI架電の結果は Business 以上）。

  - name: 通知（カスタム Webhook）
    description: |
      画面の「設定 > 通知」で通知先に **カスタム（Webhook）** を選ぶと、選んだ通知タイミングごとに、
      指定した URL へ JSON を POST します。Slack / Chatwork と同じ通知タイミング・電話番号の絞り込み・プランの条件
      （発信の結果は Business 以上、着信の結果は全プラン）で届きます。

      ## 本文

      すべての通知で同じ封筒に入ります。`data` の中身は通知タイミング（`event`）ごとに下の各 Webhook を見てください。

      ```json
      { "id": "6f1c…", "event": "call_script_action_pushed", "sent_at": "2026-09-03T10:00:15+09:00", "data": { … } }
      ```

      - 日時は ISO 8601（JST）、電話番号は E.164（`+819012345678`）です
      - 連絡先（`contact`）は電話帳に無い相手だと `null` です
      - **キーは今後増えることがあります**（既存のキーの名前・型は変えません）。知らないキーは無視してください

      ## 受け口の要件

      - **https** の公開 URL（社内ネットワークなどのプライベートアドレスには送れません）
      - 8 秒以内に **2xx** を返してください。転送（3xx）は追いません
      - 2xx 以外・接続失敗は**再送しません**。結果は画面の通知先一覧に「失敗」として残ります
      - まれに同じ通知が二度届くことがあります。`id`（= `X-Otocol-Delivery`）で重複を除いてください

      ## 署名の検証

      **何と何を照合するか**: ヘッダの `X-Otocol-Signature` と、受け口が自分で計算した署名を比べます。
      計算方法（HMAC-SHA256）は公開の標準で、秘密なのは**署名シークレット**（鍵）だけです。鍵を知るのはオトコルとお客様だけなので、
      署名が一致すれば「オトコルが送り、途中で書き換えられていない」と確かめられます。鍵を知らない第三者は、本文を変えると正しい署名を作れません。

      署名シークレット（`whsec_…`）は、画面の「設定 > 通知」でカスタムの通知先を**追加した直後**と、**編集**を開いたときに表示されます
      （URL を変えても変わりません）。

      | ヘッダ | 値 |
      |---|---|
      | `X-Otocol-Event` | 通知タイミング（本文の `event` と同じ） |
      | `X-Otocol-Delivery` | 配信 ID（本文の `id` と同じ） |
      | `X-Otocol-Signature` | `sha256=` + HMAC-SHA256(署名シークレット, 本文の生バイト列) の 16 進表記 |

      受け口では、受け取った本文の**生バイト列**（JSON をパースし直さない）で同じ HMAC を計算し、
      `X-Otocol-Signature` と定数時間比較してください。

      ```js
      // Node.js（Express は express.raw({ type: 'application/json' }) で生の本文を受ける）
      const crypto = require('crypto')
      const expected = Buffer.from('sha256=' + crypto.createHmac('sha256', SECRET).update(rawBody).digest('hex'))
      const given = Buffer.from(req.get('X-Otocol-Signature') || '')
      // timingSafeEqual は長さが違うと例外になるので、先に長さを比べる
      const ok = given.length === expected.length && crypto.timingSafeEqual(given, expected)
      ```

      ```ruby
      # Ruby (Rails)
      expected = "sha256=#{OpenSSL::HMAC.hexdigest('SHA256', SECRET, request.raw_post)}"
      ok = ActiveSupport::SecurityUtils.secure_compare(expected, request.headers['X-Otocol-Signature'].to_s)
      ```

      ```python
      # Python (Flask)
      import hashlib, hmac
      raw_body = request.get_data()
      expected = 'sha256=' + hmac.new(SECRET.encode(), raw_body, hashlib.sha256).hexdigest()
      ok = hmac.compare_digest(expected, request.headers.get('X-Otocol-Signature', ''))
      ```

      ```php
      // PHP
      $raw = file_get_contents('php://input');
      $expected = 'sha256=' . hash_hmac('sha256', $raw, $secret);
      $ok = hash_equals($expected, $_SERVER['HTTP_X_OTOCOL_SIGNATURE'] ?? '');
      ```

      ### 再送（リプレイ）を防ぐ

      署名は本文が変わっていないことを示しますが、**本物の通知をそのまま送り直す**ことは防げません。署名を確かめたうえで、次の 2 つも確かめてください。

      - `sent_at` が今から **5 分**より前なら捨てる（`sent_at` も署名の対象なので書き換えられません。サーバーの時計は NTP で合わせてください）
      - 同じ `id` を一度受け取ったら、2 回目からは捨てる（少なくとも 5 分は `id` を覚えておく）

      オトコルが送り直すとき（まれな再試行）は、`id` は同じで `sent_at` は送り直した時刻になります。

      ### 署名が合わないとき

      | 原因 | 直し方 |
      |---|---|
      | 署名シークレットの貼り間違い | `whsec_` から末尾まで全体が鍵です。前後の空白・改行も入れないでください |
      | 本文をパースし直してから計算している | JSON を読み込んで文字列に戻すと、キーの順番・空白・エスケープが変わります。受け取った生バイト列で計算してください |
      | 文字コード | 本文は UTF-8 です。文字列に変換せずバイト列のまま計算してください |
      | 鍵を再発行した | 再発行すると古い鍵での検証は通りません。受け口の鍵も差し替えてください |

      **テスト送信**を押すと、送った本文と署名がそのまま画面に出ます。その本文で受け口と同じ計算をして、同じ署名になるかを見比べてください。

      ### 署名シークレットが漏れたら

      編集画面の「再発行」で作り直してください。**その時点から古い鍵での検証は通りません**（受け口の鍵も差し替えるまで、届いた通知は検証に落ちます）。

      ## テスト送信と見本

      通知先一覧の「テスト送信」で、購読中の通知タイミングごとに見本の値で 1 通ずつ、本番と同じ形・同じヘッダ・同じ署名で送ります
      （`id` は `test-` で始まります。購読が無いときは `event` が `test` の疎通確認を 1 通）。
      送ったあと、それぞれの本文・署名・受け口の応答コードが画面に出ます（署名の検証の確認に使えます）。
      各通知タイミングの「通知の例」でも、届く JSON をそのまま確認できます。

paths:
  /webhooks/contacts:
    get:
      tags: [連絡先の管理]
      operationId: listContacts
      summary: 連絡先を取得する
      description: |
        `contacts:read` が必要です。自アカウントの公開・除外状態の連絡先を作成日時・ID順に返します。削除済みは返しません。
        `updated_since` は指定時刻以降（境界を含む）の更新を絞り込みます。削除通知の取得には使えません。
        ページング中の更新に対するスナップショットではありません。定期同期は時間に余裕を持って再取得し、IDとupdated_atで重複を除いてください。
      parameters:
        - $ref: "#/components/parameters/Page"
        - name: per_page
          in: query
          schema: { type: integer, minimum: 1, maximum: 200, default: 100 }
        - name: phone_number
          in: query
          description: 電話番号の完全一致。国内表記・E.164・ハイフン有無を正規化します。
          schema: { type: string }
        - name: updated_since
          in: query
          description: タイムゾーン付きISO 8601。例 2026-10-23T00:00:00Z
          schema: { type: string, format: date-time }
      responses:
        "200":
          description: 連絡先一覧
          content:
            application/json:
              schema:
                type: object
                required: [contacts, pagination]
                properties:
                  contacts:
                    type: array
                    items: { $ref: "#/components/schemas/ManagedContact" }
                  pagination:
                    type: object
                    properties:
                      page: { type: integer }
                      per_page: { type: integer }
                      total_pages: { type: integer }
                      total_count: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "422": { description: updated_sinceの形式が不正 }
    post:
      tags: [連絡先の管理]
      operationId: createContact
      summary: 発信せずに連絡先を登録する
      description: |
        `contacts:write` が必要です。発信・SMS・タグ動作は実行しません。項目マッピングは使いません。
        同じ電話番号が存在すると409です。通信断の再送で409になった場合は、電話番号で一覧を取得してIDを照合してください。
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/ManagedContactInput"
                - required: [name, phone_number]
      responses:
        "201": { $ref: "#/components/responses/ManagedContactResponse" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "409": { description: 同じ電話番号が登録済み（除外状態の連絡先を含む） }
        "422": { description: 名前・電話番号等の入力が不正 }
  /webhooks/contacts/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: string, format: uuid }
    get:
      tags: [連絡先の管理]
      operationId: getContact
      summary: 連絡先1件を取得する
      description: '`contacts:read` が必要です。自アカウントの削除済みではない連絡先を取得します。'
      responses:
        "200": { $ref: "#/components/responses/ManagedContactResponse" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { description: 存在しない・削除済み・他アカウントのID }
    patch:
      tags: [連絡先の管理]
      operationId: updateContact
      summary: 連絡先を更新する
      description: |
        `contacts:write` が必要です。指定した項目だけ更新します。発信・SMSは行いません。削除済みの連絡先は復元できません。
        メモはnullで消去できます。custom_fieldsは指定したオブジェクト全体で置換され、空オブジェクトで全任意項目を消去できます。
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ManagedContactInput" }
      responses:
        "200": { $ref: "#/components/responses/ManagedContactResponse" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { description: 存在しない・削除済み・他アカウントのID }
        "409": { description: 同じ電話番号への並行更新が競合 }
        "422": { description: 不正な入力・登録済み電話番号への変更 }
    delete:
      tags: [連絡先の管理]
      operationId: deleteContact
      summary: 電話帳の個人情報を削除する
      description: |
        `contacts:delete` が必要です。氏名・電話番号・メモ1〜3・任意項目を消去し、タグ紐付けを削除します。元の値への復元はできません。
        請求・通話参照を保つため、元のIDと削除状態の行は残します。同じIDの再削除も204です。他アカウントのIDは404です。
        **通話履歴・録音・文字起こし・AI抽出結果・SMS本文/宛先・過去の発火ログ・外部同期対応表・除外リスト・外部通知先・バックアップは消えません。**
        発信キャンセルや再取り込み禁止はしません。同期・予約・実行中処理を止めてから呼び出してください。一括・条件指定の全件削除は提供しません。
      responses:
        "204": { description: 電話帳の個人情報を削除済み（本文なし） }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { description: 存在しない・他アカウントのID }
  /webhooks/{webhook_key}/fires:
    post:
      tags: [連絡先の送信]
      operationId: createFire
      summary: 連絡先を送信する
      description: |
        **AI スクリプトで発信する Webhook では、スクリプトの台詞に差し込まれていて逃げ道の無い項目（管理画面の項目マッピングで「必須」と出る項目）が payload に無いと、取り込まずに 422 を返します。**

        連絡先を取り込み、その Webhook に設定された動作（自動発信・SMS 送信・タグ付与・プレディクティブコールへの追加）を実行します。
        1 回のリクエストで送信した連絡先は同一の通話グループ / SMS 送信グループにまとまります。
        プレディクティブコールは、Webhook ごとに受付中の 1 本へ追加されます（概要の「プレディクティブコール（オペレーター接続）」を参照）。

        **100 件を送信する場合は、必ず `contacts` でまとめてください。**
        1 件ずつ 100 回に分けて送信すると通話グループが 100 個に分かれ、結果の取得と集計が煩雑になります。
        また 1 分あたりの送信回数の上限（60 回）にも達します。

        ### 項目マッピング
        Webhook に項目マッピングを設定している場合は、`name` / `phone_number` ではなく **設定した送信元の項目名** で送信してください
        （`user.name` のようなドット区切りの階層指定にも対応します）。

        ### 冪等性
        `idempotency_key` を指定すると、同じキーのリクエストを再送しても 2 回目以降は取り込み・発信を行わず `status: "duplicate"` を返します。
        **このキーに期限はありません**（何日後に同じキーで送っても `duplicate` になります）。

        省略した場合は **リクエスト本文のハッシュ値** をキーとして扱い、**10 分間**だけ重複と判定します。
        通信エラー時の自動リトライを二重送信にしないための仕組みなので、10 分を過ぎた同一内容のリクエストは新しい送信として実行されます
        （同じ相手に同じ案内をあとでもう一度送る、設定を直してテストし直す、といった操作はそのまま行えます）。

        短い間隔で同一内容を続けて送る必要がある場合は、`idempotency_key` に毎回異なる値を指定してください。

        ### レート制限
        Webhook ごとに 1 分あたり 60 リクエストです。件数ではなくリクエスト回数で数えるため、複数件をまとめて送る方が制限に達しにくくなります。

        ### `skipped` に数えられるもの
        - 電話番号が正しくない行、名前が空の行
        - 同一の電話番号が複数含まれていた場合の重複分（1 件にまとめられ、後の行の内容が優先されます）
        - 発信対象から除外している連絡先

        一部の行が無効でも、有効な行は取り込まれて実行されます。すべての行が無効（または発信除外リストに載っている番号）の場合のみ 422 を返します。
        すでに電話帳に存在する電話番号は新規登録ではなく既存の連絡先の更新となり、`imported` に数えられます。

        ### 発火結果の通知
        「設定 > 通知」で「発信APIの発火」「発信APIの失敗」を購読すると、受付（200）と失敗（403 / 422 / 429）が Slack / Chatwork に届きます。
        失敗は同じ Webhook × 同じ失敗について 10 分に 1 回に抑えられます。重複（duplicate）は通知されません。
      parameters:
        - $ref: "#/components/parameters/WebhookKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: "#/components/schemas/BulkFireRequest"
                - $ref: "#/components/schemas/SingleFireRequest"
            examples:
              bulk:
                summary: 複数件をまとめて送信（推奨）
                value:
                  contacts:
                    - name: 山田太郎
                      phone_number: "09012345678"
                      memo: 8月申込
                    - name: 鈴木花子
                      phone_number: "080-9999-8888"
                  idempotency_key: batch-2026-09-01-001
              scheduled:
                summary: 予約発信
                value:
                  contacts:
                    - name: 山田太郎
                      phone_number: "09012345678"
                  scheduled_at: "2026-09-01T10:00:00+09:00"
                  idempotency_key: batch-2026-09-01-001
              single:
                summary: 1 件のみ送信
                value:
                  name: 山田太郎
                  phone_number: "09012345678"
                  memo: 8月申込
      responses:
        "200":
          description: 受け付けました（重複判定も 200）
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/FireAccepted"
                  - $ref: "#/components/schemas/FireDuplicate"
              examples:
                accepted:
                  summary: 受け付けて実行
                  value:
                    status: accepted
                    imported: 98
                    skipped: 2
                    fired: 2
                    call_groups:
                      - id: e54a0c02-3f1d-4705-85bc-f81c7a340fb9
                        title: キャンペーン案内_20260901_1000
                    short_message_groups:
                      - id: 1b2f7c40-0d6e-4c8a-9a7e-3f2a1c5d8e90
                        title: キャンペーン案内SMS_20260901_1000
                scheduled:
                  summary: 予約を作成
                  value:
                    status: scheduled
                    imported: 1
                    skipped: 0
                    fired: 1
                    call_groups:
                      - id: e54a0c02-3f1d-4705-85bc-f81c7a340fb9
                        title: キャンペーン案内_20260901_1000
                    short_message_groups: []
                    scheduled_at: "2026-09-01T10:00:00+09:00"
                predictive:
                  summary: プレディクティブコールに追加
                  value:
                    status: accepted
                    imported: 50
                    skipped: 0
                    fired: 0
                    call_groups: []
                    short_message_groups: []
                    predictive_call_groups:
                      - id: 3c0f6f0e-7a55-4f0b-9c58-2b1c7f5d9a11
                        title: 面談予約フォロー_20260901_1000
                predictive_skipped:
                  summary: プレディクティブコールに追加できなかった（取り込みは成立）
                  value:
                    status: accepted
                    imported: 50
                    skipped: 0
                    fired: 0
                    call_groups: []
                    short_message_groups: []
                    predictive_call_groups: []
                    predictive_call_skipped_reason: 対応できるオペレーターがいません（未登録、または全員が他のプレディクティブコールを対応中です）
                duplicate:
                  summary: 重複のため何もしていない
                  value:
                    status: duplicate
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: Webhook が見つかりません（URL と、Webhook がアーカイブされていないかを確認してください）
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: Webhook が見つかりません
        "422":
          description: |
            内容エラー。`error` の内容と対処:

            | error | 対処 |
            |---|---|
            | name と phone_number(有効な電話番号)は必須です | 必須項目と電話番号の形式を確認してください |
            | contacts は1リクエストあたり 200 件までです(N 件) | 200 件以内に分割して送信してください |
            | contacts が空です / contacts は連絡先の配列で指定してください | 1 件以上の連絡先を配列で指定してください |
            | scheduled_at の形式が不正です(UNIX時刻または日時文字列で指定してください) | ISO 8601 か UNIX 時刻で指定してください |
            | scheduled_at は現在時刻より後を指定してください | 未来の日時を指定してください |
            | scheduled_at は30日以内を指定してください | 30 日以内の日時を指定してください |
            | コンタクトの取り込みに失敗しました | 連絡先を 1 件も登録できませんでした。内容を確認してください |
            | 発信除外リストに載っている電話番号のため、発信・SMS を行いません | 送った番号がすべて発信除外リストに載っています（AI 通話で「今後は連絡しないで」と言われた相手は自動で載ります）。解除は管理画面の電話帳から行えます |
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: name と phone_number(有効な電話番号)は必須です
        "429":
          description: リクエストが多すぎます（まとめて送信するか、間隔をあけて再送してください）
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: リクエストが多すぎます

  /webhooks/call_groups/{id}:
    get:
      tags: [発信結果の取得]
      operationId: getCallGroup
      summary: 通話結果を取得する
      description: |
        通話グループの集計と、通話 1 件ごとの結果を取得します。
        発信が完了するまで繰り返し呼び出し、`call_group.status` が `completed` になった時点で完了と判断してください。

        **取得できるのは、ご自身のアカウントの通話グループのみです。**
        本 API で送信した分に限らず、画面から作成した発信や他の連携（スプレッドシート連携など）で作成された通話グループも、
        同じアカウントのものであれば取得できます。他のアカウントの通話グループは、ID を指定しても 404 を返します。

        集計値は画面の表示と同じ定義です。`connected`（つながった件数）に数えられるのは実際に通話が成立したもので、
        `no_answer` / `busy` / `failed` / `canceled` / `machine_answer` / `risk_rejected` は `not_connected` に集計されます。

        ### 折り返し
        折り返しは `status` と `called_back_at` の 2 つで表されます。**この 2 つは別々に決まり、連動しません。**

        | 項目 | 内容 |
        |---|---|
        | `status` = `called_back` | 相手から着信があった時点で、その相手への**いちばん新しい発信**が `completed`（全て回答）以外なら `called_back` に変わります。画面の操作で「折り返しあり」に変更した場合も同じ値になります |
        | `called_back_at` | その発信のあと、同じ相手から最初に着信した日時。着信の記録から求めます |

        そのため、次の組み合わせがあります。

        - `status` が `completed` のまま `called_back_at` に日時が入る（全て回答した相手が、あとで電話をかけてきた）
        - `status` が `called_back` で `called_back_at` が `null`（画面の操作で「折り返しあり」に変更した）
        - 同じ相手に複数回発信していると、着信より前の発信すべての `called_back_at` に同じ日時が入る（`status` が変わるのはいちばん新しい発信だけ）

        「折り返しがあったか・いつか」は `called_back_at` で判断してください。

        ### memo
        `calls[].contact` には、連絡先の `memo` / `memo2` / `memo3` が入ります。送信元で付けた識別子を `memo` に入れておくと、結果と突き合わせられます。
        同じ電話番号の連絡先を再度送信すると `memo` は新しい内容に更新され、それより前の通話の結果にも更新後の内容が入ります。
      parameters:
        - name: id
          in: path
          required: true
          description: 通話グループの ID。送信時のレスポンス `call_groups[].id` の値
          schema:
            type: string
            format: uuid
          example: e54a0c02-3f1d-4705-85bc-f81c7a340fb9
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
      responses:
        "200":
          description: 通話グループの集計と通話明細
          headers:
            X-Total-Count:
              description: 通話明細の総件数
              schema:
                type: integer
              example: 100
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallGroupResult"
              example:
                call_group:
                  id: e54a0c02-3f1d-4705-85bc-f81c7a340fb9
                  title: キャンペーン案内_20260901_1000
                  status: completed
                  status_label: 完了
                  start_at: "2026-09-01T10:00:00+09:00"
                  summary:
                    total: 100
                    connected: 42
                    not_connected: 58
                    approved: 12
                    rejected: 5
                    not_connected_breakdown:
                      no_answer: 40
                      busy: 3
                      failed: 2
                      canceled: 0
                      machine_answer: 13
                      risk_rejected: 0
                calls:
                  - id: ed8a2c34-efe4-4f51-acb6-96d3322bbbec
                    contact:
                      id: 5d7b09b1-cb86-4465-b77f-058b2eb3739b
                      name: 山田太郎
                      phone_number: "+819012345678"
                      memo: 8月申込
                      memo2: null
                      memo3: null
                    status: completed
                    status_label: 全て回答
                    duration: 42
                    called_at: "2026-09-01T10:00:12+09:00"
                    called_back_at: null
                    pushed:
                      - dtmf: "1"
                        result: approved
                  - id: 0c1e9a77-5b3d-4e2f-8a91-2d4c6e8f0a13
                    contact:
                      id: 7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d
                      name: 鈴木花子
                      phone_number: "+818099998888"
                      memo: null
                      memo2: null
                      memo3: null
                    status: called_back
                    status_label: 折り返しあり
                    duration: null
                    called_at: "2026-09-01T10:00:15+09:00"
                    called_back_at: "2026-09-01T10:42:03+09:00"
                    pushed: []
                pagination:
                  page: 1
                  per_page: 100
                  total_pages: 1
                  total_count: 100
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: 発信グループが見つかりません（ID が正しいか、ご自身のアカウントの通話グループかを確認してください）
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: 発信グループが見つかりません

  /webhooks/predictive_call_groups:
    get:
      tags: [プレディクティブコールの結果]
      operationId: listPredictiveCallGroups
      summary: プレディクティブコールの一覧を取得する
      description: |
        ご自身のアカウントのプレディクティブコールを、作成日時の新しい順に取得します（画面から作成したものも含みます）。
        送信時のレスポンスの `predictive_call_groups[].id` を控え損ねても、ここから引き直せます。

        - 各プレディクティブコールの状態と集計（`summary`）は「プレディクティブコールの結果を取得する」と同じ内容です。
          通話ごとの結果（対応したオペレーター・`memo`・折り返しの日時）は、ここで得た `id` で「結果を取得する」を呼んでください
        - 定期的に取り込む場合は、`created_since` に前回取得した日時より少し前を指定すると、取りこぼしなく確認できます
        - 1 ページの件数は最大 100 件です
      parameters:
        - name: status
          in: query
          required: false
          description: 状態で絞り込みます（`not_started` / `in_progress` / `pause` / `completed`）
          schema:
            $ref: "#/components/schemas/PredictiveCallGroupStatus"
          example: completed
        - name: created_since
          in: query
          required: false
          description: この日時以降に作成されたものに絞り込みます（ISO 8601）
          schema:
            type: string
            format: date-time
          example: "2026-09-01T00:00:00+09:00"
        - $ref: "#/components/parameters/Page"
        - name: per_page
          in: query
          required: false
          description: 1 ページの件数（既定 100、最大 100）
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 100
      responses:
        "200":
          description: プレディクティブコールの一覧
          headers:
            X-Total-Count:
              description: 条件に合うプレディクティブコールの総件数
              schema:
                type: integer
              example: 1
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PredictiveCallGroupList"
              example:
                predictive_call_groups:
                  - id: 3c0f6f0e-7a55-4f0b-9c58-2b1c7f5d9a11
                    title: 面談予約フォロー_20260901_1000
                    status: completed
                    status_label: 完了
                    created_at: "2026-09-01T10:00:00+09:00"
                    summary:
                      contacts: 50
                      not_called_contacts: 0
                      calls: 62
                      connected: 31
                      not_connected: 31
                      not_connected_breakdown:
                        no_answer: 27
                        busy: 4
                pagination:
                  page: 1
                  per_page: 100
                  total_pages: 1
                  total_count: 1
        "400":
          description: 絞り込みの指定が正しくありません
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: created_since は ISO 8601 形式の日時で指定してください
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  /webhooks/predictive_call_groups/{id}:
    get:
      tags: [プレディクティブコールの結果]
      operationId: getPredictiveCallGroup
      summary: プレディクティブコールの結果を取得する
      description: |
        プレディクティブコール（オペレーター接続）の集計と、通話 1 件ごとの結果を取得します。
        通話ごとに、**対応したオペレーター**・連絡先の `memo`・折り返しの日時が返ります。

        発信 API からの連絡先は、Webhook ごとに受付中の 1 本へ追加されていくため、1 つのプレディクティブコールに複数回の送信分が入ります。
        ご自身の送信分は、`calls[].contact` の `id` / `phone_number` / `memo` で突き合わせてください。
        すべての発信が終わると `predictive_call_group.status` が `completed` になります。

        **取得できるのは、ご自身のアカウントのプレディクティブコールのみです**（画面から作成したものも取得できます）。

        - 同じ連絡先に複数回発信した場合（不在で再度発信など）、通話は発信ごとに 1 件ずつ返ります
        - まだ発信していない連絡先は `calls` に含まれません（件数は `summary.not_called_contacts`）
        - `connected` は相手が電話に出た通話の件数です。オペレーターにつながったかは `calls[].operator` で判断してください
          （相手が出たときに待機中のオペレーターがいなかった通話は `operator` が `null` になります）
        - プレディクティブコールの通話には「折り返しあり」のステータスはありません。折り返しは `called_back_at` で判断してください
        - オペレーターが通話後に入力する項目はありません。通話の録音 URL は返しません
      parameters:
        - name: id
          in: path
          required: true
          description: プレディクティブコールの ID。送信時のレスポンス `predictive_call_groups[].id` の値
          schema:
            type: string
            format: uuid
          example: 3c0f6f0e-7a55-4f0b-9c58-2b1c7f5d9a11
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
      responses:
        "200":
          description: プレディクティブコールの集計と通話明細
          headers:
            X-Total-Count:
              description: 通話明細の総件数
              schema:
                type: integer
              example: 2
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PredictiveCallGroupResult"
              example:
                predictive_call_group:
                  id: 3c0f6f0e-7a55-4f0b-9c58-2b1c7f5d9a11
                  title: 面談予約フォロー_20260901_1000
                  status: in_progress
                  status_label: 進行中
                  summary:
                    contacts: 50
                    not_called_contacts: 48
                    calls: 2
                    connected: 1
                    not_connected: 1
                    not_connected_breakdown:
                      no_answer: 1
                calls:
                  - id: 9b7d2f64-1c3e-4a5b-8d6f-0e1a2b3c4d5e
                    contact:
                      id: 5d7b09b1-cb86-4465-b77f-058b2eb3739b
                      name: 山田太郎
                      phone_number: "+819012345678"
                      memo: student-10234
                      memo2: null
                      memo3: null
                    operator:
                      id: 2f8e4c1a-6b7d-4e9f-a0b1-c2d3e4f5a6b7
                      name: 佐藤
                      user:
                        id: 7c6b5a49-3827-4615-9048-372615049382
                        name: 佐藤 一郎
                        email: sato@example.com
                    status: completed
                    status_label: 完了
                    duration: 185
                    called_at: "2026-09-01T10:00:12+09:00"
                    called_back_at: null
                  - id: 4e3d2c1b-0a9f-4b8e-9d7c-6b5a49382716
                    contact:
                      id: 7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d
                      name: 鈴木花子
                      phone_number: "+818099998888"
                      memo: student-10235
                      memo2: null
                      memo3: null
                    operator: null
                    status: no_answer
                    status_label: 電話に出ず
                    duration: 0
                    called_at: "2026-09-01T10:00:15+09:00"
                    called_back_at: "2026-09-01T10:42:03+09:00"
                pagination:
                  page: 1
                  per_page: 100
                  total_pages: 1
                  total_count: 2
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: プレディクティブコールが見つかりません（ID が正しいか、ご自身のアカウントのものかを確認してください）
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: プレディクティブコールが見つかりません

  /webhooks/short_message_groups/{id}:
    get:
      tags: [SMS 結果の取得]
      operationId: getShortMessageGroup
      summary: SMS 送信結果を取得する
      description: |
        SMS 送信グループの集計と、送信 1 件ごとの結果を取得します。

        **到達結果は後追いで確定します。**
        送信直後は `sent` や `queued` のまま返り、到達（`delivered`）/ 未到達（`undelivered`）は通信事業者からの非同期通知で確定します。
        `short_message_group.status` が `completed` になったあとも、到達結果を反映するには **数分〜30 分ほど空けてもう一度取得** してください。

        SMS を受けられない番号（固定電話など）は送信自体を行わないため、`summary.total` は取り込んだ連絡先の件数（`imported`）と一致しないことがあります。

        `short_messages[].contact` には Webhook 受信時に取り込んだ `memo` / `memo2` / `memo3` がそのまま入ります。
        送信元で付けた識別子を `memo` に入れておくと、結果と突き合わせられます。

        **取得できるのは、ご自身のアカウントの SMS 送信グループのみです。** 画面から作成した一斉送信も、同じアカウントのものであれば取得できます。
      parameters:
        - name: id
          in: path
          required: true
          description: SMS 送信グループの ID。送信時のレスポンス `short_message_groups[].id` の値
          schema:
            type: string
            format: uuid
          example: 1b2f7c40-0d6e-4c8a-9a7e-3f2a1c5d8e90
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
      responses:
        "200":
          description: SMS 送信グループの集計と送信明細
          headers:
            X-Total-Count:
              description: 送信明細の総件数
              schema:
                type: integer
              example: 2
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ShortMessageGroupResult"
              example:
                short_message_group:
                  id: 1b2f7c40-0d6e-4c8a-9a7e-3f2a1c5d8e90
                  title: キャンペーン案内SMS_20260901_1000
                  status: completed
                  status_label: 完了
                  start_at: "2026-09-01T10:00:00+09:00"
                  send_from: 050-1234-5678
                  summary:
                    total: 2
                    success: 1
                    failed: 1
                    total_price: 20.0
                short_messages:
                  - id: 3f6d2a91-8c4b-4e0f-9a2d-7b1c5e8f0a34
                    send_to: 090-1234-5678
                    contact:
                      id: 5d7b09b1-cb86-4465-b77f-058b2eb3739b
                      name: 山田太郎
                      phone_number: "+819012345678"
                      memo: 8月申込
                      memo2: null
                      memo3: null
                    status: delivered
                    status_label: 送信済み
                    content: 山田太郎様 8月のお申込ありがとうございます。詳細はこちら https://example.com/abc
                    price: 10.0
                    sent_at: "2026-09-01T10:00:03+09:00"
                  - id: 9e0f1a2b-3c4d-4e5f-8a6b-7c8d9e0f1a2b
                    send_to: 080-9999-8888
                    contact:
                      id: 7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d
                      name: 鈴木花子
                      phone_number: "+818099998888"
                      memo: null
                      memo2: null
                      memo3: null
                    status: undelivered
                    status_label: 未到達
                    content: 鈴木花子様 8月のお申込ありがとうございます。詳細はこちら https://example.com/def
                    price: 10.0
                    sent_at: "2026-09-01T10:00:03+09:00"
                pagination:
                  page: 1
                  per_page: 100
                  total_pages: 1
                  total_count: 2
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: SMS送信グループが見つかりません（ID が正しいか、ご自身のアカウントのグループかを確認してください）
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: SMS送信グループが見つかりません

  /webhooks/ai_calls/{id}:
    get:
      tags: [AI通話の結果]
      operationId: getAiCall
      summary: AI通話の結果を取得する
      description: |
        AI通話 1 件の終わり方・集めた項目・経路を取得します。発火のレスポンス `ai_calls[].id` を渡してください。
        `status` が `dialing`（発信中）のうちは `end_label` と `fields` は入りません。

        **取得できるのは、ご自身のアカウントの通話のみです。** 他のアカウントの通話は ID を指定しても 404 を返します。

        `fields` のキーはスクリプトごとにオトコルの画面で付けた物なので、見出し（`field_labels`）も一緒に返します。終わり方は見出し（`end_label`）と成果（`call_outcome`）で返します。

        メールアドレス・氏名など、復唱して確かめた項目は `fields_meta` に `verified` が付きます。`verified` が `false` の値は、復唱しても確定できなかった値（未確定）です。未確定の値も `fields` に入り、電話帳への保存を設定した項目は電話帳にも保存します。確かめ直す対象の見分けに使ってください。
      parameters:
        - name: id
          in: path
          required: true
          description: AI通話の ID。送信時のレスポンス `ai_calls[].id` の値
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: 通話の結果
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AiCallResult"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: 通話が見つからない（ID 違い・他のアカウントの通話）
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

webhooks:
  call_group_created:
    post:
      # オトコルから送る側なので、API キー(bearerAuth)は要らない(全体の security を打ち消す)
      security: []
      tags: [通知（カスタム Webhook）]
      summary: 発信グループの開始
      description: |
        発信グループの発信が始まったとき（再電話の発信も含む）。

        通知タイミング「発信グループの開始」を選んだカスタム通知先に届きます（Business 以上）。
      parameters:
        - in: header
          name: X-Otocol-Signature
          required: true
          schema: { type: string }
          description: "`sha256=` + HMAC-SHA256(署名シークレット, 本文の生バイト列)"
        - in: header
          name: X-Otocol-Event
          required: true
          schema: { type: string, const: call_group_created }
        - in: header
          name: X-Otocol-Delivery
          required: true
          schema: { type: string }
          description: 配信 ID（本文の `id` と同じ）
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/NotificationEnvelope"
                - type: object
                  required: [data]
                  properties:
                    event:
                      const: call_group_created
                    data:
                      $ref: "#/components/schemas/CallGroupCreatedData"
            example:
              {
                "id": "6f1c2a9e-3b7d-4c1a-9e2f-0a1b2c3d4e5f",
                "event": "call_group_created",
                "sent_at": "2026-09-03T10:00:15+09:00",
                "data": {
                  "call_group": {
                    "id": "00000000-0000-0000-0000-000000000001",
                    "title": "9/3 予約リマインド",
                    "url": "https://dashboard.autocall.app/call_groups/00000000-0000-0000-0000-000000000001"
                  },
                  "call_group_job_id": "00000000-0000-0000-0000-00000000000a",
                  "script_title": "予約前日の確認",
                  "re_call": false,
                  "target_count": 120,
                  "start_at": "2026-09-03T10:00:00+09:00",
                  "phone_number": {
                    "id": "00000000-0000-0000-0000-000000000009",
                    "phone_number": "+81312345678",
                    "label": "本社代表"
                  }
                }
              }
      responses:
        "200":
          description: 受け取った（2xx なら何でも可）
  call_group_completed:
    post:
      # オトコルから送る側なので、API キー(bearerAuth)は要らない(全体の security を打ち消す)
      security: []
      tags: [通知（カスタム Webhook）]
      summary: 発信グループの完了
      description: |
        発信グループの発信がすべて終わったとき。集計値つき。

        通知タイミング「発信グループの完了」を選んだカスタム通知先に届きます（Business 以上）。
      parameters:
        - in: header
          name: X-Otocol-Signature
          required: true
          schema: { type: string }
          description: "`sha256=` + HMAC-SHA256(署名シークレット, 本文の生バイト列)"
        - in: header
          name: X-Otocol-Event
          required: true
          schema: { type: string, const: call_group_completed }
        - in: header
          name: X-Otocol-Delivery
          required: true
          schema: { type: string }
          description: 配信 ID（本文の `id` と同じ）
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/NotificationEnvelope"
                - type: object
                  required: [data]
                  properties:
                    event:
                      const: call_group_completed
                    data:
                      $ref: "#/components/schemas/CallGroupCompletedData"
            example:
              {
                "id": "6f1c2a9e-3b7d-4c1a-9e2f-0a1b2c3d4e5f",
                "event": "call_group_completed",
                "sent_at": "2026-09-03T10:00:15+09:00",
                "data": {
                  "call_group": {
                    "id": "00000000-0000-0000-0000-000000000001",
                    "title": "9/3 予約リマインド",
                    "url": "https://dashboard.autocall.app/call_groups/00000000-0000-0000-0000-000000000001"
                  },
                  "call_group_job_id": "00000000-0000-0000-0000-00000000000a",
                  "script_title": "予約前日の確認",
                  "start_at": "2026-09-03T10:00:00+09:00",
                  "calls_end_at": "2026-09-03T10:42:15+09:00",
                  "calls_count": 120,
                  "connected_count": 84,
                  "not_connected_count": 36,
                  "approved_rate": 62.5,
                  "rejected_rate": 8.33,
                  "total_price": 1260,
                  "phone_number": {
                    "id": "00000000-0000-0000-0000-000000000009",
                    "phone_number": "+81312345678",
                    "label": "本社代表"
                  }
                }
              }
      responses:
        "200":
          description: 受け取った（2xx なら何でも可）
  call_script_action_pushed:
    post:
      # オトコルから送る側なので、API キー(bearerAuth)は要らない(全体の security を打ち消す)
      security: []
      tags: [通知（カスタム Webhook）]
      summary: 発信時の番号アクション押下
      description: |
        発信した通話で、相手が番号（1・2 など）を押したとき。スクリプト側で各番号アクションの「通知する」を ON にした番号だけ届きます。

        通知タイミング「発信時の番号アクション押下」を選んだカスタム通知先に届きます（Business 以上）。
      parameters:
        - in: header
          name: X-Otocol-Signature
          required: true
          schema: { type: string }
          description: "`sha256=` + HMAC-SHA256(署名シークレット, 本文の生バイト列)"
        - in: header
          name: X-Otocol-Event
          required: true
          schema: { type: string, const: call_script_action_pushed }
        - in: header
          name: X-Otocol-Delivery
          required: true
          schema: { type: string }
          description: 配信 ID（本文の `id` と同じ）
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/NotificationEnvelope"
                - type: object
                  required: [data]
                  properties:
                    event:
                      const: call_script_action_pushed
                    data:
                      $ref: "#/components/schemas/CallScriptActionPushedData"
            example:
              {
                "id": "6f1c2a9e-3b7d-4c1a-9e2f-0a1b2c3d4e5f",
                "event": "call_script_action_pushed",
                "sent_at": "2026-09-03T10:00:15+09:00",
                "data": {
                  "call": {
                    "id": "00000000-0000-0000-0000-000000000002",
                    "to": "+819012345678",
                    "started_at": "2026-09-03T10:00:12+09:00",
                    "url": "https://dashboard.autocall.app/call_groups/00000000-0000-0000-0000-000000000001/calls/00000000-0000-0000-0000-000000000002"
                  },
                  "contact": {
                    "id": "00000000-0000-0000-0000-0000000000c1",
                    "name": "山田 太郎",
                    "phone_number": "+819012345678"
                  },
                  "call_group": {
                    "id": "00000000-0000-0000-0000-000000000001",
                    "title": "9/3 予約リマインド",
                    "url": "https://dashboard.autocall.app/call_groups/00000000-0000-0000-0000-000000000001"
                  },
                  "script_title": "予約前日の確認",
                  "dtmf": "1",
                  "phone_number": {
                    "id": "00000000-0000-0000-0000-000000000009",
                    "phone_number": "+81312345678",
                    "label": "本社代表"
                  }
                }
              }
      responses:
        "200":
          description: 受け取った（2xx なら何でも可）
  forwarding_unanswered:
    post:
      # オトコルから送る側なので、API キー(bearerAuth)は要らない(全体の security を打ち消す)
      security: []
      tags: [通知（カスタム Webhook）]
      summary: 発信時の転送不成立
      description: |
        発信した通話で、転送がつながらなかったとき。

        通知タイミング「発信時の転送不成立」を選んだカスタム通知先に届きます（Business 以上）。
      parameters:
        - in: header
          name: X-Otocol-Signature
          required: true
          schema: { type: string }
          description: "`sha256=` + HMAC-SHA256(署名シークレット, 本文の生バイト列)"
        - in: header
          name: X-Otocol-Event
          required: true
          schema: { type: string, const: forwarding_unanswered }
        - in: header
          name: X-Otocol-Delivery
          required: true
          schema: { type: string }
          description: 配信 ID（本文の `id` と同じ）
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/NotificationEnvelope"
                - type: object
                  required: [data]
                  properties:
                    event:
                      const: forwarding_unanswered
                    data:
                      $ref: "#/components/schemas/ForwardingUnansweredData"
            example:
              {
                "id": "6f1c2a9e-3b7d-4c1a-9e2f-0a1b2c3d4e5f",
                "event": "forwarding_unanswered",
                "sent_at": "2026-09-03T10:00:15+09:00",
                "data": {
                  "call": {
                    "id": "00000000-0000-0000-0000-000000000002",
                    "to": "+819012345678",
                    "started_at": "2026-09-03T10:00:12+09:00",
                    "url": "https://dashboard.autocall.app/call_groups/00000000-0000-0000-0000-000000000001/calls/00000000-0000-0000-0000-000000000002"
                  },
                  "contact": {
                    "id": "00000000-0000-0000-0000-0000000000c1",
                    "name": "山田 太郎",
                    "phone_number": "+819012345678"
                  },
                  "call_group": {
                    "id": "00000000-0000-0000-0000-000000000001",
                    "title": "9/3 予約リマインド",
                    "url": "https://dashboard.autocall.app/call_groups/00000000-0000-0000-0000-000000000001"
                  },
                  "script_title": "予約前日の確認",
                  "reason": "unanswered",
                  "reason_label": "転送先が応答しませんでした",
                  "phone_number": {
                    "id": "00000000-0000-0000-0000-000000000009",
                    "phone_number": "+81312345678",
                    "label": "本社代表"
                  }
                }
              }
      responses:
        "200":
          description: 受け取った（2xx なら何でも可）
  ai_call_completed:
    post:
      # オトコルから送る側なので、API キー(bearerAuth)は要らない(全体の security を打ち消す)
      security: []
      tags: [通知（カスタム Webhook）]
      summary: AI架電の結果
      description: |
        AI架電で相手と話した通話が終わったとき。`data.ai_call` は `GET /webhooks/ai_calls/{id}` と同じ形に `url` を足した物です。

        通知タイミング「AI架電の結果」を選んだカスタム通知先に届きます（Business 以上）。
      parameters:
        - in: header
          name: X-Otocol-Signature
          required: true
          schema: { type: string }
          description: "`sha256=` + HMAC-SHA256(署名シークレット, 本文の生バイト列)"
        - in: header
          name: X-Otocol-Event
          required: true
          schema: { type: string, const: ai_call_completed }
        - in: header
          name: X-Otocol-Delivery
          required: true
          schema: { type: string }
          description: 配信 ID（本文の `id` と同じ）
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/NotificationEnvelope"
                - type: object
                  required: [data]
                  properties:
                    event:
                      const: ai_call_completed
                    data:
                      $ref: "#/components/schemas/AiCallCompletedData"
            example:
              {
                "id": "6f1c2a9e-3b7d-4c1a-9e2f-0a1b2c3d4e5f",
                "event": "ai_call_completed",
                "sent_at": "2026-09-03T10:00:15+09:00",
                "data": {
                  "ai_call": {
                    "id": "00000000-0000-0000-0000-000000000006",
                    "test": false,
                    "direction": "outbound",
                    "contact": {
                      "id": "00000000-0000-0000-0000-0000000000c1",
                      "name": "山田 太郎",
                      "phone_number": "+819012345678"
                    },
                    "talk_script": {
                      "id": "00000000-0000-0000-0000-0000000000a1",
                      "name": "面談日程のご案内",
                      "version": 3
                    },
                    "status": "human",
                    "status_label": "人が出た",
                    "not_dialed_reason": null,
                    "scheduled_at": null,
                    "dialed_at": "2026-09-03T10:00:05+09:00",
                    "result_received_at": "2026-09-03T10:02:31+09:00",
                    "total_ms": 146000,
                    "end_label": "面談予約",
                    "call_outcome": "appointment_booked",
                    "do_not_call": false,
                    "transferred": false,
                    "fields": {
                      "preferred_date": "9月10日の午前",
                      "email": "taro@example.com"
                    },
                    "field_labels": {
                      "preferred_date": "希望日",
                      "email": "メールアドレス"
                    },
                    "fields_meta": {
                      "email": {
                        "verified": false
                      }
                    },
                    "path": [],
                    "unanswered_questions": [],
                    "url": "https://dashboard.autocall.app/ai_calls/00000000-0000-0000-0000-000000000006"
                  }
                }
              }
      responses:
        "200":
          description: 受け取った（2xx なら何でも可）
  short_message_group_completed:
    post:
      # オトコルから送る側なので、API キー(bearerAuth)は要らない(全体の security を打ち消す)
      security: []
      tags: [通知（カスタム Webhook）]
      summary: SMS送信の完了
      description: |
        SMS の送信が終わり、到達結果が確定したとき（送信完了の約 30 分後）。

        通知タイミング「SMS送信の完了」を選んだカスタム通知先に届きます（Business 以上）。
      parameters:
        - in: header
          name: X-Otocol-Signature
          required: true
          schema: { type: string }
          description: "`sha256=` + HMAC-SHA256(署名シークレット, 本文の生バイト列)"
        - in: header
          name: X-Otocol-Event
          required: true
          schema: { type: string, const: short_message_group_completed }
        - in: header
          name: X-Otocol-Delivery
          required: true
          schema: { type: string }
          description: 配信 ID（本文の `id` と同じ）
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/NotificationEnvelope"
                - type: object
                  required: [data]
                  properties:
                    event:
                      const: short_message_group_completed
                    data:
                      $ref: "#/components/schemas/ShortMessageGroupCompletedData"
            example:
              {
                "id": "6f1c2a9e-3b7d-4c1a-9e2f-0a1b2c3d4e5f",
                "event": "short_message_group_completed",
                "sent_at": "2026-09-03T10:00:15+09:00",
                "data": {
                  "short_message_group": {
                    "id": "00000000-0000-0000-0000-000000000003",
                    "title": "9/3 来店お礼SMS",
                    "url": "https://dashboard.autocall.app/short_message_groups/00000000-0000-0000-0000-000000000003"
                  },
                  "start_at": "2026-09-03T18:00:00+09:00",
                  "completed_at": "2026-09-03T18:03:40+09:00",
                  "send_count": 50,
                  "success_count": 48,
                  "failed_count": 2,
                  "total_price": 500.0,
                  "phone_number": {
                    "id": "00000000-0000-0000-0000-000000000009",
                    "phone_number": "+81312345678",
                    "label": "本社代表"
                  }
                }
              }
      responses:
        "200":
          description: 受け取った（2xx なら何でも可）
  inbound_webhook_succeeded:
    post:
      # オトコルから送る側なので、API キー(bearerAuth)は要らない(全体の security を打ち消す)
      security: []
      tags: [通知（カスタム Webhook）]
      summary: 発信APIの発火
      description: |
        発信API の呼び出しを受け付けて、発信・SMS・タグ付与を始めた（予約した）とき。

        通知タイミング「発信APIの発火」を選んだカスタム通知先に届きます（Business 以上）。
      parameters:
        - in: header
          name: X-Otocol-Signature
          required: true
          schema: { type: string }
          description: "`sha256=` + HMAC-SHA256(署名シークレット, 本文の生バイト列)"
        - in: header
          name: X-Otocol-Event
          required: true
          schema: { type: string, const: inbound_webhook_succeeded }
        - in: header
          name: X-Otocol-Delivery
          required: true
          schema: { type: string }
          description: 配信 ID（本文の `id` と同じ）
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/NotificationEnvelope"
                - type: object
                  required: [data]
                  properties:
                    event:
                      const: inbound_webhook_succeeded
                    data:
                      $ref: "#/components/schemas/InboundWebhookDeliveryData"
            example:
              {
                "id": "6f1c2a9e-3b7d-4c1a-9e2f-0a1b2c3d4e5f",
                "event": "inbound_webhook_succeeded",
                "sent_at": "2026-09-03T10:00:15+09:00",
                "data": {
                  "inbound_webhook": {
                    "id": "00000000-0000-0000-0000-000000000004",
                    "name": "予約システム連携",
                    "url": "https://dashboard.autocall.app/settings/webhooks/00000000-0000-0000-0000-000000000004"
                  },
                  "delivery_id": "00000000-0000-0000-0000-0000000000d1",
                  "status": "succeeded",
                  "error_message": null,
                  "contact": {
                    "id": "00000000-0000-0000-0000-0000000000c1",
                    "name": "山田 太郎",
                    "phone_number": "+819012345678"
                  },
                  "contact_count": 1,
                  "call_groups": [
                    {
                      "id": "00000000-0000-0000-0000-000000000001",
                      "title": "9/3 予約リマインド"
                    }
                  ],
                  "short_message_count": 1,
                  "scheduled_at": null,
                  "occurred_at": "2026-09-03T10:00:00+09:00"
                }
              }
      responses:
        "200":
          description: 受け取った（2xx なら何でも可）
  inbound_webhook_failed:
    post:
      # オトコルから送る側なので、API キー(bearerAuth)は要らない(全体の security を打ち消す)
      security: []
      tags: [通知（カスタム Webhook）]
      summary: 発信APIの失敗
      description: |
        発信API の呼び出しが失敗したとき（同じ失敗は 10 分に 1 回）。`status` は `throttled` / `rejected` / `invalid` / `failed`。

        通知タイミング「発信APIの失敗」を選んだカスタム通知先に届きます（Business 以上）。
      parameters:
        - in: header
          name: X-Otocol-Signature
          required: true
          schema: { type: string }
          description: "`sha256=` + HMAC-SHA256(署名シークレット, 本文の生バイト列)"
        - in: header
          name: X-Otocol-Event
          required: true
          schema: { type: string, const: inbound_webhook_failed }
        - in: header
          name: X-Otocol-Delivery
          required: true
          schema: { type: string }
          description: 配信 ID（本文の `id` と同じ）
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/NotificationEnvelope"
                - type: object
                  required: [data]
                  properties:
                    event:
                      const: inbound_webhook_failed
                    data:
                      $ref: "#/components/schemas/InboundWebhookDeliveryData"
            example:
              {
                "id": "6f1c2a9e-3b7d-4c1a-9e2f-0a1b2c3d4e5f",
                "event": "inbound_webhook_failed",
                "sent_at": "2026-09-03T10:00:15+09:00",
                "data": {
                  "inbound_webhook": {
                    "id": "00000000-0000-0000-0000-000000000004",
                    "name": "予約システム連携",
                    "url": "https://dashboard.autocall.app/settings/webhooks/00000000-0000-0000-0000-000000000004"
                  },
                  "delivery_id": "00000000-0000-0000-0000-0000000000d2",
                  "status": "invalid",
                  "error_message": "name と phone_number(有効な電話番号)は必須です",
                  "contact": null,
                  "contact_count": 0,
                  "call_groups": [],
                  "short_message_count": 0,
                  "scheduled_at": null,
                  "occurred_at": "2026-09-03T10:00:00+09:00"
                }
              }
      responses:
        "200":
          description: 受け取った（2xx なら何でも可）
  receive_call:
    post:
      # オトコルから送る側なので、API キー(bearerAuth)は要らない(全体の security を打ち消す)
      security: []
      tags: [通知（カスタム Webhook）]
      summary: 着信時すべて
      description: |
        この電話番号に着信があったとき（毎着信）。

        通知タイミング「着信時すべて」を選んだカスタム通知先に届きます（全プラン）。
      parameters:
        - in: header
          name: X-Otocol-Signature
          required: true
          schema: { type: string }
          description: "`sha256=` + HMAC-SHA256(署名シークレット, 本文の生バイト列)"
        - in: header
          name: X-Otocol-Event
          required: true
          schema: { type: string, const: receive_call }
        - in: header
          name: X-Otocol-Delivery
          required: true
          schema: { type: string }
          description: 配信 ID（本文の `id` と同じ）
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/NotificationEnvelope"
                - type: object
                  required: [data]
                  properties:
                    event:
                      const: receive_call
                    data:
                      $ref: "#/components/schemas/ReceiveCallData"
            example:
              {
                "id": "6f1c2a9e-3b7d-4c1a-9e2f-0a1b2c3d4e5f",
                "event": "receive_call",
                "sent_at": "2026-09-03T10:00:15+09:00",
                "data": {
                  "receive_call": {
                    "id": 12345,
                    "from": "+819012345678",
                    "received_at": "2026-09-03T19:05:40+09:00",
                    "phone_number": {
                      "id": "00000000-0000-0000-0000-000000000009",
                      "phone_number": "+81312345678",
                      "label": "本社代表"
                    },
                    "url": "https://dashboard.autocall.app/phone_numbers/00000000-0000-0000-0000-000000000009/receive_calls/12345"
                  },
                  "contact": {
                    "id": "00000000-0000-0000-0000-0000000000c1",
                    "name": "山田 太郎",
                    "phone_number": "+819012345678"
                  }
                }
              }
      responses:
        "200":
          description: 受け取った（2xx なら何でも可）
  receive_call_script_action_pushed:
    post:
      # オトコルから送る側なので、API キー(bearerAuth)は要らない(全体の security を打ち消す)
      security: []
      tags: [通知（カスタム Webhook）]
      summary: 着信時の番号アクション押下
      description: |
        着信した通話で、相手が番号を押したとき。スクリプト側で「通知する」を ON にした番号だけ届きます。

        通知タイミング「着信時の番号アクション押下」を選んだカスタム通知先に届きます（全プラン）。
      parameters:
        - in: header
          name: X-Otocol-Signature
          required: true
          schema: { type: string }
          description: "`sha256=` + HMAC-SHA256(署名シークレット, 本文の生バイト列)"
        - in: header
          name: X-Otocol-Event
          required: true
          schema: { type: string, const: receive_call_script_action_pushed }
        - in: header
          name: X-Otocol-Delivery
          required: true
          schema: { type: string }
          description: 配信 ID（本文の `id` と同じ）
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/NotificationEnvelope"
                - type: object
                  required: [data]
                  properties:
                    event:
                      const: receive_call_script_action_pushed
                    data:
                      $ref: "#/components/schemas/ReceiveCallScriptActionPushedData"
            example:
              {
                "id": "6f1c2a9e-3b7d-4c1a-9e2f-0a1b2c3d4e5f",
                "event": "receive_call_script_action_pushed",
                "sent_at": "2026-09-03T10:00:15+09:00",
                "data": {
                  "receive_call": {
                    "id": 12345,
                    "from": "+819012345678",
                    "received_at": "2026-09-03T19:05:40+09:00",
                    "phone_number": {
                      "id": "00000000-0000-0000-0000-000000000009",
                      "phone_number": "+81312345678",
                      "label": "本社代表"
                    },
                    "url": "https://dashboard.autocall.app/phone_numbers/00000000-0000-0000-0000-000000000009/receive_calls/12345"
                  },
                  "contact": {
                    "id": "00000000-0000-0000-0000-0000000000c1",
                    "name": "山田 太郎",
                    "phone_number": "+819012345678"
                  },
                  "script_title": "営業時間外の受付",
                  "dtmf": "2"
                }
              }
      responses:
        "200":
          description: 受け取った（2xx なら何でも可）
  receive_forwarding_unanswered:
    post:
      # オトコルから送る側なので、API キー(bearerAuth)は要らない(全体の security を打ち消す)
      security: []
      tags: [通知（カスタム Webhook）]
      summary: 着信時の転送不成立
      description: |
        着信した通話で、転送がつながらなかったとき。

        通知タイミング「着信時の転送不成立」を選んだカスタム通知先に届きます（全プラン）。
      parameters:
        - in: header
          name: X-Otocol-Signature
          required: true
          schema: { type: string }
          description: "`sha256=` + HMAC-SHA256(署名シークレット, 本文の生バイト列)"
        - in: header
          name: X-Otocol-Event
          required: true
          schema: { type: string, const: receive_forwarding_unanswered }
        - in: header
          name: X-Otocol-Delivery
          required: true
          schema: { type: string }
          description: 配信 ID（本文の `id` と同じ）
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/NotificationEnvelope"
                - type: object
                  required: [data]
                  properties:
                    event:
                      const: receive_forwarding_unanswered
                    data:
                      $ref: "#/components/schemas/ReceiveForwardingUnansweredData"
            example:
              {
                "id": "6f1c2a9e-3b7d-4c1a-9e2f-0a1b2c3d4e5f",
                "event": "receive_forwarding_unanswered",
                "sent_at": "2026-09-03T10:00:15+09:00",
                "data": {
                  "receive_call": {
                    "id": 12345,
                    "from": "+819012345678",
                    "received_at": "2026-09-03T19:05:40+09:00",
                    "phone_number": {
                      "id": "00000000-0000-0000-0000-000000000009",
                      "phone_number": "+81312345678",
                      "label": "本社代表"
                    },
                    "url": "https://dashboard.autocall.app/phone_numbers/00000000-0000-0000-0000-000000000009/receive_calls/12345"
                  },
                  "contact": {
                    "id": "00000000-0000-0000-0000-0000000000c1",
                    "name": "山田 太郎",
                    "phone_number": "+819012345678"
                  },
                  "reason": "unanswered",
                  "reason_label": "転送先が応答しませんでした"
                }
              }
      responses:
        "200":
          description: 受け取った（2xx なら何でも可）
  receive_ai_call_completed:
    post:
      # オトコルから送る側なので、API キー(bearerAuth)は要らない(全体の security を打ち消す)
      security: []
      tags: [通知（カスタム Webhook）]
      summary: AI受電の結果
      description: |
        AI受電で相手と話した通話が終わったとき。形は「AI架電の結果」と同じです。

        通知タイミング「AI受電の結果」を選んだカスタム通知先に届きます（全プラン）。
      parameters:
        - in: header
          name: X-Otocol-Signature
          required: true
          schema: { type: string }
          description: "`sha256=` + HMAC-SHA256(署名シークレット, 本文の生バイト列)"
        - in: header
          name: X-Otocol-Event
          required: true
          schema: { type: string, const: receive_ai_call_completed }
        - in: header
          name: X-Otocol-Delivery
          required: true
          schema: { type: string }
          description: 配信 ID（本文の `id` と同じ）
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/NotificationEnvelope"
                - type: object
                  required: [data]
                  properties:
                    event:
                      const: receive_ai_call_completed
                    data:
                      $ref: "#/components/schemas/AiCallCompletedData"
            example:
              {
                "id": "6f1c2a9e-3b7d-4c1a-9e2f-0a1b2c3d4e5f",
                "event": "receive_ai_call_completed",
                "sent_at": "2026-09-03T10:00:15+09:00",
                "data": {
                  "ai_call": {
                    "id": "00000000-0000-0000-0000-000000000007",
                    "test": false,
                    "direction": "inbound",
                    "contact": {
                      "id": "00000000-0000-0000-0000-0000000000c1",
                      "name": "山田 太郎",
                      "phone_number": "+819012345678"
                    },
                    "talk_script": {
                      "id": "00000000-0000-0000-0000-0000000000a1",
                      "name": "面談日程のご案内",
                      "version": 3
                    },
                    "status": "human",
                    "status_label": "人が出た",
                    "not_dialed_reason": null,
                    "scheduled_at": null,
                    "dialed_at": "2026-09-03T10:00:05+09:00",
                    "result_received_at": "2026-09-03T10:02:31+09:00",
                    "total_ms": 146000,
                    "end_label": "折り返し希望",
                    "call_outcome": "callback_requested",
                    "do_not_call": false,
                    "transferred": false,
                    "fields": {
                      "purpose": "予約の変更",
                      "callback_time": "明日の午前中"
                    },
                    "field_labels": {
                      "purpose": "ご用件",
                      "callback_time": "折り返しの時間"
                    },
                    "fields_meta": {},
                    "path": [],
                    "unanswered_questions": [],
                    "url": "https://dashboard.autocall.app/ai_calls/00000000-0000-0000-0000-000000000007"
                  }
                }
              }
      responses:
        "200":
          description: 受け取った（2xx なら何でも可）

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API キー（ack_ で始まる文字列）
      description: |
        オトコルの画面（設定 > 発信API）で発行した API キーを Bearer トークンとして付与します。

        ```
        Authorization: Bearer ack_xxxxxxxxxxxxxxxxxxxxxxxx
        ```

        API キーはアカウント単位で発行され、発行時にのみ全体が表示されます。失効させると即座に利用できなくなります。

  parameters:
    WebhookKey:
      name: webhook_key
      in: path
      required: true
      description: Webhook の識別子。画面に表示される Webhook URL に含まれています
      schema:
        type: string
      example: 7f3a9c1e2b4d
    Page:
      name: page
      in: query
      required: false
      description: 取得するページ番号
      schema:
        type: integer
        minimum: 1
        default: 1
    PerPage:
      name: per_page
      in: query
      required: false
      description: 1 ページあたりの明細の件数。最大 500（超える指定は 500 として扱います）
      schema:
        type: integer
        minimum: 1
        maximum: 500
        default: 100

  responses:
    ManagedContactResponse:
      description: 連絡先
      content:
        application/json:
          schema:
            type: object
            required: [contact]
            properties:
              contact: { $ref: "#/components/schemas/ManagedContact" }
    Unauthorized:
      description: 認証エラー（API キーが未指定・誤り・失効済み・期限切れ）
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: 認証に失敗しました
    Forbidden:
      description: 利用不可（必要なキー権限がない、Business 未満のプラン、または解約済み）
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: ご利用中のプランではこの機能をご利用いただけません。

  schemas:
    ManagedContactInput:
      type: object
      properties:
        name: { type: string, minLength: 1, example: 連携テスト }
        phone_number: { type: string, example: "09012345678" }
        memo: { type: [string, "null"] }
        memo2: { type: [string, "null"] }
        memo3: { type: [string, "null"] }
        custom_fields:
          type: object
          additionalProperties: true
          example: { customer_reference: sample-001 }
    ManagedContact:
      allOf:
        - $ref: "#/components/schemas/ManagedContactInput"
        - type: object
          properties:
            id: { type: string, format: uuid }
            status: { type: string, enum: [published, archived] }
            created_at: { type: string, format: date-time }
            updated_at: { type: string, format: date-time }
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: string
          description: エラー内容の説明

    ContactInput:
      type: object
      title: 連絡先
      description: |
        取り込む連絡先 1 件。Webhook に項目マッピングを設定している場合は、ここに示す名前ではなく設定した送信元の項目名で送信してください。
      required: [name, phone_number]
      additionalProperties: true
      properties:
        name:
          type: string
          description: 連絡先の名前
          example: 山田太郎
        phone_number:
          type: string
          description: 電話番号。ハイフンの有無は問いません（例 `09012345678` / `090-1234-5678`）
          example: "09012345678"
        memo:
          type: string
          description: メモ。発信スクリプトや SMS 本文での読み上げ・差し込みに使用できます。SMS 結果の `contact.memo` にそのまま返ります
          example: 8月申込
        memo2:
          type: string
          description: 予備のメモ欄
        memo3:
          type: string
          description: 予備のメモ欄

    ScheduledAt:
      title: 予約日時
      description: |
        予約発信の日時。ISO 8601 の文字列、または UNIX 時刻（秒。13 桁以上はミリ秒として扱います）。
        タイムゾーン無しの文字列は JST として解釈します。**未来かつ受信から 30 日以内** である必要があります。省略時は即時実行。
      oneOf:
        - type: string
          format: date-time
          example: "2026-09-01T10:00:00+09:00"
        - type: integer
          example: 1787148000

    IdempotencyKey:
      type: string
      title: 冪等キー
      description: |
        重複送信を防ぐためのキー。指定した場合は期限なく重複と判定します。
        省略時はリクエスト本文のハッシュ値を 10 分間だけキーとして使用します
      example: batch-2026-09-01-001

    BulkFireRequest:
      type: object
      title: 複数件をまとめて送信
      description: 複数の連絡先を 1 リクエストで送信します（推奨）。1 リクエスト = 1 通話グループにまとまります。
      required: [contacts]
      properties:
        contacts:
          type: array
          description: 連絡先の配列。最大 200 件
          minItems: 1
          maxItems: 200
          items:
            $ref: "#/components/schemas/ContactInput"
        scheduled_at:
          $ref: "#/components/schemas/ScheduledAt"
        idempotency_key:
          $ref: "#/components/schemas/IdempotencyKey"

    SingleFireRequest:
      title: 1 件のみ送信
      description: 1 件のみ送信する場合は `contacts` を省略し、`name` / `phone_number` / `memo` をボディ直下に記述します。
      allOf:
        - $ref: "#/components/schemas/ContactInput"
        - type: object
          properties:
            scheduled_at:
              $ref: "#/components/schemas/ScheduledAt"
            idempotency_key:
              $ref: "#/components/schemas/IdempotencyKey"

    GroupRef:
      type: object
      required: [id, title]
      properties:
        id:
          type: string
          format: uuid
          description: グループの ID。結果取得エンドポイントに渡します
        title:
          type: string
          description: グループの名称

    FireAccepted:
      type: object
      title: 受付
      required: [status, imported, skipped, fired, call_groups, short_message_groups]
      properties:
        status:
          type: string
          enum: [accepted, scheduled]
          description: |
            `accepted` = 受け付けて実行 / `scheduled` = 予約を作成
          x-enumDescriptions:
            accepted: 受け付けて実行
            scheduled: 予約を作成
        imported:
          type: integer
          description: 取り込んだ連絡先の件数（既存連絡先の更新も含む）
        skipped:
          type: integer
          description: 取り込まなかった件数（無効な行・重複番号・発信除外）
        fired:
          type: integer
          description: 実行した動作の数
        call_groups:
          type: array
          description: 作成された通話グループ。発信を設定していない場合は空配列
          items:
            $ref: "#/components/schemas/GroupRef"
        short_message_groups:
          type: array
          description: 作成された SMS 送信グループ。SMS 送信を設定していない場合は空配列
          items:
            $ref: "#/components/schemas/GroupRef"
        predictive_call_groups:
          type: array
          description: |
            連絡先を追加したプレディクティブコール。設定していない場合・追加できなかった場合は空配列。
            Webhook ごとに受付中の 1 本へ追加するため、続けて送信すると同じ ID が返ります
          items:
            $ref: "#/components/schemas/GroupRef"
        predictive_call_skipped_reason:
          type: string
          description: |
            プレディクティブコールに追加できなかった理由（プラン外・発信元の電話番号なし・対応できるオペレーターがいない・発信可能件数の超過など）。
            追加できた場合・設定していない場合は含みません。連絡先の取り込みと他の動作は実行されています
        scheduled_at:
          type: string
          format: date-time
          description: 予約した日時。`status` が `scheduled` の場合のみ返します

    FireDuplicate:
      type: object
      title: 重複
      description: 重複と判定された場合のレスポンス。他の項目は含みません
      required: [status]
      properties:
        status:
          type: string
          enum: [duplicate]
          description: 重複のため何もしていない

    GroupStatus:
      type: string
      title: グループのステータス
      enum: [not_start, in_progress, completed, canceled]
      description: |
        | 値 | 表記 | 意味 |
        |---|---|---|
        | `not_start` | 開始前 | 実行前（予約待ちを含む） |
        | `in_progress` | 進行中 | 実行中 |
        | `completed` | 完了 | すべての実行が終了 |
        | `canceled` | キャンセル | 実行前に取り消された |
      x-enumDescriptions:
        not_start: 開始前（予約待ちを含む）
        in_progress: 進行中
        completed: 完了
        canceled: キャンセル

    Pagination:
      type: object
      required: [page, per_page, total_pages, total_count]
      properties:
        page:
          type: integer
          description: 現在のページ番号
        per_page:
          type: integer
          description: 1 ページあたりの件数
        total_pages:
          type: integer
          description: 総ページ数
        total_count:
          type: integer
          description: 明細の総件数

    ContactRef:
      type: object
      required: [id, name, phone_number]
      properties:
        id:
          type: string
          format: uuid
          description: 連絡先の ID
        name:
          type: string
          description: 連絡先の名前
        phone_number:
          type: string
          description: 電話番号（E.164。例 `+819012345678`）

    CallContact:
      type: object
      description: 発信した相手の連絡先
      required: [id, name, phone_number, memo, memo2, memo3]
      properties:
        id:
          type: string
          format: uuid
          description: 連絡先の ID
        name:
          type: string
          description: 連絡先の名前
        phone_number:
          type: string
          description: 電話番号（E.164。例 `+819012345678`）
        memo:
          type: [string, "null"]
          description: 連絡先の `memo`（取得時点の電話帳の値）。送信元の識別子との突き合わせに使えます
        memo2:
          type: [string, "null"]
          description: 連絡先の `memo2`
        memo3:
          type: [string, "null"]
          description: 連絡先の `memo3`

    CalledBackAt:
      type: [string, "null"]
      format: date-time
      description: |
        その発信のあと、同じ相手（電話番号）からお客様の電話番号へ最初に着信した日時（着信した時点から返ります）。該当する着信が無ければ `null`。
        着信の記録から求めた値で、折り返しかどうかの判定は行いません（期限なし・着信先の番号は問いません）。
        同じ相手に複数回発信していると、着信より前の発信すべてに同じ日時が入ります。通話の `status` とは連動しません

    CallStatus:
      type: string
      title: 通話のステータス
      enum:
        - completed
        - uncompleted
        - no_reaction
        - called_back
        - in_progress
        - not_start
        - no_answer
        - busy
        - machine_answer
        - failed
        - canceled
        - risk_rejected
      description: |
        | 値 | 表記 | 集計 |
        |---|---|---|
        | `completed` | 全て回答 | connected |
        | `uncompleted` | 一部回答 | connected |
        | `no_reaction` | 未回答 | connected |
        | `called_back` | 折り返しあり | connected |
        | `in_progress` | 進行中 | — |
        | `not_start` | 開始前 | — |
        | `no_answer` | 電話に出ず | not_connected |
        | `busy` | 通話中 | not_connected |
        | `machine_answer` | 留守電 | not_connected |
        | `failed` | 失敗 | not_connected |
        | `canceled` | キャンセル | not_connected |
        | `risk_rejected` | 発信拒否 | not_connected |
      x-enumDescriptions:
        completed: 全て回答
        uncompleted: 一部回答
        no_reaction: 未回答
        called_back: 折り返しあり
        in_progress: 進行中
        not_start: 開始前
        no_answer: 電話に出ず
        busy: 通話中
        machine_answer: 留守電
        failed: 失敗
        canceled: キャンセル
        risk_rejected: 発信拒否

    NotConnectedBreakdown:
      type: object
      description: つながらなかった内訳。発信前は空のオブジェクト
      properties:
        no_answer:
          type: integer
          description: 電話に出ず
        busy:
          type: integer
          description: 通話中
        failed:
          type: integer
          description: 失敗
        canceled:
          type: integer
          description: キャンセル
        machine_answer:
          type: integer
          description: 留守電
        risk_rejected:
          type: integer
          description: 発信拒否

    CallGroupSummary:
      type: object
      required: [total, connected, not_connected, approved, rejected, not_connected_breakdown]
      properties:
        total:
          type: integer
          description: 発信した件数
        connected:
          type: integer
          description: つながった件数
        not_connected:
          type: integer
          description: つながらなかった件数
        approved:
          type: integer
          description: 成約に設定されたボタンが押された件数
        rejected:
          type: integer
          description: 不成約に設定されたボタンが押された件数
        not_connected_breakdown:
          $ref: "#/components/schemas/NotConnectedBreakdown"

    CallGroup:
      type: object
      required: [id, title, status, status_label, start_at, summary]
      properties:
        id:
          type: string
          format: uuid
          description: 通話グループの ID
        title:
          type: string
          description: 通話グループの名称
        status:
          $ref: "#/components/schemas/GroupStatus"
        status_label:
          type: string
          description: 状態の日本語表記（画面の表示と同一）
          example: 完了
        start_at:
          type: [string, "null"]
          format: date-time
          description: 発信の開始日時（予約の場合は予定日時）
        summary:
          $ref: "#/components/schemas/CallGroupSummary"

    PushedButton:
      type: object
      required: [dtmf, result]
      properties:
        dtmf:
          type: [string, "null"]
          description: 押された番号
          example: "1"
        result:
          type: [string, "null"]
          enum: [approved, rejected, null]
          description: そのボタンに設定された成約 / 不成約。未設定は `null`
          x-enumDescriptions:
            approved: 成約
            rejected: 不成約

    Call:
      type: object
      required: [id, contact, status, status_label, duration, called_at, called_back_at, pushed]
      properties:
        id:
          type: string
          format: uuid
          description: 通話の ID
        contact:
          $ref: "#/components/schemas/CallContact"
        status:
          $ref: "#/components/schemas/CallStatus"
        status_label:
          type: string
          description: ステータスの日本語表記
          example: 全て回答
        duration:
          type: [integer, "null"]
          description: 通話時間（秒）。通話していない場合は `null`
        called_at:
          type: string
          format: date-time
          description: 発信した日時
        called_back_at:
          $ref: "#/components/schemas/CalledBackAt"
        pushed:
          type: array
          description: 押されたボタン（押された順）
          items:
            $ref: "#/components/schemas/PushedButton"

    CallGroupResult:
      type: object
      required: [call_group, calls, pagination]
      properties:
        call_group:
          $ref: "#/components/schemas/CallGroup"
        calls:
          type: array
          description: 通話明細。発信前は空配列
          items:
            $ref: "#/components/schemas/Call"
        pagination:
          $ref: "#/components/schemas/Pagination"

    PredictiveCallGroupStatus:
      type: string
      title: プレディクティブコールの状態
      enum: [not_started, in_progress, pause, completed]
      description: |
        | 値 | 表記 | 説明 |
        |---|---|---|
        | `not_started` | 開始前 | オペレーターの開始待ち |
        | `in_progress` | 進行中 | 発信中 |
        | `pause` | 一時停止 | — |
        | `completed` | 完了 | すべての発信が終わった |

    PredictiveCallStatus:
      type: string
      title: プレディクティブコールの通話のステータス
      enum: [not_start, in_progress, completed, no_answer, busy, machine_answer, failed, canceled, risk_rejected]
      description: |
        | 値 | 表記 | 集計 |
        |---|---|---|
        | `completed` | 完了（相手が電話に出た） | connected |
        | `in_progress` | 進行中 | — |
        | `not_start` | 開始前 | — |
        | `no_answer` | 電話に出ず | not_connected |
        | `busy` | 通話中 | not_connected |
        | `machine_answer` | 留守電 | not_connected |
        | `failed` | 失敗 | not_connected |
        | `canceled` | キャンセル | not_connected |
        | `risk_rejected` | 発信拒否 | not_connected |

    PredictiveCallGroupSummary:
      type: object
      required: [contacts, not_called_contacts, calls, connected, not_connected, not_connected_breakdown]
      properties:
        contacts:
          type: integer
          description: 対象の連絡先の件数（発信 API で追加されると増えます）
        not_called_contacts:
          type: integer
          description: まだ発信していない連絡先の件数
        calls:
          type: integer
          description: 発信した通話の件数
        connected:
          type: integer
          description: 相手が電話に出た通話の件数
        not_connected:
          type: integer
          description: つながらなかった通話の件数
        not_connected_breakdown:
          $ref: "#/components/schemas/NotConnectedBreakdown"

    PredictiveCallGroup:
      type: object
      required: [id, title, status, status_label, summary]
      properties:
        id:
          type: string
          format: uuid
          description: プレディクティブコールの ID
        title:
          type: string
          description: プレディクティブコールの名称（発信 API が作ったものは「Webhook 名_作成日時」）
        status:
          $ref: "#/components/schemas/PredictiveCallGroupStatus"
        status_label:
          type: string
          description: 状態の日本語表記（画面の表示と同一）
          example: 進行中
        summary:
          $ref: "#/components/schemas/PredictiveCallGroupSummary"

    Operator:
      type: [object, "null"]
      description: |
        対応したオペレーター。相手が出なかった通話、相手が出たときに待機中のオペレーターがいなかった通話は `null`
      required: [id, name, user]
      properties:
        id:
          type: string
          format: uuid
          description: オペレーターの ID
        name:
          type: string
          description: オペレーターの名前（画面の「オペレーター」で登録した名前）
        user:
          type: [object, "null"]
          description: オペレーターに紐づくオトコルのユーザー。紐づけの無いオペレーター（電話番号で受けるオペレーターなど）は `null`
          required: [id, name, email]
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string
            email:
              type: string

    PredictiveCall:
      type: object
      required: [id, contact, operator, status, status_label, duration, called_at, called_back_at]
      properties:
        id:
          type: string
          format: uuid
          description: 通話の ID
        contact:
          $ref: "#/components/schemas/CallContact"
        operator:
          $ref: "#/components/schemas/Operator"
        status:
          $ref: "#/components/schemas/PredictiveCallStatus"
        status_label:
          type: string
          description: ステータスの日本語表記
          example: 完了
        duration:
          type: [integer, "null"]
          description: 通話時間（秒）。確定前は `null`
        called_at:
          type: string
          format: date-time
          description: 発信した日時
        called_back_at:
          $ref: "#/components/schemas/CalledBackAt"

    PredictiveCallGroupResult:
      type: object
      required: [predictive_call_group, calls, pagination]
      properties:
        predictive_call_group:
          $ref: "#/components/schemas/PredictiveCallGroup"
        calls:
          type: array
          description: 通話明細（発信した順）。発信前は空配列
          items:
            $ref: "#/components/schemas/PredictiveCall"
        pagination:
          $ref: "#/components/schemas/Pagination"

    PredictiveCallGroupList:
      type: object
      required: [predictive_call_groups, pagination]
      properties:
        predictive_call_groups:
          type: array
          description: プレディクティブコール（作成日時の新しい順）
          items:
            allOf:
              - $ref: "#/components/schemas/PredictiveCallGroup"
              - type: object
                required: [created_at]
                properties:
                  created_at:
                    type: string
                    format: date-time
                    description: 作成日時（JST）
        pagination:
          $ref: "#/components/schemas/Pagination"

    ShortMessageStatus:
      type: string
      title: SMS のステータス
      enum: [queued, sending, sent, delivered, failed, undelivered, delivery_unknown]
      description: |
        | 値 | 表記 | 集計 | 意味 |
        |---|---|---|---|
        | `queued` | 送信予定 | — | 送信待ち |
        | `sending` | 送信中 | — | 送信処理中 |
        | `sent` | 送信済み | success | 送信までを確認（到達は未確定） |
        | `delivered` | 送信済み | success | 到達確認まで取れた |
        | `failed` | 送信失敗 | failed | 送信できなかった |
        | `undelivered` | 未到達 | failed | 送信したが相手に届かなかった |
        | `delivery_unknown` | 到達不明 | failed | 到達結果を確認できなかった |

        `sent` は通信事業者からの到達通知で `delivered` / `undelivered` に更新されます。時間を空けて再取得してください。
      x-enumDescriptions:
        queued: 送信予定
        sending: 送信中
        sent: 送信済み（到達は未確定）
        delivered: 送信済み（到達確認済）
        failed: 送信失敗
        undelivered: 未到達
        delivery_unknown: 到達不明

    ShortMessageGroupSummary:
      type: object
      required: [total, success, failed, total_price]
      properties:
        total:
          type: integer
          description: 送信を試みた件数。SMS を受けられない番号（固定電話など）は送信自体を行わないため、取り込んだ連絡先の件数と一致しないことがあります
        success:
          type: integer
          description: 送信成功の件数（`sent` + `delivered`）
        failed:
          type: integer
          description: 送信失敗の件数（`failed` + `undelivered` + `delivery_unknown`）
        total_price:
          type: number
          description: 送信料金の合計（円）
          example: 20.0

    ShortMessageGroup:
      type: object
      required: [id, title, status, status_label, start_at, send_from, summary]
      properties:
        id:
          type: string
          format: uuid
          description: SMS 送信グループの ID
        title:
          type: string
          description: SMS 送信グループの名称
        status:
          $ref: "#/components/schemas/GroupStatus"
        status_label:
          type: string
          description: 状態の日本語表記（画面の表示と同一）
          example: 完了
        start_at:
          type: [string, "null"]
          format: date-time
          description: 送信の開始日時（予約の場合は予定日時）
        send_from:
          type: [string, "null"]
          description: 送信元の電話番号（国内表記。例 `050-1234-5678`）
        summary:
          $ref: "#/components/schemas/ShortMessageGroupSummary"

    ShortMessageContact:
      type: object
      description: 宛先の連絡先。同じ電話番号の連絡先が電話帳に無い場合は `null`
      required: [id, name, phone_number, memo, memo2, memo3]
      properties:
        id:
          type: string
          format: uuid
          description: 連絡先の ID
        name:
          type: string
          description: 連絡先の名前
        phone_number:
          type: string
          description: 電話番号（E.164。例 `+819012345678`）
        memo:
          type: [string, "null"]
          description: Webhook 受信時に取り込んだ `memo`。送信元の識別子との突き合わせに使えます
        memo2:
          type: [string, "null"]
          description: Webhook 受信時に取り込んだ `memo2`
        memo3:
          type: [string, "null"]
          description: Webhook 受信時に取り込んだ `memo3`

    ShortMessage:
      type: object
      required: [id, send_to, contact, status, status_label, content, price, sent_at]
      properties:
        id:
          type: string
          format: uuid
          description: 送信の ID
        send_to:
          type: [string, "null"]
          description: 宛先の電話番号（国内表記。例 `090-1234-5678`）
        contact:
          oneOf:
            - $ref: "#/components/schemas/ShortMessageContact"
            - type: "null"
        status:
          $ref: "#/components/schemas/ShortMessageStatus"
        status_label:
          type: string
          description: ステータスの日本語表記
          example: 送信済み
        content:
          type: [string, "null"]
          description: 送信した本文（変数展開後）
        price:
          type: [number, "null"]
          description: 送信料金（円）
          example: 10.0
        sent_at:
          type: string
          format: date-time
          description: 送信した日時

    ShortMessageGroupResult:
      type: object
      required: [short_message_group, short_messages, pagination]
      properties:
        short_message_group:
          $ref: "#/components/schemas/ShortMessageGroup"
        short_messages:
          type: array
          description: 送信明細。送信前は空配列
          items:
            $ref: "#/components/schemas/ShortMessage"
        pagination:
          $ref: "#/components/schemas/Pagination"

    AiCallResult:
      type: object
      description: AI通話 1 件の結果。取得（GET）と通知の「AI架電の結果」「AI受電の結果」（`data.ai_call`）で同じ形
      required: [id, status, status_label, fields, fields_meta, path, unanswered_questions]
      properties:
        id:
          type: string
          format: uuid
          description: 通話の ID
        test:
          type: boolean
          description: スクリプトの「試しに話す」で鳴らした通話なら true。本番の結果と分けるときに使う（`contact` が null でも本番の着信はありえるので、`contact` では見分けられません）
          example: false
        direction:
          type: string
          enum: [outbound, inbound]
          description: "`outbound`（こちらからかけた） / `inbound`（かかってきた）"
          example: outbound
        contact:
          type: [object, "null"]
          description: 相手の連絡先。試し発信・電話帳に無い番号からの着信など、電話帳を通らない通話は null
          properties:
            id:
              type: string
              format: uuid
            name:
              type: [string, "null"]
            phone_number:
              type: string
              example: "+819012345678"
        talk_script:
          type: object
          description: 使ったスクリプトと版
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string
            version:
              type: [integer, "null"]
        status:
          type: string
          description: 通話の状態。`scheduled`（予約中） / `dialing`（発信中） / `undeliverable`（相手が出なかった・話し中・不達） / `not_dialed`（発信禁止時間帯・無料枠・お支払いなどで、オトコル側が鳴らさなかった） / `canceled`（鳴らす前に取り消した） / `human`（会話した） / `unknown`
          example: human
        status_label:
          type: string
          description: 状態の日本語表記
          example: 会話した
        not_dialed_reason:
          type: [string, "null"]
          description: "`status` が `not_dialed` のときだけ、オトコルが鳴らさなかった理由（例: `発信禁止時間帯` / `無料枠を使い切った` / `カード決済が回収不能` / `公開された版が無い`）。それ以外は null。人が読むための文言で、変わることがあります"
          example: null
        scheduled_at:
          type: [string, "null"]
          format: date-time
          description: 予約発信の予定時刻。即時発信は null
        dialed_at:
          type: [string, "null"]
          format: date-time
          description: 鳴らし始めた日時
        result_received_at:
          type: [string, "null"]
          format: date-time
          description: 会話の結果が確定した日時。相手が出なかった通話は null
        total_ms:
          type: [integer, "null"]
          description: 通話時間（ミリ秒）
        end_label:
          type: [string, "null"]
          description: 終わり方の見出し（スクリプトの「終わり方」の表示名）。相手が出なかった通話・鳴らさなかった通話は null
          example: 予約が取れた
        call_outcome:
          type: [string, "null"]
          description: 終わり方から引いた成果（集計用）
          example: appointment_booked
        do_not_call:
          type: boolean
          description: 「二度とかけるな」と言われた（この番号は発信除外リストに載り、以後このアカウントの発信・SMS から外れます）
        transferred:
          type: boolean
          description: 担当者へ転送した
        fields:
          type: object
          description: 集めた項目の最終値 `{項目キー => 値}`。値は項目の型（文字列・日付 `YYYY-MM-DD`・真偽値・数値）。復唱しても確定できなかった値も入ります（`fields_meta` で見分けます）
          additionalProperties: true
          example: { birth_date: "1994-03-08", consent: true, email: "taro@exampel.jp" }
        field_labels:
          type: object
          description: 項目キー → 見出し
          additionalProperties:
            type: string
          example: { birth_date: 生年月日, consent: 同意, email: メールアドレス }
        fields_meta:
          type: object
          description: |
            項目キー → 項目の確かさ。メールアドレス・氏名など、**復唱して確かめた項目だけ**が入ります。それ以外の項目（復唱の前に通話が終わった項目を含む）のキーは入りません。該当が無い通話は `{}` です。

            | `verified` | 意味 |
            |---|---|
            | `true` | 復唱して、相手が「はい」と答えた |
            | `false` | 復唱しても確定できなかった（未確定）。最後に聞いた値のまま `fields` に入ります。電話帳への保存を設定した項目は電話帳にも保存し、後の通話で確定した値が出たら、人が直していない限りそちらに置き換えます |
          additionalProperties:
            type: object
            required: [verified]
            properties:
              verified:
                type: boolean
                description: 復唱で確定したか。`false` は未確定
          example: { email: { verified: false } }
        path:
          type: array
          description: 通った経路（どのステップで、どの意図に分岐したか）
          items:
            type: object
            additionalProperties: true
            example: { kind: branch, stepId: nationality, intent: japanese, atMs: 13481 }
        unanswered_questions:
          type: array
          description: 相手の質問のうち、スクリプトに答えが無かった物
          items:
            type: object
            properties:
              stepId:
                type: string
              text:
                type: string
              atMs:
                type: integer

    NotificationEnvelope:
      type: object
      description: カスタム Webhook の通知の封筒（全通知タイミング共通）
      required: [id, event, sent_at, data]
      properties:
        id:
          type: string
          description: 配信 ID（`X-Otocol-Delivery` と同じ。重複除去に使う）。テスト送信は `test-` で始まる
        event:
          type: string
          description: 通知タイミング
        sent_at:
          type: string
          format: date-time
          description: オトコルが送信した日時
        data:
          type: object
          description: 通知タイミングごとの中身

    NullableContact:
      description: 相手の連絡先。電話帳に無い相手は null
      anyOf:
        - $ref: "#/components/schemas/ContactRef"
        - type: "null"

    NotificationPhoneNumber:
      type: [object, "null"]
      description: こちらの電話番号（発信元 / 着信した番号）
      properties:
        id:
          type: string
          format: uuid
        phone_number:
          type: string
          description: E.164
          example: "+81312345678"
        label:
          type: [string, "null"]
          description: 画面で付けた番号の名前

    NotificationCallGroup:
      type: object
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        url:
          type: string
          description: オトコルの画面の詳細ページ

    NotificationCall:
      type: object
      description: 発信 1 件
      properties:
        id:
          type: string
          format: uuid
        to:
          type: string
          description: かけた番号（E.164）
        started_at:
          type: string
          format: date-time
        url:
          type: string
          description: オトコルの画面の通話詳細

    NotificationReceiveCall:
      type: object
      description: 着信 1 件
      properties:
        id:
          type: integer
        from:
          type: string
          description: かけてきた番号（E.164）。非通知などは Twilio の値のまま（`anonymous` など）
        received_at:
          type: string
          format: date-time
        phone_number:
          $ref: "#/components/schemas/NotificationPhoneNumber"
        url:
          type: string
          description: オトコルの画面の着信詳細

    ForwardingReason:
      type: string
      description: |
        転送がつながらなかった理由。
        `no_target`（転送先未設定・オペレーター不在・発信失敗） / `unanswered`（転送先が応答しなかった） /
        `caller_hung_up`（呼び出し中に相手が切った） / `queue_abandoned`（オペレーターにつながる前に終了）
      enum: [no_target, unanswered, caller_hung_up, queue_abandoned]

    CallGroupCreatedData:
      type: object
      properties:
        call_group:
          $ref: "#/components/schemas/NotificationCallGroup"
        call_group_job_id:
          type: string
          format: uuid
          description: 今回の発信（再電話ごとに別）の ID
        script_title:
          type: [string, "null"]
        re_call:
          type: boolean
          description: 再電話の発信なら true
        target_count:
          type: integer
          description: 発信対象の件数
        start_at:
          type: string
          format: date-time
        phone_number:
          $ref: "#/components/schemas/NotificationPhoneNumber"

    CallGroupCompletedData:
      type: object
      properties:
        call_group:
          $ref: "#/components/schemas/NotificationCallGroup"
        call_group_job_id:
          type: string
          format: uuid
        script_title:
          type: [string, "null"]
        start_at:
          type: string
          format: date-time
        calls_end_at:
          type: [string, "null"]
          format: date-time
        calls_count:
          type: integer
        connected_count:
          type: integer
        not_connected_count:
          type: integer
        approved_rate:
          type: number
          description: 成立率（%・つながった通話に対する）
        rejected_rate:
          type: number
          description: 拒否率（%・つながった通話に対する）
        total_price:
          type: integer
          description: 通話料金の合計（円）
        phone_number:
          $ref: "#/components/schemas/NotificationPhoneNumber"

    CallScriptActionPushedData:
      type: object
      properties:
        call:
          $ref: "#/components/schemas/NotificationCall"
        contact:
          $ref: "#/components/schemas/NullableContact"
        call_group:
          $ref: "#/components/schemas/NotificationCallGroup"
        script_title:
          type: [string, "null"]
        dtmf:
          type: string
          description: 押された番号
          example: "1"
        phone_number:
          $ref: "#/components/schemas/NotificationPhoneNumber"

    ForwardingUnansweredData:
      type: object
      properties:
        call:
          $ref: "#/components/schemas/NotificationCall"
        contact:
          $ref: "#/components/schemas/NullableContact"
        call_group:
          $ref: "#/components/schemas/NotificationCallGroup"
        script_title:
          type: [string, "null"]
        reason:
          $ref: "#/components/schemas/ForwardingReason"
        reason_label:
          type: string
          description: 理由の日本語表記（人が読むための文言で、変わることがあります）
        phone_number:
          $ref: "#/components/schemas/NotificationPhoneNumber"

    ShortMessageGroupCompletedData:
      type: object
      properties:
        short_message_group:
          type: object
          properties:
            id:
              type: string
              format: uuid
            title:
              type: string
            url:
              type: string
        start_at:
          type: string
          format: date-time
        completed_at:
          type: [string, "null"]
          format: date-time
        send_count:
          type: integer
        success_count:
          type: integer
          description: 届いた件数
        failed_count:
          type: integer
        total_price:
          type: number
          description: SMS 料金の合計（円）
        phone_number:
          $ref: "#/components/schemas/NotificationPhoneNumber"

    InboundWebhookDeliveryData:
      type: object
      description: 発信API の 1 回の呼び出し（発火）の結果。成功・失敗とも同じ形
      properties:
        inbound_webhook:
          type: object
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string
            url:
              type: string
        delivery_id:
          type: string
          format: uuid
          description: 発火ログの ID
        status:
          type: string
          enum: [succeeded, throttled, rejected, invalid, failed]
        error_message:
          type: [string, "null"]
          description: 失敗の理由（API の `error` と同じ文言）
        contact:
          $ref: "#/components/schemas/NullableContact"
        contact_count:
          type: integer
          description: 取り込んだ連絡先の件数（`contact` は代表 1 件）
        call_groups:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
              title:
                type: string
        predictive_call_groups:
          type: array
          description: 連絡先を追加したプレディクティブコール
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
              title:
                type: string
        short_message_count:
          type: integer
        scheduled_at:
          type: [string, "null"]
          format: date-time
          description: 予約発火の予定時刻。即時は null
        occurred_at:
          type: string
          format: date-time

    AiCallCompletedData:
      type: object
      properties:
        ai_call:
          allOf:
            - $ref: "#/components/schemas/AiCallResult"
            - type: object
              properties:
                url:
                  type: string
                  description: オトコルの画面の通話詳細

    ReceiveCallData:
      type: object
      properties:
        receive_call:
          $ref: "#/components/schemas/NotificationReceiveCall"
        contact:
          $ref: "#/components/schemas/NullableContact"

    ReceiveCallScriptActionPushedData:
      type: object
      properties:
        receive_call:
          $ref: "#/components/schemas/NotificationReceiveCall"
        contact:
          $ref: "#/components/schemas/NullableContact"
        script_title:
          type: [string, "null"]
        dtmf:
          type: string
          description: 押された番号
          example: "2"

    ReceiveForwardingUnansweredData:
      type: object
      properties:
        receive_call:
          $ref: "#/components/schemas/NotificationReceiveCall"
        contact:
          $ref: "#/components/schemas/NullableContact"
        reason:
          $ref: "#/components/schemas/ForwardingReason"
        reason_label:
          type: string
