# 콜 플로우 부분 수정 (/docs/api/callflows/updateCallFlow)
**PATCH /v1/accounts/{accountId}/call-flows/{callFlowId}**
```json
{
  "operationId": "updateCallFlow",
  "summary": "콜 플로우 부분 수정",
  "description": "준 필드만 바꿉니다. `name`·`graph` 중 최소 하나는 있어야 하고, 둘 다 없으면 400입니다.\n\n`graph`를 줄 때는 콘솔 빌더가 잡아 둔 캔버스 좌표를 **보존**합니다 — 공개 계약에 좌표가\n없어 API로는 보낼 수 없는데, 그대로 덮으면 콘솔에서 잡아 둔 배치가 매 요청마다 날아가기\n때문입니다. (좌표까지 초기화하려면 `PUT`을 쓰세요.)\n\n`graph`는 부분 그래프가 아니라 **완결된 그래프 하나**여야 합니다 — 블럭 하나만 보내는\n병합은 지원하지 않습니다.",
  "tags": [
    "CallFlows"
  ],
  "parameters": [
    {
      "name": "accountId",
      "in": "path",
      "required": true,
      "schema": {
        "type": "string"
      },
      "description": "계정 ID",
      "example": "AC1a2b3c4d"
    },
    {
      "name": "callFlowId",
      "in": "path",
      "required": true,
      "schema": {
        "type": "string"
      },
      "description": "콜 플로우 ID",
      "example": "cmryw3ycm000001s6on0kp9a8"
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "minProperties": 1,
          "description": "`name`·`graph` 중 최소 하나",
          "properties": {
            "name": {
              "type": "string",
              "maxLength": 80,
              "description": "플로우 이름",
              "example": "고객센터 ARS (개편)"
            },
            "graph": {
              "description": "콜 플로우 그래프 — **요청용**. 블럭(`nodes`)과 시작점(`start`)으로 통화 흐름 전체를 기술합니다.\n\n⚠️ **요청과 응답의 형태가 다릅니다.** 기본값이 있는 필드(`menu.maxDigits`·`menu.timeoutSec`,\n`record.maxLengthSec`·`playBeep`·`finishKey`, `dial.timeoutSec`, `voice.language`,\n`optout.number`·`channel`)는 요청에서 **생략할 수 있고**, 응답에는 **항상 채워져** 돌아옵니다.\n\n캔버스 좌표(`ui`)는 공개 계약 밖입니다 — 보내도 저장되지 않고, 응답에도 실리지 않습니다.\n콘솔 빌더가 잡아 둔 좌표는 `PUT`이면 버려지고 `PATCH`면 보존됩니다.\n\n그래프 전체는 256KB를 넘을 수 없습니다.",
              "allOf": [
                {
                  "type": "object",
                  "description": "콜 플로우 그래프 — **응답용**. 요청에서 생략했던 기본값 필드가 모두 채워진 형태입니다\n(`CallFlowGraphInput` 설명 참조). 이 값을 그대로 고쳐 `PUT`/`PATCH`로 돌려보낼 수 있습니다.\n\n캔버스 좌표(`ui`)는 실리지 않습니다 — 에디터 전용이라 공개 계약에서 제외합니다.",
                  "required": [
                    "version",
                    "start",
                    "nodes"
                  ],
                  "properties": {
                    "version": {
                      "type": "integer",
                      "enum": [
                        1
                      ],
                      "example": 1
                    },
                    "start": {
                      "type": "string",
                      "description": "시작 블럭 id",
                      "example": "greeting"
                    },
                    "voice": {
                      "type": "object",
                      "description": "플로우 전역 음성. 이 플로우의 모든 멘트를 이 목소리가 읽습니다. 생략하면 기본(무료) 음성입니다.\n`clawops`(한국AI통신 기본 음성)는 무료이고 `cartesia`·`elevenlabs`·`google`은 유료입니다. 다른 조직이 등록한 복제 음성 id는 403(`call_flow_voice_forbidden`)으로 거절합니다.",
                      "required": [
                        "provider"
                      ],
                      "properties": {
                        "provider": {
                          "type": "string",
                          "enum": [
                            "clawops",
                            "cartesia",
                            "elevenlabs",
                            "google"
                          ],
                          "description": "TTS 공급자",
                          "example": "clawops"
                        },
                        "voiceId": {
                          "type": "string",
                          "description": "유료 공급자의 음성 id (cartesia=카탈로그 UUID · elevenlabs=20자 영숫자 · google=Chirp 3 HD 음성명, 예 `ko-KR-Chirp3-HD-Aoede`)",
                          "example": "794f9389-aac1-45b6-b726-9d9369183238"
                        },
                        "model": {
                          "type": "string",
                          "description": "공급자 모델. **ElevenLabs 전용** — 다른 공급자에 붙이면 400입니다.",
                          "example": "eleven_v3"
                        },
                        "language": {
                          "type": "string",
                          "description": "언어 코드. 요청에서 생략하면 `ko`로 채워집니다.",
                          "example": "ko"
                        },
                        "controls": {
                          "type": "object",
                          "description": "음색 튜닝. 공급자가 지원하지 않는 값은 통화에 태우지 않고 저장만 합니다.",
                          "properties": {
                            "speed": {
                              "type": "number",
                              "example": 1
                            },
                            "volume": {
                              "type": "number",
                              "example": 1
                            }
                          }
                        }
                      },
                      "example": {
                        "provider": "clawops",
                        "language": "ko"
                      }
                    },
                    "nodes": {
                      "type": "object",
                      "description": "블럭 id → 블럭",
                      "additionalProperties": {
                        "type": "object",
                        "description": "블럭 하나. `type` 이 종류를 정하고 나머지 필드는 종류마다 다릅니다.\n\n연결 필드(`next`·`invalidNext`·`timeoutNext`·`defaultNext`·`errorNext`·`openNext`·`closedNext`)는 **다른 블럭의 id**\n또는 `null`(= 거기서 통화 종료)입니다. 존재하지 않는 id를 가리키면 400으로 거절합니다.\n\n| type | 필드 | 생략 시 기본값 |\n| --- | --- | --- |\n| `say` | `text`(1~2000), `next` | — |\n| `play` | `url`(https), `next` | — |\n| `pause` | `seconds`(1~60), `next` | — |\n| `message` | `body`(1~2000), `to?`, `subject?`, `next` | — |\n| `menu` | `prompt`, `branches`, `invalidNext`, `timeoutNext`, `maxDigits?`, `timeoutSec?`, `saveAs?` | `maxDigits:1`, `timeoutSec:5` |\n| `record` | `next`, `prompt?`, `maxLengthSec?`, `playBeep?`, `finishKey?`, `saveAs?` | `60`, `true`, `\"any\"` |\n| `dial` | `target:{kind,value}`, `next`, `callerId?`, `timeoutSec?`, `outcomes?`, `saveAs?` | `timeoutSec:30` |\n| `hangup` | `message?` | — (터미널) |\n| `setvar` | `assignments:[{name,value}]`(≤20), `next` | — |\n| `condition` | `cases:[{variable,op,value?,next}]`(≤20), `defaultNext` | — |\n| `http` | `method`, `url`, `next`, `errorNext`, `headers?`, `body?`, `saveAs:[{var,path}]?` | — |\n| `optout` | `next`, `number?`, `channel?`, `note?`, `saveAs?` | `\"{{call.customer_number}}\"`, `\"call\"` |\n| `hours` | `days`, `openNext`, `closedNext`, `exceptions?`, `saveAs?` | — |\n\n- `branches` — 누른 키(`0`~`9`, `*`, `#`) → 블럭 id. 쓰는 키만 넣는 부분 맵이며 최소 1개는 있어야 합니다.\n- `outcomes` — 전환 결과(`completed` | `no-answer` | `busy` | `failed`) → 블럭 id. 쓰는 결과만 넣는 부분 맵이며, 넣지 않은 결과는 `next` 로 갑니다. 결과는 변수 `dial_status` 에 담기고, `saveAs` 를 주면 그 이름에도 담깁니다.\n- `days` — 요일(`mon`~`sun`) → 영업 구간 `[{start,end}]`(`\"HH:MM\"`, 하루 4개까지, 겹치지 않게). 한국 시간 기준이며 `end` 부터 영업 외입니다(`\"24:00\"` 가능). 넣지 않은 요일은 하루 종일 영업 외입니다.\n- `exceptions` — 날짜 예외 `[{name,from,to,ranges?}]`(50개까지). `from`·`to` 는 `\"YYYY-MM-DD\"`(한국 날짜, 양 끝 포함)이고 기간끼리 겹치지 않아야 합니다. 그 기간에는 `days` 대신 `ranges` 로 판단하며, `ranges` 가 없거나 비어 있으면 하루 종일 영업 외입니다. hours 블럭의 `saveAs` 에는 걸린 예외의 `name` 이 담기고, 걸린 예외가 없으면 빈 값이 담깁니다.\n- `finishKey` — `any`(아무 키) | `hash`(`#`만) | `none`(키로는 안 끝남)\n- `op` — `eq` | `ne` | `contains` | `gt` | `lt` | `exists` | `empty`\n- `target.kind` — `number` | `sip`\n- 변수 이름은 `^[A-Za-z_][A-Za-z0-9_]*$`, 멘트에서의 참조는 `{{이름}}`\n\n⚠️ **스키마에 없는 필드는 400으로 거절합니다.** `timeoutSecs` 같은 한 글자 오타가 조용히\n기본값으로 도는 것을 막기 위해서이고, 어느 필드가 문제인지는 응답의 `issues[]`가 짚어 줍니다.",
                        "required": [
                          "type"
                        ],
                        "properties": {
                          "type": {
                            "type": "string",
                            "description": "블럭 종류",
                            "enum": [
                              "say",
                              "play",
                              "pause",
                              "message",
                              "menu",
                              "record",
                              "dial",
                              "hangup",
                              "setvar",
                              "condition",
                              "http",
                              "optout",
                              "hours"
                            ],
                            "example": "say"
                          }
                        },
                        "additionalProperties": true,
                        "example": {
                          "type": "menu",
                          "prompt": "상담은 1번, 예약은 2번을 눌러주세요.",
                          "branches": {
                            "1": "agent",
                            "2": "booking"
                          },
                          "invalidNext": null,
                          "timeoutNext": null,
                          "saveAs": "choice"
                        }
                      },
                      "example": {
                        "greeting": {
                          "type": "say",
                          "text": "안녕하세요, 클로옵스입니다.",
                          "next": "bye"
                        },
                        "bye": {
                          "type": "hangup",
                          "message": "이용해 주셔서 감사합니다."
                        }
                      }
                    }
                  },
                  "example": {
                    "version": 1,
                    "start": "greeting",
                    "voice": {
                      "provider": "clawops",
                      "language": "ko"
                    },
                    "nodes": {
                      "greeting": {
                        "type": "say",
                        "text": "안녕하세요, 클로옵스입니다.",
                        "next": "bye"
                      },
                      "bye": {
                        "type": "hangup",
                        "message": "이용해 주셔서 감사합니다."
                      }
                    }
                  }
                }
              ]
            }
          }
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "수정 성공",
      "content": {
        "application/json": {
          "schema": {
            "description": "콜 플로우 단건 — 요약(`CallFlowResponse`)에 블럭 구성(`graph`)을 더한 형태입니다. 단건 조회와 생성·수정 응답이 모두 이 형태이므로, 받은 `graph`를 고쳐 그대로 되돌려보내는 왕복 편집(GET → PUT/PATCH)이 성립합니다.",
            "allOf": [
              {
                "type": "object",
                "description": "콜 플로우(결정적 ARS) 요약 — 목록 응답의 항목 형태입니다. 그래프(블럭 구성)는 포함하지 않습니다: 플로우 하나가 수십 KB라 목록에 싣지 않습니다. 그래프까지 필요하면 단건 조회나 쓰기 응답(`CallFlowDetailResponse`)을 보세요.\n",
                "properties": {
                  "callFlowId": {
                    "type": "string",
                    "description": "콜 플로우 ID. createCall의 CallFlowId, 번호 라우팅의 callFlowId에 그대로 사용합니다.",
                    "example": "cmryw3ycm000001s6on0kp9a8"
                  },
                  "name": {
                    "type": "string",
                    "description": "콜 플로우 이름",
                    "example": "고객센터 ARS"
                  },
                  "phoneNumbers": {
                    "type": "array",
                    "description": "이 플로우로 인입되는 번호 목록(routingType=callflow). 아웃바운드 전용이면 빈 배열입니다.",
                    "items": {
                      "type": "string"
                    },
                    "example": [
                      "07052753864"
                    ]
                  },
                  "variables": {
                    "type": "array",
                    "description": "발신 시 createCall의 Variables로 **넣어야 하는** 변수 이름들. 그래프가 `{{이름}}`으로 참조하지만 통화 중에는 만들어지지 않는 값입니다(누른 키·HTTP 응답·녹음 결과처럼 통화가 만들어내는 변수는 제외). 채우지 않으면 그 자리는 빈 문자열로 나갑니다.",
                    "items": {
                      "type": "string"
                    },
                    "example": [
                      "name"
                    ]
                  },
                  "dateCreated": {
                    "type": "string",
                    "format": "date-time",
                    "example": "2026-07-24T09:12:33.000Z"
                  },
                  "dateUpdated": {
                    "type": "string",
                    "format": "date-time",
                    "example": "2026-07-27T01:40:11.000Z"
                  }
                }
              },
              {
                "type": "object",
                "properties": {
                  "graph": {
                    "type": "object",
                    "allOf": [
                      {
                        "type": "object",
                        "description": "콜 플로우 그래프 — **응답용**. 요청에서 생략했던 기본값 필드가 모두 채워진 형태입니다\n(`CallFlowGraphInput` 설명 참조). 이 값을 그대로 고쳐 `PUT`/`PATCH`로 돌려보낼 수 있습니다.\n\n캔버스 좌표(`ui`)는 실리지 않습니다 — 에디터 전용이라 공개 계약에서 제외합니다.",
                        "required": [
                          "version",
                          "start",
                          "nodes"
                        ],
                        "properties": {
                          "version": {
                            "type": "integer",
                            "enum": [
                              1
                            ],
                            "example": 1
                          },
                          "start": {
                            "type": "string",
                            "description": "시작 블럭 id",
                            "example": "greeting"
                          },
                          "voice": {
                            "type": "object",
                            "description": "플로우 전역 음성. 이 플로우의 모든 멘트를 이 목소리가 읽습니다. 생략하면 기본(무료) 음성입니다.\n`clawops`(한국AI통신 기본 음성)는 무료이고 `cartesia`·`elevenlabs`·`google`은 유료입니다. 다른 조직이 등록한 복제 음성 id는 403(`call_flow_voice_forbidden`)으로 거절합니다.",
                            "required": [
                              "provider"
                            ],
                            "properties": {
                              "provider": {
                                "type": "string",
                                "enum": [
                                  "clawops",
                                  "cartesia",
                                  "elevenlabs",
                                  "google"
                                ],
                                "description": "TTS 공급자",
                                "example": "clawops"
                              },
                              "voiceId": {
                                "type": "string",
                                "description": "유료 공급자의 음성 id (cartesia=카탈로그 UUID · elevenlabs=20자 영숫자 · google=Chirp 3 HD 음성명, 예 `ko-KR-Chirp3-HD-Aoede`)",
                                "example": "794f9389-aac1-45b6-b726-9d9369183238"
                              },
                              "model": {
                                "type": "string",
                                "description": "공급자 모델. **ElevenLabs 전용** — 다른 공급자에 붙이면 400입니다.",
                                "example": "eleven_v3"
                              },
                              "language": {
                                "type": "string",
                                "description": "언어 코드. 요청에서 생략하면 `ko`로 채워집니다.",
                                "example": "ko"
                              },
                              "controls": {
                                "type": "object",
                                "description": "음색 튜닝. 공급자가 지원하지 않는 값은 통화에 태우지 않고 저장만 합니다.",
                                "properties": {
                                  "speed": {
                                    "type": "number",
                                    "example": 1
                                  },
                                  "volume": {
                                    "type": "number",
                                    "example": 1
                                  }
                                }
                              }
                            },
                            "example": {
                              "provider": "clawops",
                              "language": "ko"
                            }
                          },
                          "nodes": {
                            "type": "object",
                            "description": "블럭 id → 블럭",
                            "additionalProperties": {
                              "type": "object",
                              "description": "블럭 하나. `type` 이 종류를 정하고 나머지 필드는 종류마다 다릅니다.\n\n연결 필드(`next`·`invalidNext`·`timeoutNext`·`defaultNext`·`errorNext`·`openNext`·`closedNext`)는 **다른 블럭의 id**\n또는 `null`(= 거기서 통화 종료)입니다. 존재하지 않는 id를 가리키면 400으로 거절합니다.\n\n| type | 필드 | 생략 시 기본값 |\n| --- | --- | --- |\n| `say` | `text`(1~2000), `next` | — |\n| `play` | `url`(https), `next` | — |\n| `pause` | `seconds`(1~60), `next` | — |\n| `message` | `body`(1~2000), `to?`, `subject?`, `next` | — |\n| `menu` | `prompt`, `branches`, `invalidNext`, `timeoutNext`, `maxDigits?`, `timeoutSec?`, `saveAs?` | `maxDigits:1`, `timeoutSec:5` |\n| `record` | `next`, `prompt?`, `maxLengthSec?`, `playBeep?`, `finishKey?`, `saveAs?` | `60`, `true`, `\"any\"` |\n| `dial` | `target:{kind,value}`, `next`, `callerId?`, `timeoutSec?`, `outcomes?`, `saveAs?` | `timeoutSec:30` |\n| `hangup` | `message?` | — (터미널) |\n| `setvar` | `assignments:[{name,value}]`(≤20), `next` | — |\n| `condition` | `cases:[{variable,op,value?,next}]`(≤20), `defaultNext` | — |\n| `http` | `method`, `url`, `next`, `errorNext`, `headers?`, `body?`, `saveAs:[{var,path}]?` | — |\n| `optout` | `next`, `number?`, `channel?`, `note?`, `saveAs?` | `\"{{call.customer_number}}\"`, `\"call\"` |\n| `hours` | `days`, `openNext`, `closedNext`, `exceptions?`, `saveAs?` | — |\n\n- `branches` — 누른 키(`0`~`9`, `*`, `#`) → 블럭 id. 쓰는 키만 넣는 부분 맵이며 최소 1개는 있어야 합니다.\n- `outcomes` — 전환 결과(`completed` | `no-answer` | `busy` | `failed`) → 블럭 id. 쓰는 결과만 넣는 부분 맵이며, 넣지 않은 결과는 `next` 로 갑니다. 결과는 변수 `dial_status` 에 담기고, `saveAs` 를 주면 그 이름에도 담깁니다.\n- `days` — 요일(`mon`~`sun`) → 영업 구간 `[{start,end}]`(`\"HH:MM\"`, 하루 4개까지, 겹치지 않게). 한국 시간 기준이며 `end` 부터 영업 외입니다(`\"24:00\"` 가능). 넣지 않은 요일은 하루 종일 영업 외입니다.\n- `exceptions` — 날짜 예외 `[{name,from,to,ranges?}]`(50개까지). `from`·`to` 는 `\"YYYY-MM-DD\"`(한국 날짜, 양 끝 포함)이고 기간끼리 겹치지 않아야 합니다. 그 기간에는 `days` 대신 `ranges` 로 판단하며, `ranges` 가 없거나 비어 있으면 하루 종일 영업 외입니다. hours 블럭의 `saveAs` 에는 걸린 예외의 `name` 이 담기고, 걸린 예외가 없으면 빈 값이 담깁니다.\n- `finishKey` — `any`(아무 키) | `hash`(`#`만) | `none`(키로는 안 끝남)\n- `op` — `eq` | `ne` | `contains` | `gt` | `lt` | `exists` | `empty`\n- `target.kind` — `number` | `sip`\n- 변수 이름은 `^[A-Za-z_][A-Za-z0-9_]*$`, 멘트에서의 참조는 `{{이름}}`\n\n⚠️ **스키마에 없는 필드는 400으로 거절합니다.** `timeoutSecs` 같은 한 글자 오타가 조용히\n기본값으로 도는 것을 막기 위해서이고, 어느 필드가 문제인지는 응답의 `issues[]`가 짚어 줍니다.",
                              "required": [
                                "type"
                              ],
                              "properties": {
                                "type": {
                                  "type": "string",
                                  "description": "블럭 종류",
                                  "enum": [
                                    "say",
                                    "play",
                                    "pause",
                                    "message",
                                    "menu",
                                    "record",
                                    "dial",
                                    "hangup",
                                    "setvar",
                                    "condition",
                                    "http",
                                    "optout",
                                    "hours"
                                  ],
                                  "example": "say"
                                }
                              },
                              "additionalProperties": true,
                              "example": {
                                "type": "menu",
                                "prompt": "상담은 1번, 예약은 2번을 눌러주세요.",
                                "branches": {
                                  "1": "agent",
                                  "2": "booking"
                                },
                                "invalidNext": null,
                                "timeoutNext": null,
                                "saveAs": "choice"
                              }
                            },
                            "example": {
                              "greeting": {
                                "type": "say",
                                "text": "안녕하세요, 클로옵스입니다.",
                                "next": "bye"
                              },
                              "bye": {
                                "type": "hangup",
                                "message": "이용해 주셔서 감사합니다."
                              }
                            }
                          }
                        },
                        "example": {
                          "version": 1,
                          "start": "greeting",
                          "voice": {
                            "provider": "clawops",
                            "language": "ko"
                          },
                          "nodes": {
                            "greeting": {
                              "type": "say",
                              "text": "안녕하세요, 클로옵스입니다.",
                              "next": "bye"
                            },
                            "bye": {
                              "type": "hangup",
                              "message": "이용해 주셔서 감사합니다."
                            }
                          }
                        }
                      }
                    ],
                    "nullable": true,
                    "description": "블럭 구성. 저장된 그래프가 현재 스키마로 읽히지 않으면(콘솔이 아닌 경로로 손댄 아주 오래된 플로우) `null`입니다 — 이름·연결된 번호는 그대로 유효하며, 이 경우 `PUT`으로 그래프 전체를 다시 올려 고칠 수 있습니다."
                  }
                }
              }
            ]
          }
        }
      }
    },
    "400": {
      "description": "입력 검증 실패. `code` 는 `validation`(빈 요청·이름) · `call_flow_invalid_graph`(`issues[]` 동반) · `call_flow_graph_too_large`",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "description": "콜 플로우 쓰기 에러. 공통 `{ error, code }`에 그래프 검증 실패일 때만 `issues[]`가 더 붙습니다. 그래프가 중첩 구조라 \"구성이 올바르지 않습니다\" 한 줄로는 어디를 고칠지 알 수 없기 때문입니다.",
            "properties": {
              "error": {
                "type": "string",
                "description": "사람이 읽을 수 있는 에러 메시지",
                "example": "콜 플로우 구성이 올바르지 않습니다"
              },
              "code": {
                "type": "string",
                "description": "기계 판독용 에러 코드.\n\n  - `validation`: 이름 등 기본 입력 오류\n  - `call_flow_invalid_graph`: 그래프 구조·참조 오류 (`issues[]` 동반)\n  - `call_flow_graph_too_large`: 그래프 256KB 초과\n  - `call_flow_not_found`: 계정이 소유한 플로우가 아님\n  - `call_flow_in_use`: 착신에 물린 번호가 있어 삭제 불가\n  - `call_flow_limit_reached`: 계정당 개수 상한 초과\n  - `call_flow_voice_forbidden`: 다른 조직이 등록한 복제 음성",
                "example": "call_flow_invalid_graph"
              },
              "issues": {
                "type": "array",
                "description": "그래프 검증 실패의 필드 단위 사유. `code=call_flow_invalid_graph` 일 때만 실립니다.",
                "items": {
                  "type": "object",
                  "properties": {
                    "path": {
                      "type": "string",
                      "description": "문제가 난 위치(점 표기). 참조 오류는 `nodes.<블럭id>` 입니다.",
                      "example": "nodes.ask.timeoutSecs"
                    },
                    "message": {
                      "type": "string",
                      "example": "알 수 없는 필드입니다: timeoutSecs"
                    }
                  }
                },
                "example": [
                  {
                    "path": "nodes.ask.timeoutSecs",
                    "message": "알 수 없는 필드입니다: timeoutSecs"
                  }
                ]
              }
            }
          }
        }
      }
    },
    "401": {
      "description": "인증 실패",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "description": "사람이 읽을 수 있는 에러 메시지"
              },
              "code": {
                "type": "string",
                "description": "기계 판독용 에러 코드. **분기는 이 값으로 하세요** — `error` 문구는 안내를 다듬으면서\n바뀔 수 있지만 코드는 계약입니다.\n\n같은 상태 코드라도 대응이 갈리는 경우가 있습니다. 예를 들어 발신의 `422` 는\n  - `recipient_blocked`: 수신거부 명단에 있는 번호입니다. 재시도하지 말고 명단에서\n    해제해야 발신됩니다.\n  - `quota_exceeded`: 플랜 한도를 넘었습니다. 다음 주기에 다시 발신됩니다.\n\n전화번호 정규화 관련:\n  - `INVALID_PHONE_NUMBER`: 전화번호 포맷 오류 (한국 번호 형식 아님)\n  - `UNSUPPORTED_COUNTRY`: 국제 발신 미지원 (+82 외 국가코드)",
                "example": "recipient_blocked"
              },
              "ip": {
                "type": "string",
                "description": "`overseas_ip_blocked` 일 때만 옵니다. 국외 IP 라고 판정한 요청 IP 입니다.\n해외 리전 클라우드 서버에서 호출한다면 이 값이 그 서버의 공인 IP 이며, 콘솔\n「국외 접속 예외」에서 예외를 신청할 때 이 고정 IP 를 적습니다.",
                "example": "52.79.213.41"
              },
              "processedAt": {
                "type": "string",
                "format": "date-time",
                "description": "문자 발송 요청을 거부한 서버가 오류 응답을 생성한 시각(UTC). 콘솔은 이 값을 한국 시각으로 표시합니다. 인증·요청 형식 검증 등 발송 서비스 진입 전 오류에는 없을 수 있습니다.",
                "example": "2026-10-21T04:10:34.303Z"
              },
              "blockers": {
                "type": "array",
                "description": "`certificate_not_eligible` 일 때만 옵니다. 발급할 수 없는 번호와 그 이유입니다.",
                "items": {
                  "type": "object",
                  "description": "번호 하나가 발급되지 않는(또는 안내가 필요한) 이유.\n\n| code | 뜻 | 발급 |\n|---|---|---|\n| `number_not_owned` | 이 계정이 보유한 번호가 아님(반납·재배정 포함) | 막힘 |\n| `number_not_carrier_issued` | 한국AI통신이 개통한 070·대표번호가 아님(모바일 게이트웨이, 타사 번호) | 막힘 |\n| `business_info_incomplete` | 사업자 계정인데(관리번호 제외) 상호·사업자등록번호·대표자명·사업장 주소 중 빈 칸 | 막힘 |\n| `identity_unverified` | 개인 계정 번호 또는 관리번호인데 그 번호에 본인확인 정보가 없음 | 막힘 |\n| `account_restricted` | 이용 제한 계정 | 막힘 |\n| `business_recheck_pending` | 사업자 계정인데 사업자 정보 재확인이 진행 중(통과 전) — 관리번호를 뺀 계정의 모든 번호 | 막힘 |\n| `holder_is_personal` | 개인 명의로 찍힌다는 안내 | 가능 |",
                  "properties": {
                    "number": {
                      "type": "string",
                      "description": "422 응답의 blockers 에만 있다",
                      "example": "07012340002"
                    },
                    "code": {
                      "type": "string",
                      "enum": [
                        "number_not_owned",
                        "number_not_carrier_issued",
                        "business_info_incomplete",
                        "identity_unverified",
                        "account_restricted",
                        "business_recheck_pending",
                        "holder_is_personal"
                      ],
                      "example": "number_not_carrier_issued"
                    },
                    "message": {
                      "type": "string",
                      "example": "이용증명원은 한국AI통신이 개통한 070·대표번호만 발급됩니다."
                    }
                  },
                  "required": [
                    "code",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "403": {
      "description": "다른 조직이 등록한 복제 음성 사용 (`call_flow_voice_forbidden`)",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "description": "사람이 읽을 수 있는 에러 메시지"
              },
              "code": {
                "type": "string",
                "description": "기계 판독용 에러 코드. **분기는 이 값으로 하세요** — `error` 문구는 안내를 다듬으면서\n바뀔 수 있지만 코드는 계약입니다.\n\n같은 상태 코드라도 대응이 갈리는 경우가 있습니다. 예를 들어 발신의 `422` 는\n  - `recipient_blocked`: 수신거부 명단에 있는 번호입니다. 재시도하지 말고 명단에서\n    해제해야 발신됩니다.\n  - `quota_exceeded`: 플랜 한도를 넘었습니다. 다음 주기에 다시 발신됩니다.\n\n전화번호 정규화 관련:\n  - `INVALID_PHONE_NUMBER`: 전화번호 포맷 오류 (한국 번호 형식 아님)\n  - `UNSUPPORTED_COUNTRY`: 국제 발신 미지원 (+82 외 국가코드)",
                "example": "recipient_blocked"
              },
              "ip": {
                "type": "string",
                "description": "`overseas_ip_blocked` 일 때만 옵니다. 국외 IP 라고 판정한 요청 IP 입니다.\n해외 리전 클라우드 서버에서 호출한다면 이 값이 그 서버의 공인 IP 이며, 콘솔\n「국외 접속 예외」에서 예외를 신청할 때 이 고정 IP 를 적습니다.",
                "example": "52.79.213.41"
              },
              "processedAt": {
                "type": "string",
                "format": "date-time",
                "description": "문자 발송 요청을 거부한 서버가 오류 응답을 생성한 시각(UTC). 콘솔은 이 값을 한국 시각으로 표시합니다. 인증·요청 형식 검증 등 발송 서비스 진입 전 오류에는 없을 수 있습니다.",
                "example": "2026-10-21T04:10:34.303Z"
              },
              "blockers": {
                "type": "array",
                "description": "`certificate_not_eligible` 일 때만 옵니다. 발급할 수 없는 번호와 그 이유입니다.",
                "items": {
                  "type": "object",
                  "description": "번호 하나가 발급되지 않는(또는 안내가 필요한) 이유.\n\n| code | 뜻 | 발급 |\n|---|---|---|\n| `number_not_owned` | 이 계정이 보유한 번호가 아님(반납·재배정 포함) | 막힘 |\n| `number_not_carrier_issued` | 한국AI통신이 개통한 070·대표번호가 아님(모바일 게이트웨이, 타사 번호) | 막힘 |\n| `business_info_incomplete` | 사업자 계정인데(관리번호 제외) 상호·사업자등록번호·대표자명·사업장 주소 중 빈 칸 | 막힘 |\n| `identity_unverified` | 개인 계정 번호 또는 관리번호인데 그 번호에 본인확인 정보가 없음 | 막힘 |\n| `account_restricted` | 이용 제한 계정 | 막힘 |\n| `business_recheck_pending` | 사업자 계정인데 사업자 정보 재확인이 진행 중(통과 전) — 관리번호를 뺀 계정의 모든 번호 | 막힘 |\n| `holder_is_personal` | 개인 명의로 찍힌다는 안내 | 가능 |",
                  "properties": {
                    "number": {
                      "type": "string",
                      "description": "422 응답의 blockers 에만 있다",
                      "example": "07012340002"
                    },
                    "code": {
                      "type": "string",
                      "enum": [
                        "number_not_owned",
                        "number_not_carrier_issued",
                        "business_info_incomplete",
                        "identity_unverified",
                        "account_restricted",
                        "business_recheck_pending",
                        "holder_is_personal"
                      ],
                      "example": "number_not_carrier_issued"
                    },
                    "message": {
                      "type": "string",
                      "example": "이용증명원은 한국AI통신이 개통한 070·대표번호만 발급됩니다."
                    }
                  },
                  "required": [
                    "code",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "404": {
      "description": "콜 플로우 없음 (`call_flow_not_found`)",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "description": "사람이 읽을 수 있는 에러 메시지"
              },
              "code": {
                "type": "string",
                "description": "기계 판독용 에러 코드. **분기는 이 값으로 하세요** — `error` 문구는 안내를 다듬으면서\n바뀔 수 있지만 코드는 계약입니다.\n\n같은 상태 코드라도 대응이 갈리는 경우가 있습니다. 예를 들어 발신의 `422` 는\n  - `recipient_blocked`: 수신거부 명단에 있는 번호입니다. 재시도하지 말고 명단에서\n    해제해야 발신됩니다.\n  - `quota_exceeded`: 플랜 한도를 넘었습니다. 다음 주기에 다시 발신됩니다.\n\n전화번호 정규화 관련:\n  - `INVALID_PHONE_NUMBER`: 전화번호 포맷 오류 (한국 번호 형식 아님)\n  - `UNSUPPORTED_COUNTRY`: 국제 발신 미지원 (+82 외 국가코드)",
                "example": "recipient_blocked"
              },
              "ip": {
                "type": "string",
                "description": "`overseas_ip_blocked` 일 때만 옵니다. 국외 IP 라고 판정한 요청 IP 입니다.\n해외 리전 클라우드 서버에서 호출한다면 이 값이 그 서버의 공인 IP 이며, 콘솔\n「국외 접속 예외」에서 예외를 신청할 때 이 고정 IP 를 적습니다.",
                "example": "52.79.213.41"
              },
              "processedAt": {
                "type": "string",
                "format": "date-time",
                "description": "문자 발송 요청을 거부한 서버가 오류 응답을 생성한 시각(UTC). 콘솔은 이 값을 한국 시각으로 표시합니다. 인증·요청 형식 검증 등 발송 서비스 진입 전 오류에는 없을 수 있습니다.",
                "example": "2026-10-21T04:10:34.303Z"
              },
              "blockers": {
                "type": "array",
                "description": "`certificate_not_eligible` 일 때만 옵니다. 발급할 수 없는 번호와 그 이유입니다.",
                "items": {
                  "type": "object",
                  "description": "번호 하나가 발급되지 않는(또는 안내가 필요한) 이유.\n\n| code | 뜻 | 발급 |\n|---|---|---|\n| `number_not_owned` | 이 계정이 보유한 번호가 아님(반납·재배정 포함) | 막힘 |\n| `number_not_carrier_issued` | 한국AI통신이 개통한 070·대표번호가 아님(모바일 게이트웨이, 타사 번호) | 막힘 |\n| `business_info_incomplete` | 사업자 계정인데(관리번호 제외) 상호·사업자등록번호·대표자명·사업장 주소 중 빈 칸 | 막힘 |\n| `identity_unverified` | 개인 계정 번호 또는 관리번호인데 그 번호에 본인확인 정보가 없음 | 막힘 |\n| `account_restricted` | 이용 제한 계정 | 막힘 |\n| `business_recheck_pending` | 사업자 계정인데 사업자 정보 재확인이 진행 중(통과 전) — 관리번호를 뺀 계정의 모든 번호 | 막힘 |\n| `holder_is_personal` | 개인 명의로 찍힌다는 안내 | 가능 |",
                  "properties": {
                    "number": {
                      "type": "string",
                      "description": "422 응답의 blockers 에만 있다",
                      "example": "07012340002"
                    },
                    "code": {
                      "type": "string",
                      "enum": [
                        "number_not_owned",
                        "number_not_carrier_issued",
                        "business_info_incomplete",
                        "identity_unverified",
                        "account_restricted",
                        "business_recheck_pending",
                        "holder_is_personal"
                      ],
                      "example": "number_not_carrier_issued"
                    },
                    "message": {
                      "type": "string",
                      "example": "이용증명원은 한국AI통신이 개통한 070·대표번호만 발급됩니다."
                    }
                  },
                  "required": [
                    "code",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "413": {
      "description": "요청 본문이 너무 큼 (`payload_too_large`)",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "description": "사람이 읽을 수 있는 에러 메시지"
              },
              "code": {
                "type": "string",
                "description": "기계 판독용 에러 코드. **분기는 이 값으로 하세요** — `error` 문구는 안내를 다듬으면서\n바뀔 수 있지만 코드는 계약입니다.\n\n같은 상태 코드라도 대응이 갈리는 경우가 있습니다. 예를 들어 발신의 `422` 는\n  - `recipient_blocked`: 수신거부 명단에 있는 번호입니다. 재시도하지 말고 명단에서\n    해제해야 발신됩니다.\n  - `quota_exceeded`: 플랜 한도를 넘었습니다. 다음 주기에 다시 발신됩니다.\n\n전화번호 정규화 관련:\n  - `INVALID_PHONE_NUMBER`: 전화번호 포맷 오류 (한국 번호 형식 아님)\n  - `UNSUPPORTED_COUNTRY`: 국제 발신 미지원 (+82 외 국가코드)",
                "example": "recipient_blocked"
              },
              "ip": {
                "type": "string",
                "description": "`overseas_ip_blocked` 일 때만 옵니다. 국외 IP 라고 판정한 요청 IP 입니다.\n해외 리전 클라우드 서버에서 호출한다면 이 값이 그 서버의 공인 IP 이며, 콘솔\n「국외 접속 예외」에서 예외를 신청할 때 이 고정 IP 를 적습니다.",
                "example": "52.79.213.41"
              },
              "processedAt": {
                "type": "string",
                "format": "date-time",
                "description": "문자 발송 요청을 거부한 서버가 오류 응답을 생성한 시각(UTC). 콘솔은 이 값을 한국 시각으로 표시합니다. 인증·요청 형식 검증 등 발송 서비스 진입 전 오류에는 없을 수 있습니다.",
                "example": "2026-10-21T04:10:34.303Z"
              },
              "blockers": {
                "type": "array",
                "description": "`certificate_not_eligible` 일 때만 옵니다. 발급할 수 없는 번호와 그 이유입니다.",
                "items": {
                  "type": "object",
                  "description": "번호 하나가 발급되지 않는(또는 안내가 필요한) 이유.\n\n| code | 뜻 | 발급 |\n|---|---|---|\n| `number_not_owned` | 이 계정이 보유한 번호가 아님(반납·재배정 포함) | 막힘 |\n| `number_not_carrier_issued` | 한국AI통신이 개통한 070·대표번호가 아님(모바일 게이트웨이, 타사 번호) | 막힘 |\n| `business_info_incomplete` | 사업자 계정인데(관리번호 제외) 상호·사업자등록번호·대표자명·사업장 주소 중 빈 칸 | 막힘 |\n| `identity_unverified` | 개인 계정 번호 또는 관리번호인데 그 번호에 본인확인 정보가 없음 | 막힘 |\n| `account_restricted` | 이용 제한 계정 | 막힘 |\n| `business_recheck_pending` | 사업자 계정인데 사업자 정보 재확인이 진행 중(통과 전) — 관리번호를 뺀 계정의 모든 번호 | 막힘 |\n| `holder_is_personal` | 개인 명의로 찍힌다는 안내 | 가능 |",
                  "properties": {
                    "number": {
                      "type": "string",
                      "description": "422 응답의 blockers 에만 있다",
                      "example": "07012340002"
                    },
                    "code": {
                      "type": "string",
                      "enum": [
                        "number_not_owned",
                        "number_not_carrier_issued",
                        "business_info_incomplete",
                        "identity_unverified",
                        "account_restricted",
                        "business_recheck_pending",
                        "holder_is_personal"
                      ],
                      "example": "number_not_carrier_issued"
                    },
                    "message": {
                      "type": "string",
                      "example": "이용증명원은 한국AI통신이 개통한 070·대표번호만 발급됩니다."
                    }
                  },
                  "required": [
                    "code",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    }
  }
}
```