# 사용량 집계 조회 (/docs/api/usage/getUsage)
**GET /v1/accounts/{accountId}/usage**
```json
{
  "operationId": "getUsage",
  "summary": "사용량 집계 조회",
  "description": "계정의 사용량을 **집계된 형태로** 돌려줍니다. 통화 목록을 받아 직접 합산하는 것과 다릅니다 —\n\n- **사후 정정이 반영됩니다.** 통화 시간은 종료 후에도 정정될 수 있고(`durationOverride`,\n  늦은 finalize, 운영 보정), 이 API 는 호출 시점에 원장을 다시 집계하므로 그 정정이\n  항상 반영됩니다. 목록을 긁어 직접 쌓으면 이미 받아간 건의 정정이 반영되지 않아\n  청구서와 계속 어긋납니다.\n- **청구 주기 경계가 맞습니다.** 주기 시작은 일 경계가 아니라 가입 순간의 시각입니다.\n  달력 월로 합산하면 어긋납니다.\n- **통화 목록에 없는 미터도 포함됩니다.** 문자·전사·요약은 `call_logs` 에 없습니다.\n\n## 정산에 쓰는 축\n\n`groupBy=linkId` 가 관리번호 파트너의 정산 단위입니다. 이 값은 `assignment.completed`\nwebhook 의 `LinkId` 와 같아서 파트너가 자기 사용자와 바로 조인할 수 있습니다.\n관리번호로 발급되지 않은 번호는 `linkId` 가 `null` 로 묶입니다.\n\n⚠️ **번호(`groupBy=number`)를 정산 단위로 쓰지 마십시오.** 번호는 반납 후 재배정됩니다.\n같은 번호에 이전 이용자의 사용량이 섞이지 않도록 이 API 는 배정 구간으로 잘라 집계하지만,\n수신측에서 번호를 키로 누적하면 그 구분이 사라집니다.\n\n## 단위 — 초로 돌려줍니다\n\n통화·전사·요약은 **초**입니다. 청구는 계정 합계에 올림을 한 번만 겁니다\n(`ceil((발신+전환)/60)`). 축을 쪼갠 값을 각각 올림해 더하면 합계가 청구서보다 커지므로,\n올림한 분은 `totals[].billableMinutes` 에만 담습니다.\n\n## 금액\n\n`records` 에는 **수량만** 담깁니다. 단가가 누진 구간(발신 60 → 45 → 25원)이라 축별로\n금액을 나눌 수 없습니다 — 합계에만 정의됩니다.\n\n⚠️ **현재 `amount` 와 `totals[].unitPriceHint` 는 항상 `null` 입니다.** 금액 계산은 플랜\n포함량·누진 구간·애드온을 함께 봐야 해서 청구 경로를 재사용하는 후속 작업으로 남았습니다.\n지금은 **수량**에 자사 단가를 적용해 사용하십시오.",
  "tags": [
    "Usage"
  ],
  "parameters": [
    {
      "name": "accountId",
      "in": "path",
      "required": true,
      "schema": {
        "type": "string"
      },
      "description": "계정 ID",
      "example": "AC1a2b3c4d"
    },
    {
      "name": "from",
      "in": "query",
      "required": false,
      "allowReserved": true,
      "schema": {
        "type": "string",
        "format": "date-time"
      },
      "description": "집계 시작(UTC, 포함). `to` 와 **함께** 주어야 합니다 — 한쪽만 주면 400.\n생략하면 현재 청구 주기 전체를 집계합니다.",
      "example": "2026-08-01T00:00:00Z"
    },
    {
      "name": "to",
      "in": "query",
      "required": false,
      "allowReserved": true,
      "schema": {
        "type": "string",
        "format": "date-time"
      },
      "description": "집계 끝(UTC, **미포함**). 구간 최대 92일.",
      "example": "2026-09-01T00:00:00Z"
    },
    {
      "name": "groupBy",
      "in": "query",
      "required": false,
      "description": "쪼갤 축. 반복 지정으로 조합합니다(`?groupBy=day&groupBy=linkId`).\n⚠️ 콤마 구분(`?groupBy=day,linkId`)은 받지 않습니다 — 값 검증을 유지하기 위한\n선택이라, 오타는 조용히 무시되지 않고 400 이 됩니다.\n생략하면 미터별 합계만 돌려줍니다.",
      "schema": {
        "type": "array",
        "items": {
          "type": "string",
          "enum": [
            "meter",
            "day",
            "linkId",
            "number"
          ]
        }
      },
      "example": [
        "day",
        "linkId"
      ]
    },
    {
      "name": "meter",
      "in": "query",
      "required": false,
      "description": "특정 미터만 조회. 반복 지정(`?meter=a&meter=b`). 생략 시 전체.",
      "example": [
        "outbound_seconds",
        "sms"
      ],
      "schema": {
        "type": "array",
        "items": {
          "type": "string",
          "description": "사용량 미터. 새 미터가 늘어도 응답 스키마는 바뀌지 않는다(행이 하나 늘 뿐).\n\n⚠️ 통화·전사·요약은 **초**로 돌려준다. 분 환산(올림)은 청구 정책이고 **계정 합계에만**\n적용되므로 `totals[].billableMinutes` 에만 담긴다 — 축을 쪼갠 값을 각각 올림해 더하면\n합계가 청구서보다 커진다.",
          "enum": [
            "outbound_seconds",
            "inbound_seconds",
            "transfer_seconds",
            "sms",
            "lms",
            "mms",
            "ata",
            "bms",
            "transcription_seconds",
            "summary_seconds",
            "amd_invocations"
          ],
          "example": "outbound_seconds"
        }
      }
    },
    {
      "name": "includeToday",
      "in": "query",
      "required": false,
      "schema": {
        "type": "boolean",
        "default": false
      },
      "example": false,
      "description": "진행 중인 오늘(UTC) 구간을 포함할지. 오늘 구간은 다시 조회할 때마다 값이 달라지고\n스캔 구간도 그만큼 늘어납니다. 정산은 확정 구간만 쓰는 것이 정상이므로 기본은 false 입니다."
    },
    {
      "name": "pageSize",
      "in": "query",
      "required": false,
      "schema": {
        "type": "integer",
        "minimum": 1,
        "maximum": 1000
      },
      "example": 200,
      "description": "페이지당 항목 수(기본 200, 최대 1000)."
    },
    {
      "name": "page_size",
      "in": "query",
      "required": false,
      "deprecated": true,
      "schema": {
        "type": "integer",
        "minimum": 1,
        "maximum": 1000
      },
      "example": 200,
      "description": "`pageSize` 의 별칭(하위호환). 신규 연동은 `pageSize` 를 사용하세요."
    },
    {
      "name": "limit",
      "in": "query",
      "required": false,
      "deprecated": true,
      "schema": {
        "type": "integer",
        "minimum": 1,
        "maximum": 1000
      },
      "example": 200,
      "description": "`pageSize` 의 별칭(하위호환). 신규 연동은 `pageSize` 를 사용하세요."
    },
    {
      "name": "page",
      "in": "query",
      "required": false,
      "schema": {
        "type": "integer",
        "minimum": 1,
        "default": 1
      },
      "example": 1,
      "description": "1-based 페이지 번호."
    }
  ],
  "responses": {
    "200": {
      "description": "집계 결과",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "required": [
              "period",
              "records",
              "meta",
              "totals"
            ],
            "properties": {
              "meta": {
                "type": "object",
                "description": "페이징 상태. 다른 목록 API 의 `meta` 와 같은 모양이다.\n\n`total` 은 **`records` 를 쪼갠 전체 행 수**이며, 축을 지정할수록 커진다. `totals`(사용량\n합계)와는 다른 것이다.",
                "required": [
                  "page",
                  "pageSize",
                  "total"
                ],
                "properties": {
                  "page": {
                    "type": "integer",
                    "example": 1
                  },
                  "pageSize": {
                    "type": "integer",
                    "example": 200
                  },
                  "total": {
                    "type": "integer",
                    "description": "페이징 전 `records` 전체 개수. 마지막 페이지 판단에 쓴다.",
                    "example": 39
                  }
                }
              },
              "period": {
                "type": "object",
                "description": "집계 구간. `from`/`to` 를 주지 않으면 **현재 청구 주기**가 들어간다.\n\n⚠️ 경계는 전부 **UTC** 다. 청구 주기 시작은 일 경계가 아니라 가입 순간의 시각이 그대로\n쓰인다(실측: 03:56:49, 11:21:39). 달력 월로 직접 합산하면 우리 청구서와 어긋나므로\n정산에는 이 응답의 `from`/`to` 를 그대로 쓸 것.",
                "required": [
                  "from",
                  "to",
                  "timezone",
                  "confirmedThrough",
                  "includesToday"
                ],
                "properties": {
                  "from": {
                    "type": "string",
                    "format": "date-time",
                    "example": "2026-08-03T03:56:49.081Z"
                  },
                  "to": {
                    "type": "string",
                    "format": "date-time",
                    "example": "2026-09-03T03:56:49.081Z"
                  },
                  "timezone": {
                    "type": "string",
                    "enum": [
                      "UTC"
                    ],
                    "description": "모든 경계와 `day` 값의 기준 시간대. 현재 UTC 고정이다."
                  },
                  "confirmedThrough": {
                    "type": "string",
                    "format": "date",
                    "description": "사후 정정(지연 종료 처리·운영 보정)이 반영된 확정 구간의 마지막 날(UTC).\n이후 구간은 나중에 다시 조회하면 값이 바뀔 수 있다.",
                    "example": "2026-08-20"
                  },
                  "includesToday": {
                    "type": "boolean",
                    "description": "진행 중인 오늘(UTC) 구간이 포함되었는지. `includeToday=true` 일 때만 참."
                  }
                }
              },
              "records": {
                "type": "array",
                "description": "`groupBy` 축으로 쪼갠 사용량. **수량만 담는다.**\n\n- 통화·전사·요약은 **초**다. 분 올림은 여기서 하지 않는다(축마다 올림하면 합계가 커진다).\n- 금액도 담지 않는다 — 단가가 누진 구간이라 축별로 나눌 수 없다.",
                "items": {
                  "type": "object",
                  "required": [
                    "meter",
                    "unit",
                    "quantity"
                  ],
                  "properties": {
                    "meter": {
                      "type": "string",
                      "description": "사용량 미터. 새 미터가 늘어도 응답 스키마는 바뀌지 않는다(행이 하나 늘 뿐).\n\n⚠️ 통화·전사·요약은 **초**로 돌려준다. 분 환산(올림)은 청구 정책이고 **계정 합계에만**\n적용되므로 `totals[].billableMinutes` 에만 담긴다 — 축을 쪼갠 값을 각각 올림해 더하면\n합계가 청구서보다 커진다.",
                      "enum": [
                        "outbound_seconds",
                        "inbound_seconds",
                        "transfer_seconds",
                        "sms",
                        "lms",
                        "mms",
                        "ata",
                        "bms",
                        "transcription_seconds",
                        "summary_seconds",
                        "amd_invocations"
                      ],
                      "example": "outbound_seconds"
                    },
                    "unit": {
                      "type": "string",
                      "enum": [
                        "second",
                        "count"
                      ]
                    },
                    "quantity": {
                      "type": "number",
                      "description": "초 또는 건수. 올림하지 않은 원값이다.",
                      "example": 24680
                    },
                    "day": {
                      "type": "string",
                      "format": "date",
                      "description": "`groupBy` 에 `day` 가 포함될 때만. UTC 기준."
                    },
                    "linkId": {
                      "type": "string",
                      "nullable": true,
                      "description": "'`groupBy` 에 `linkId` 가 포함될 때만. 관리번호 발급 링크 ID —\n`assignment.completed` webhook 의 `LinkId` 와 같은 값이라 파트너가 자기 사용자와\n바로 조인할 수 있다. 관리번호로 발급되지 않은 번호(직접 발급)는 `null` 로 묶인다.\n\n⛔ 이 값은 **발급 링크 주소에 포함되는 값과 동일**하다. 정산서·CSV 등 외부로\n나가는 문서에 그대로 싣지 말 것 — 자사 고객 ID 로 치환해 쓴다.'",
                      "example": "AL7f2c9a1b"
                    },
                    "phoneNumber": {
                      "type": "string",
                      "description": "'`groupBy` 에 `number` 가 포함될 때만. 저장된 형태 그대로(E.164 아님).\n⚠️ 번호는 반납 후 재배정된다 — 이 API 는 배정 구간으로 잘라 집계하지만,\n수신측에서 번호를 키로 누적하면 그 구분이 사라진다. 정산 키는 `linkId` 다.'",
                      "example": "07052753941"
                    }
                  }
                }
              },
              "totals": {
                "type": "array",
                "description": "축과 무관한 계정 전체 합계. `records` 를 미터별로 합한 값과 일치한다.",
                "items": {
                  "type": "object",
                  "required": [
                    "meter",
                    "unit",
                    "quantity"
                  ],
                  "properties": {
                    "meter": {
                      "type": "string",
                      "description": "사용량 미터. 새 미터가 늘어도 응답 스키마는 바뀌지 않는다(행이 하나 늘 뿐).\n\n⚠️ 통화·전사·요약은 **초**로 돌려준다. 분 환산(올림)은 청구 정책이고 **계정 합계에만**\n적용되므로 `totals[].billableMinutes` 에만 담긴다 — 축을 쪼갠 값을 각각 올림해 더하면\n합계가 청구서보다 커진다.",
                      "enum": [
                        "outbound_seconds",
                        "inbound_seconds",
                        "transfer_seconds",
                        "sms",
                        "lms",
                        "mms",
                        "ata",
                        "bms",
                        "transcription_seconds",
                        "summary_seconds",
                        "amd_invocations"
                      ],
                      "example": "outbound_seconds"
                    },
                    "unit": {
                      "type": "string",
                      "enum": [
                        "second",
                        "count"
                      ]
                    },
                    "quantity": {
                      "type": "number"
                    },
                    "billableMinutes": {
                      "type": "integer",
                      "nullable": true,
                      "description": "청구와 **같은 규칙**으로 올림한 분. 계정 합계에만 정의된다.\n\n- `outbound_seconds` → `ceil((발신 + 전환) / 60)`.\n  **전환 초가 여기 합산되므로** `transfer_seconds` 행은 항상 `null` 이다.\n  ⭐ `meter=outbound_seconds` 만 요청해도 전환은 이 계산에 포함된다(요청하지\n  않았다면 응답에는 나오지 않는다) — 필터에 따라 청구 분이 달라지지 않게 한다.\n- `inbound_seconds` → `ceil(수신 / 60)`\n- `transcription_seconds` · `summary_seconds` → 각각 `ceil(초 / 60)`\n- 건수 미터(`sms`·`lms`·`mms`·`amd_invocations`)는 `null`.",
                      "example": 442
                    },
                    "unitPriceHint": {
                      "type": "integer",
                      "nullable": true,
                      "description": "⚠️ **현재는 항상 `null` 이다**(금액 계산은 청구 경로 재사용이 필요해 후속 작업).\n채워지면 아래 의미를 갖는다.\n\n조회 시점에 이 미터가 놓인 **누진 구간의 단가(KRW, 부가세 포함)**. 참고값이다 —\n사용량이 늘어 다음 구간으로 넘어가면 달라지고, 플랜 포함량 안에 있으면 `null` 이다.\n이 값을 수량에 곱한 것은 청구액이 아니다.",
                      "example": 60
                    }
                  }
                }
              },
              "amount": {
                "type": "object",
                "nullable": true,
                "description": "이번 구간의 종량 예상액.\n\n⚠️ **현재는 어떤 조회에서도 항상 `null` 이다** — 금액 계산은 플랜 포함량·누진 구간·\n애드온을 함께 봐야 해서 청구 경로를 재사용하는 후속 작업으로 남았다. 아래는 채워졌을\n때의 계약이다.\n\n**다음 두 경우에는 그때에도 `null` 이다.**\n\n- `groupBy` 에 `linkId`·`number`·`day` 중 하나라도 지정한 조회 — 축별 금액은 제공하지 않는다.\n- `from`/`to` 로 청구 주기가 아닌 임의 구간을 조회한 경우.\n\n⚠️ 확정 청구액이 아니다. 정기 결제 시점의 플랜료·애드온·일할 계산은 포함되지 않는다.",
                "required": [
                  "currency",
                  "vatIncluded",
                  "meteredTotal"
                ],
                "properties": {
                  "currency": {
                    "type": "string",
                    "enum": [
                      "KRW"
                    ]
                  },
                  "vatIncluded": {
                    "type": "boolean",
                    "description": "항상 `true`. 표시가는 부가세 포함이 원칙이다."
                  },
                  "meteredTotal": {
                    "type": "integer",
                    "description": "플랜 포함량을 넘은 종량분의 합계.",
                    "example": 24700
                  },
                  "breakdown": {
                    "type": "array",
                    "description": "미터별 종량 금액. 축이 아니라 미터로만 나눈 것이라 누진 단가와 어긋나지 않는다.",
                    "items": {
                      "type": "object",
                      "required": [
                        "meter",
                        "quantity",
                        "amount"
                      ],
                      "properties": {
                        "meter": {
                          "type": "string",
                          "description": "사용량 미터. 새 미터가 늘어도 응답 스키마는 바뀌지 않는다(행이 하나 늘 뿐).\n\n⚠️ 통화·전사·요약은 **초**로 돌려준다. 분 환산(올림)은 청구 정책이고 **계정 합계에만**\n적용되므로 `totals[].billableMinutes` 에만 담긴다 — 축을 쪼갠 값을 각각 올림해 더하면\n합계가 청구서보다 커진다.",
                          "enum": [
                            "outbound_seconds",
                            "inbound_seconds",
                            "transfer_seconds",
                            "sms",
                            "lms",
                            "mms",
                            "ata",
                            "bms",
                            "transcription_seconds",
                            "summary_seconds",
                            "amd_invocations"
                          ],
                          "example": "outbound_seconds"
                        },
                        "quantity": {
                          "type": "number"
                        },
                        "amount": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "400": {
      "description": "`from`/`to` 중 하나만 지정 · 기간 역전 · 92일 초과 · `groupBy`/`meter` enum 외 값 ·\n`pageSize`/`page` 가 정수가 아님",
      "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": "다른 계정의 사용량 조회, 또는 활성 구독이 없는 계정이 `from`/`to` 없이 호출\n(기본 구간인 청구 주기를 특정할 수 없음 — `from`/`to` 를 지정해 조회).",
      "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": "계정 없음",
      "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"
                  ]
                }
              }
            }
          }
        }
      }
    }
  }
}
```