# 메시지 발송 (/docs/api/messages/sendMessage)
**POST /v1/accounts/{accountId}/messages**
```json
{
  "operationId": "sendMessage",
  "summary": "메시지 발송",
  "description": "SMS/LMS/MMS 메시지를 발송합니다. From 번호는 콘솔의 「문자 발신번호」에 등록해 승인된 번호여야 합니다.\n\nKCT 통합메시징 Agent를 통해 실제 발송되며, 발송 결과는 webhook으로 비동기 수신합니다.\n\n### 메시지 타입별 사용법\n\n**SMS** (단문, EUC-KR 90byte 이하 = 한글 45자):\n```json\n{ \"To\": \"010...\", \"From\": \"070...\", \"Body\": \"안녕하세요\" }\n```\n\n통신사 SMS 상한은 **EUC-KR 90byte** 입니다. 이를 넘겨 `Type: \"sms\"` 로 보내면\n`400 body_too_long` 입니다. **`Type` 을 생략하면 긴 본문은 LMS 로 자동 발송**되므로,\n길이가 런타임에 정해지는 경우(템플릿 치환 등)에는 생략하시는 편이 안전합니다.\n\n**LMS** (장문, 2000자 이하, 첨부 없음):\n```json\n{ \"To\": \"010...\", \"From\": \"070...\", \"Body\": \"긴 내용...\", \"Type\": \"lms\", \"Subject\": \"제목\" }\n```\n\n**MMS** (이미지 첨부, 최대 3개):\n```json\n{ \"To\": \"010...\", \"From\": \"070...\", \"Body\": \"사진\", \"Type\": \"mms\", \"MediaUrl\": [\"https://example.com/photo.jpg\"] }\n```\n\n**알림톡** (`Kakao` 를 실으면 알림톡입니다 — `Type` 은 생략하십시오):\n```json\n{\n  \"To\": \"010...\", \"From\": \"070...\",\n  \"Kakao\": {\n    \"ChannelId\": \"clx9kak0001\", \"TemplateId\": \"clx9tpl0001\",\n    \"Variables\": { \"고객명\": \"홍길동\", \"#{금액}\": \"12,000\" }\n  },\n  \"Fallback\": { \"Body\": \"주문이 접수되었습니다.\" }\n}\n```\n\n알림톡은 **승인된 템플릿으로만** 보낼 수 있고, 본문·버튼·아이템 리스트는 템플릿에 검수된\n그대로 발송됩니다. 요청에서 바꿀 수 있는 것은 `Variables` 뿐입니다 — 본문의 `#{변수}` 는\n물론 **버튼 링크와 강조 문구에 들어간 변수도 같은 목록으로 채워집니다.**\n\n발송에 실패하면(수신자가 카카오톡을 쓰지 않는 등) `Fallback` 문구가 문자로 대신 나갑니다.\n이때 문자는 **별도의 메시지 1건**으로 기록되고 문자 단가로 청구됩니다.\n\n**브랜드 메시지** (`Brand` 를 실으면 브랜드 메시지입니다 — `Type` 은 생략하십시오):\n```json\n{\n  \"To\": \"010...\", \"From\": \"070...\",\n  \"Brand\": {\n    \"ChannelId\": \"clx9kak0001\", \"TemplateId\": \"clx9bms0001\",\n    \"Variables\": { \"고객명\": \"홍길동\" }\n  }\n}\n```\n\n카카오 브랜드 메시지는 채널을 **추가한 친구**에게 나가는 광고성 메시지입니다. 알림톡과\n달리 검수가 없어 등록한 템플릿을 바로 쓸 수 있고, 대신 두 가지 제약이 있습니다.\n\n- **광고성이라 야간에 보낼 수 없습니다.** 오후 8시 50분 ~ 다음 날 오전 8시(KST)는\n  `422 kakao_brand_night_blocked` 입니다. 접수 전에 막으므로 예약되지 않습니다.\n- **대체발송이 없습니다.** 카카오톡을 쓰지 않는 수신자에게 문자로 대신 나가지 않습니다.\n  `Fallback` 을 실으면 `400 kakao_fallback_not_allowed` 입니다.\n\n`Brand.TemplateId` 는 브랜드 템플릿 목록 API 의 `data[].id` 입니다. `(광고)` 표기와\n수신거부 안내는 **카카오가 자동으로 붙이므로** 본문에 넣지 마십시오.\n\n### 자유형 — 템플릿 없이 말풍선을 직접 싣기\n\n`TemplateId` 대신 `Free` 에 말풍선을 실으면 **템플릿을 등록하지 않고** 보냅니다.\n종료된 친구톡을 대신하는 방식입니다.\n\n```json\n{\n  \"To\": \"010...\", \"From\": \"070...\",\n  \"Brand\": {\n    \"ChannelId\": \"clx9kak0001\",\n    \"Free\": {\n      \"chatBubbleType\": \"TEXT\",\n      \"content\": \"이번 주 신메뉴가 나왔어요.\",\n      \"buttons\": [{ \"name\": \"메뉴 보기\", \"linkType\": \"WL\", \"linkMobile\": \"https://example.com\" }]\n    }\n  }\n}\n```\n\n`TemplateId` 와 `Free` 는 **정확히 하나만** 실어야 합니다. 둘 다 실으면 어느 쪽으로\n나갈지 정해 드릴 수 없고, 둘 다 없으면 보낼 말풍선이 없습니다 — `400 invalid_input`.\n\n- **변수를 쓸 수 없습니다.** 값을 채워 줄 템플릿이 없어서 `#{이름}` 이 치환되지 않은\n  채 그대로 발송됩니다. 그래서 `Free` 안에 `#{…}` 가 있으면 `400` 으로 막습니다.\n  값을 채우려면 템플릿을 등록해 `TemplateId` 로 보내십시오.\n- **말풍선 규격은 유형(`chatBubbleType`)이 정합니다.** `TEXT` 는 본문만, `IMAGE`·\n  `WIDE`·`COMMERCE` 는 이미지가 필요하고, `CAROUSEL_FEED`·`CAROUSEL_COMMERCE` 는\n  카드를 2~6장 싣습니다. 어긋나면 무엇이 잘못됐는지 400 본문이 알려 줍니다.\n- **이미지가 필요한 유형**은 `POST /v1/accounts/{accountId}/kakao/brand-images` 로 먼저\n  올리고 받은 `id` 를 `imageId` 에 넣습니다. 한 번 올린 이미지는 여러 발송에 재사용합니다.\n\n⚠️ 자유형은 검수를 거치지 않습니다. **요청에 실은 버튼 링크가 그대로 톡에 렌더되므로**\n링크를 만들어 넣는 쪽에서 확인하십시오.",
  "tags": [
    "Messages"
  ],
  "parameters": [
    {
      "name": "accountId",
      "in": "path",
      "required": true,
      "schema": {
        "type": "string"
      },
      "description": "계정 ID",
      "example": "AC1a2b3c4d"
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "required": [
            "To",
            "From"
          ],
          "properties": {
            "To": {
              "type": "string",
              "description": "수신 번호. 국내 표기(`010-1234-5678`·`01012345678`)와 `+82` E.164 를 모두 받아 국내 표기로 정규화해 저장합니다. 국내 이동전화·지역번호·050X 안심번호·대표번호 (`1[5-9]XX-XXXX`)만 지원하며, 그 밖의 값(앞 0 이 빠진 `1012345678`, `+` 없는 `8210…`, 폐지된 015·018 등)은 `400 invalid_phone` 입니다.",
              "example": "01012345678"
            },
            "From": {
              "type": "string",
              "description": "발신 번호 (「문자 발신번호」에 등록해 승인된 번호). 승인되지 않은 번호는 `400 from_not_registered` 입니다.",
              "example": "07052358010"
            },
            "Body": {
              "type": "string",
              "description": "메시지 본문. 문자에는 필수입니다.\n\n**알림톡·브랜드 메시지에는 넣을 수 없습니다**(`400 kakao_body_not_allowed`) —\n본문은 템플릿이 정합니다. 대신 `Kakao.Variables`·`Brand.Variables` 를\n채우십시오. 응답의 `body` 에는 변수를 치환한 결과가 담깁니다.",
              "example": "안녕하세요"
            },
            "Type": {
              "type": "string",
              "enum": [
                "sms",
                "lms",
                "mms",
                "ata",
                "bms"
              ],
              "description": "메시지 유형. **생략하면 실린 항목에 맞춰 자동으로 고릅니다** —\n`Kakao` 가 있으면 `ata`, `Brand` 가 있으면 `bms`, `MediaUrl` 이 있으면 `mms`,\n`Subject` 가 있거나 본문이 EUC-KR 90byte(한글 45자)를 넘으면 `lms`,\n그 외에는 `sms` 입니다.\n\n명시하면 그대로 따릅니다. `sms` 로 명시한 본문이 90byte 를 넘으면\n`400 body_too_long` 입니다 — 잘린 채로 발송되지 않도록 막습니다.\n\n`ata`↔`Kakao`, `bms`↔`Brand` 는 **서로를 요구합니다.** 한쪽만 있거나 다른\n`Type` 을 명시하면 `400` 입니다. `Kakao` 와 `Brand` 를 **함께 실어도 `400`**\n입니다(`kakao_type_conflict`) — 어느 쪽으로 나갈지 정해 드릴 수 없습니다."
            },
            "Subject": {
              "type": "string",
              "description": "메시지 제목 (LMS/MMS에서 사용)",
              "example": "알림"
            },
            "MediaUrl": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uri"
              },
              "maxItems": 3,
              "description": "MMS 첨부 이미지 URL (최대 3개). jpg, jpeg, png, bmp만 지원. 장당 300KB 이하.\n\n- Type이 sms일 때는 사용 불가\n- Type이 mms이고 MediaUrl이 없으면 LMS로 전송\n- Type이 mms이고 MediaUrl이 있으면 MMS로 전송\n\n**알림톡·브랜드 메시지에는 쓸 수 없습니다**(`400 kakao_media_not_allowed`) —\n이미지는 템플릿에 넣으십시오.",
              "example": [
                "https://example.com/image.jpg"
              ]
            },
            "IdempotencyKey": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "발송 멱등키. 같은 계정에서 같은 키로 다시 요청하면 **발송하지 않고**\n1회차 결과를 그대로 돌려줍니다. 재시도·재실행 경로가 있는 호출자만 채우십시오.\n\n미지정이면 매번 발송합니다(기존 동작). 빈 문자열은 `400` 입니다 — 템플릿에서\n빈 값이 들어가 멱등이 조용히 꺼진 채 중복 발송되는 걸 막습니다.\n\n⚠️ 순차 재시도를 막는 용도입니다. 같은 키로 **동시에** 두 요청이 들어오면\n둘 다 발송될 수 있습니다.\n\n⚠️ **본문이 달라도 검사하지 않습니다.** 같은 키로 다른 To/Body 를 보내면\n새 메시지는 발송되지 않고 1회차 결과가 `201` 로 돌아옵니다. 키는 *메시지 한 건*\n단위로 만드십시오(`order-1024-접수`·`order-1024-배송` 처럼). 만료도 없습니다 —\n한 번 쓴 키는 계정 안에서 영구히 같은 결과를 돌려줍니다.",
              "example": "order-1024-retry"
            },
            "Kakao": {
              "type": "object",
              "description": "카카오 알림톡으로 보냅니다. **이 항목이 있으면 알림톡입니다** — `Type` 은 생략하십시오.\n\n발신 채널과 템플릿은 콘솔의 「카카오톡 채널」·「알림톡 템플릿」에서 연결·승인한\n것이어야 합니다. 승인되지 않았거나 휴면 상태인 템플릿은 `422` 입니다.",
              "required": [
                "ChannelId",
                "TemplateId"
              ],
              "properties": {
                "ChannelId": {
                  "type": "string",
                  "description": "채널 목록의 `data[].id`에서 받은 한국AI통신 채널 리소스 ID.",
                  "example": "clx9kak0001"
                },
                "TemplateId": {
                  "type": "string",
                  "description": "한국AI통신 콘솔의 템플릿 상세에서 확인한 한국AI통신 템플릿 리소스 ID.",
                  "example": "clx9tpl0001"
                },
                "Variables": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  },
                  "description": "템플릿 변수. 키는 `고객명` 과 `#{고객명}` 을 **모두 받습니다.**\n\n템플릿이 요구하는 변수가 빠지면 `400 kakao_variable_missing`,\n템플릿에 없는 변수를 주면 `400 kakao_variable_unknown` 입니다.\n(카카오는 이런 요청도 접수한 뒤 조용히 실패시키므로 보내기 전에 막습니다.)\n\n버튼 링크나 강조 문구에 들어간 변수도 **같은 목록**에 포함됩니다.",
                  "example": {
                    "고객명": "홍길동",
                    "#{금액}": "12,000"
                  }
                }
              }
            },
            "Brand": {
              "type": "object",
              "additionalProperties": false,
              "description": "카카오 브랜드 메시지로 보냅니다. **이 항목이 있으면 브랜드 메시지입니다** —\n`Type` 은 생략하십시오.\n\n채널을 **추가한 친구**에게만 나갑니다. 발신 채널은 콘솔의 「카카오톡 채널」에서\n연결한 것이어야 합니다.\n\n보내는 방식이 둘입니다 — **템플릿형**(`TemplateId`)과 **자유형**(`Free`).\n자세한 설명과 예시는 이 엔드포인트 설명을 보십시오.",
              "required": [
                "ChannelId"
              ],
              "properties": {
                "ChannelId": {
                  "type": "string",
                  "description": "채널 목록의 `data[].id`에서 받은 한국AI통신 채널 리소스 ID.",
                  "example": "clx9kak0001"
                },
                "TemplateId": {
                  "type": "string",
                  "description": "브랜드 템플릿 목록 API 의 `data[].id` (한국AI통신 리소스 ID).",
                  "example": "clx9bms0001"
                },
                "Variables": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  },
                  "description": "템플릿 변수. 키는 `고객명` 과 `#{고객명}` 을 **모두 받습니다.**\n\n템플릿이 요구하는 변수가 빠지면 `400 kakao_variable_missing`,\n템플릿에 없는 변수를 주면 `400 kakao_variable_unknown` 입니다.\n\n채워야 할 이름은 브랜드 템플릿 목록 응답의 `variables` 가 알려줍니다.\n\n⛔ **자유형(`Free`)과 함께 쓸 수 없습니다.** 값을 채워 줄 템플릿이 없습니다.",
                  "example": {
                    "고객명": "홍길동"
                  }
                },
                "Free": {
                  "type": "object",
                  "additionalProperties": true,
                  "description": "**템플릿 없이 보낼 말풍선 몸통.** `TemplateId` 대신 씁니다. 규격은\n`chatBubbleType` 이 정하고, 어긋나면 무엇이 잘못됐는지 400 본문이 알려\n줍니다.\n\n⚠️ **여기 안쪽은 모르는 키를 막지 않습니다.** 바깥 `Brand` 는 오타 키를\n400 으로 되돌려주지만, 이 안에서는 규격에 없는 칸이 조용히 무시됩니다 —\n말풍선 규격의 정본이 하나뿐이어야 해서 그렇습니다. 오타는 \"본문이 비었다\"\n같은 형태로 드러납니다.",
                  "example": {
                    "chatBubbleType": "TEXT",
                    "content": "이번 주 신메뉴가 나왔어요.",
                    "buttons": [
                      {
                        "name": "메뉴 보기",
                        "linkType": "WL",
                        "linkMobile": "https://example.com"
                      }
                    ]
                  }
                }
              }
            },
            "Fallback": {
              "type": "object",
              "description": "알림톡이 발송 실패했을 때 대신 나갈 문자. 생략하면 **템플릿 본문을 그대로** 문자로 보냅니다.\n\n대체 발송된 문자는 별도의 메시지 1건으로 기록되며 문자 단가로 청구됩니다.\n\n**브랜드 메시지에는 쓸 수 없습니다**(`400 kakao_fallback_not_allowed`) —\n대체발송 자체가 없습니다.",
              "properties": {
                "Body": {
                  "type": "string",
                  "description": "문자 본문. 생략하면 알림톡 본문(변수 치환 결과)을 씁니다.",
                  "example": "주문이 접수되었습니다."
                },
                "Subject": {
                  "type": "string",
                  "description": "문자 제목(LMS/MMS)."
                },
                "Type": {
                  "type": "string",
                  "enum": [
                    "sms",
                    "lms",
                    "mms"
                  ],
                  "description": "생략하면 본문 길이에 맞춰 자동으로 고릅니다."
                },
                "Disabled": {
                  "type": "boolean",
                  "default": false,
                  "description": "`true` 면 알림톡이 실패해도 문자를 보내지 않습니다.\n발송 실패가 그대로 실패로 남습니다."
                }
              }
            }
          }
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "발송 요청 성공",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "messageId": {
                "type": "string",
                "example": "MGabcdef1234567890"
              },
              "status": {
                "type": "string",
                "enum": [
                  "queued",
                  "sent",
                  "failed",
                  "received"
                ]
              },
              "type": {
                "type": "string",
                "enum": [
                  "sms",
                  "lms",
                  "mms",
                  "ata",
                  "bms"
                ],
                "description": "`ata` 는 카카오 알림톡, `bms` 는 카카오 브랜드 메시지입니다. 본문(`body`)은 템플릿에\n변수를 치환한 결과이며, 버튼·아이템 리스트·강조 문구는 템플릿에 담긴 대로 발송되어\n이 값에는 담기지 않습니다."
              },
              "subject": {
                "type": "string",
                "nullable": true,
                "description": "메시지 제목 (LMS/MMS)"
              },
              "to": {
                "type": "string",
                "example": "01012345678"
              },
              "from": {
                "type": "string",
                "example": "07052358010"
              },
              "body": {
                "type": "string",
                "example": "안녕하세요",
                "nullable": true
              },
              "numMedia": {
                "type": "integer",
                "example": 0,
                "description": "첨부 이미지 수"
              },
              "mediaUrl": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "example": [],
                "description": "첨부 이미지 URL 목록"
              },
              "direction": {
                "type": "string",
                "enum": [
                  "outbound",
                  "inbound"
                ]
              },
              "accountId": {
                "type": "string",
                "example": "AC1a2b3c4d"
              },
              "dateCreated": {
                "type": "string",
                "format": "date-time"
              },
              "dateUpdated": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              }
            }
          }
        }
      }
    },
    "400": {
      "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"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "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": "접근 권한 없음, 활성 구독 없음, 발송이 제한된 계정, 또는 국외 IP 에서의 문자 발송 (`overseas_ip_blocked`). 문자는 국내 IP 에서만 보낼 수 있습니다. 국내 여부는 IP 위치로 판정하며, 국내 리전(서울 등) 클라우드 서버는 국내로 봅니다. 대체발송이 켜진 알림톡도 문자로 보아 같은 판정을 받습니다 (`Fallback.Disabled` 를 `true` 로 보내면 알림톡만 발송됩니다). 국외 IP 에서 보내려면 해당 IP 가 국외 접속 예외로 승인되어 있어야 합니다. `overseas_ip_blocked` 응답의 `ip` 는 판정한 요청 IP 이며, 해외 서버는 이 고정 IP 로 콘솔 「국외 접속 예외」에서 신청합니다.",
      "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"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "422": {
      "description": "할당량 초과, 플랜 미지원 타입, 수신거부 명단에 등록된 번호 (`recipient_blocked`), 또는 차단정보(금칙어·URL·전화번호 규칙)에 걸린 문자 (`content_blocked`). `content_blocked` 응답에는 걸린 규칙 이름(`rule`)과 차단정보(`matched`: `type`·`value` 목록)가 함께 옵니다. 알림톡은 대체발송 문구가 걸리면 요청 전체가 거부됩니다. 수신거부·차단정보로 막힌 문자는 메시지 이력에 남지 않습니다 — 발송 자체가 일어나지 않기 때문입니다.",
      "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"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "429": {
      "description": "호출 빈도 제한 (`rate_limited`). 문자 발송(SMS·LMS·MMS, 문자 대체발송을 켠 알림톡)은 계정당 분당 120건, API 키당 분당 120건까지입니다. 한 계정이 여러 API 키·경로로 보내도 계정 기준으로 합산됩니다. `Retry-After` 헤더의 초가 지난 뒤 다시 보내세요. 거절된 요청은 발송되지 않고 메시지 이력에도 남지 않습니다.",
      "headers": {
        "Retry-After": {
          "description": "다시 시도할 수 있을 때까지 남은 초",
          "schema": {
            "type": "integer"
          }
        }
      },
      "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"
                  ]
                }
              }
            }
          }
        }
      }
    }
  }
}
```