{
  "openapi": "3.1.0",
  "info": {
    "title": "PINCOBIZ (핑코비즈) Open API v1",
    "version": "1.0.0",
    "summary": "핑코비즈 기업 메시징(SMS·LMS·MMS) 공식 REST Open API v1",
    "description": "단문(SMS), 장문(LMS), 포토문자(MMS) 즉시·예약 발송, 사전 검증(Dry-Run), 발송 결과 및 자동 환급 정산 조회를 지원하는 표준 REST API입니다. 분당 호출 한도: 발송 300회/분, 조회·검증·취소 30회/분, MMS 업로드 및 헬스체크 10회/분.",
    "contact": {
      "name": "PINCOBIZ Developers",
      "url": "https://sms.pinco.kr/developers"
    }
  },
  "servers": [
    {
      "url": "https://sms-api.pinco.kr",
      "description": "운영 API 서버 (대한민국 리전)"
    }
  ],
  "tags": [
    { "name": "Status & Account", "description": "서버 상태, 발송 가능 상품 권한, 잔액·단가, 사전등록 발신번호 조회" },
    { "name": "Messaging & Campaigns", "description": "사전 검증(Dry-Run), SMS·LMS·MMS 발송 접수, 캠페인 결과 조회, 예약 취소, MMS 이미지 업로드" }
  ],
  "components": {
    "securitySchemes": {
      "ApiTokenHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Pinco-Api-Token",
        "description": "콘솔(https://sms.pinco.kr/console/openapi)에서 발급받은 pco_... 토큰"
      },
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Authorization: Bearer pco_... 형식 전달 지원"
      }
    },
    "headers": {
      "X-RateLimit-Limit": {
        "description": "현재 엔드포인트의 1분 최대 허용 호출 수",
        "schema": { "type": "integer", "example": 30 }
      },
      "X-RateLimit-Remaining": {
        "description": "현재 1분 윈도우 내 남은 호출 가능 수",
        "schema": { "type": "integer", "example": 29 }
      },
      "X-RateLimit-Reset": {
        "description": "윈도우 초기화 시각 (Unix Epoch 초)",
        "schema": { "type": "integer", "example": 1793716560 }
      }
    },
    "schemas": {
      "ErrorResponse": {
        "type": "object",
        "required": ["status", "error", "message", "timestamp"],
        "properties": {
          "status": { "type": "boolean", "const": false },
          "error": {
            "type": "string",
            "enum": [
              "INVALID_REQUEST",
              "AD_POLICY_VIOLATION",
              "BLOCKED_WORD",
              "SCHEDULE_TOO_SOON",
              "INVALID_TOKEN",
              "SEND_RESTRICTED",
              "NOT_FOUND",
              "INSUFFICIENT_BALANCE",
              "CANCEL_DENIED",
              "RATE_LIMITED",
              "QUOTA_EXCEEDED",
              "INTERNAL_ERROR",
              "SERVICE_UNAVAILABLE"
            ]
          },
          "message": { "type": "string", "example": "유효하지 않거나 폐기된 API 토큰입니다." },
          "timestamp": { "type": "integer", "example": 1793716522000 }
        }
      },
      "RecipientInput": {
        "type": "object",
        "required": ["phoneNumber"],
        "properties": {
          "phoneNumber": { "type": "string", "description": "수신자 휴대전화 번호 (하이픈 무관)", "example": "01012345678" },
          "recipientName": { "type": "string", "description": "본문 {이름} 개인화 치환용 이름 (선택)", "example": "홍길동" }
        }
      },
      "MessageSendRequest": {
        "type": "object",
        "required": ["senderNumber", "messageType", "body", "recipients"],
        "properties": {
          "senderNumber": { "type": "string", "description": "사전등록 승인 완료 발신번호", "example": "15334005" },
          "messageType": { "type": "string", "enum": ["SMS", "LMS", "MMS"], "description": "SMS(≤90B), LMS(≤2,000B), MMS(≤2,000B + 이미지)" },
          "purpose": { "type": "string", "enum": ["INFO", "AD", "ELECTION"], "default": "INFO", "description": "발송 목적 (AD 지정 시 08:00~20:45 및 광고 표기 검증)" },
          "subject": { "type": "string", "description": "LMS·MMS 제목 (SMS 제외)", "example": "안내 말씀드립니다" },
          "body": { "type": "string", "description": "메시지 본문 (EUC-KR 바이트 기준)", "example": "[핑코비즈] {이름}님 인증번호는 [482910]입니다." },
          "companyName": { "type": "string", "description": "광고성 문자 상단 표기 업체명" },
          "optOutNumber": { "type": "string", "description": "080 무료수신거부 번호 (생략 시 기본 080 번호 적용)" },
          "autoFormatAd": { "type": "boolean", "default": false, "description": "true 설정 시 (광고) 및 무료수신거부 080 문구 자동 부착" },
          "scheduled": { "type": "boolean", "default": false, "description": "예약 발송 여부" },
          "scheduledAt": { "type": "string", "description": "예약 시각 (KST YYYY-MM-DD HH:mm, 최소 30분 이후)", "example": "2026-12-01 10:00" },
          "recipients": {
            "type": "array",
            "minItems": 1,
            "items": { "$ref": "#/components/schemas/RecipientInput" }
          },
          "attachmentFileIds": {
            "type": "array",
            "maxItems": 3,
            "items": { "type": "integer" },
            "description": "POST /api/open/v1/mms/images 로 발급받은 이미지 fileId 목록"
          }
        }
      },
      "MessagePreviewRequest": {
        "type": "object",
        "required": ["senderNumber", "messageType", "body"],
        "properties": {
          "senderNumber": { "type": "string", "example": "15334005" },
          "messageType": { "type": "string", "enum": ["SMS", "LMS", "MMS"] },
          "purpose": { "type": "string", "enum": ["INFO", "AD", "ELECTION"], "default": "INFO" },
          "subject": { "type": "string" },
          "body": { "type": "string" },
          "companyName": { "type": "string" },
          "optOutNumber": { "type": "string" },
          "autoFormatAd": { "type": "boolean", "default": false },
          "scheduled": { "type": "boolean", "default": false },
          "scheduledAt": { "type": "string" },
          "recipients": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/RecipientInput" }
          },
          "attachmentFileIds": {
            "type": "array",
            "items": { "type": "integer" }
          }
        }
      }
    }
  },
  "security": [
    { "ApiTokenHeader": [] },
    { "BearerAuth": [] }
  ],
  "paths": {
    "/api/open/v1/health": {
      "get": {
        "operationId": "checkHealth",
        "tags": ["Status & Account"],
        "summary": "서버 상태 체크 (분당 10회 / IP)",
        "security": [],
        "responses": {
          "200": { "description": "정상 가동 응답" },
          "429": { "description": "호출 한도 초과", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    },
    "/api/open/v1/products/status": {
      "get": {
        "operationId": "getProductsStatus",
        "tags": ["Status & Account"],
        "summary": "발송 가능 상품 · 목적 권한 및 일일 한도 조회 (분당 30회 / 토큰)",
        "parameters": [
          {
            "name": "product",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "enum": ["SMS", "LMS", "MMS"] },
            "description": "특정 상품만 조회할 때 지정"
          }
        ],
        "responses": {
          "200": { "description": "상품별 가용 상태 반환" },
          "401": { "description": "토큰 인증 실패", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "429": { "description": "호출 한도 초과", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    },
    "/api/open/v1/balance": {
      "get": {
        "operationId": "getBalanceAndRates",
        "tags": ["Status & Account"],
        "summary": "예치금 잔액 및 상품별 적용 단가 조회 (분당 30회 / 토큰)",
        "responses": {
          "200": { "description": "잔액 및 SMS/LMS/MMS 건당 단가 반환" },
          "401": { "description": "토큰 인증 실패", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "429": { "description": "호출 한도 초과", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    },
    "/api/open/v1/sender-numbers": {
      "get": {
        "operationId": "listSenderNumbers",
        "tags": ["Status & Account"],
        "summary": "사전등록 발신번호 목록 조회 (분당 30회 / 토큰)",
        "parameters": [
          { "name": "sendableOnly", "in": "query", "required": false, "schema": { "type": "boolean" } },
          { "name": "type", "in": "query", "required": false, "schema": { "type": "string", "enum": ["LANDLINE", "MOBILE"] } }
        ],
        "responses": {
          "200": { "description": "발신번호 배열 반환" },
          "401": { "description": "토큰 인증 실패", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "429": { "description": "호출 한도 초과", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    },
    "/api/open/v1/messages/preview": {
      "post": {
        "operationId": "previewMessage",
        "tags": ["Messaging & Campaigns"],
        "summary": "발송 사전 검증 및 예상 비용 계산 Dry-Run (분당 30회 / 토큰)",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MessagePreviewRequest" } } }
        },
        "responses": {
          "200": { "description": "바이트 길이, 광고 포맷팅 본문, 예상 차감액 반환" },
          "400": { "description": "검증 실패", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "429": { "description": "호출 한도 초과", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    },
    "/api/open/v1/messages/send": {
      "post": {
        "operationId": "sendMessage",
        "tags": ["Messaging & Campaigns"],
        "summary": "SMS · LMS · MMS 즉시/예약 발송 접수 (분당 300회 / 토큰)",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MessageSendRequest" } } }
        },
        "responses": {
          "200": { "description": "발송 접수 성공 (campaignId, 차감액, 잔여 예치금 반환)" },
          "400": { "description": "입력·정책 오류", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "409": { "description": "잔액 부족", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "429": { "description": "호출 한도 또는 일일 쿼터 초과", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    },
    "/api/open/v1/campaigns/{campaignId}": {
      "get": {
        "operationId": "getCampaignStatus",
        "tags": ["Messaging & Campaigns"],
        "summary": "캠페인 진행 상태 · 성공/실패 건수 · 환급액 조회 (분당 30회 / 토큰)",
        "parameters": [
          { "name": "campaignId", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": { "description": "캠페인 상태 및 정산 리포트 반환" },
          "404": { "description": "캠페인 미존재", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "429": { "description": "호출 한도 초과", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    },
    "/api/open/v1/campaigns/{campaignId}/cancel": {
      "post": {
        "operationId": "cancelScheduledCampaign",
        "tags": ["Messaging & Campaigns"],
        "summary": "발송 5분 전 예약 캠페인 취소 및 즉시 환급 (분당 30회 / 토큰)",
        "parameters": [
          { "name": "campaignId", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": { "reason": { "type": "string", "maxLength": 100 } }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "취소 및 전액 환급 완료" },
          "409": { "description": "취소 가능 시각 경과 또는 기발송 캠페인", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "429": { "description": "호출 한도 초과", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    },
    "/api/open/v1/mms/images": {
      "post": {
        "operationId": "uploadMmsImage",
        "tags": ["Messaging & Campaigns"],
        "summary": "MMS 첨부용 JPG 이미지 업로드 (분당 10회 / 토큰)",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["file"],
                "properties": {
                  "file": { "type": "string", "format": "binary", "description": "JPG/JPEG 파일 (300KB 이하 권장)" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "업로드 성공 (fileId 반환)" },
          "400": { "description": "포맷/용량 오류", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "429": { "description": "호출 한도 초과", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    }
  }
}
