# Admin API 명세서 — Subscriptions ## 공통 사항 | 항목 | 값 | |---|---| | Base URL | `https://{apim서비스명}.azure-api.net/admin` | | 인증 | `Ocp-Apim-Subscription-Key` 헤더 (Admin Product 전용 구독키) | | Content-Type | `application/json` | | 대상 리소스 | Azure APIM 자신의 `subscriptions` (ARM 리소스를 그대로 관리/노출) | 모든 오퍼레이션은 ARM 호출이 실패하면 **ARM이 반환한 상태 코드와 본문을 그대로 전달**합니다 (자체적으로 오류를 감추지 않음). --- ## 1. `GET /subscriptions` — 구독 목록 조회 | 항목 | 내용 | |---|---| | Method | `GET` | | Path | `/subscriptions` | | Query Parameter | `view` (선택, `raw` \| `filtered` \| `summary`, 기본값 `filtered`) | ### `view` 값별 차이 | 값 | 의미 | |---|---| | `raw` | ARM 원본 그대로 (id, type, ownerId 등 전부 포함) | | `filtered` (기본값) | 불필요한 필드(`id`, `type`, `properties.ownerId`) 제거, `scope`는 마지막 세그먼트만 표시 | | `summary` | `name`, `state`, `scope`만 담은 최소 형태 | > 구독키(primary/secondary key) 조회는 이 목록 API와 별개로 처리할 예정입니다 (추후 별도 오퍼레이션으로 추가). ### 응답 (200) `view=raw`: ARM이 반환하는 형태 그대로 (`id`, `type`, `properties.ownerId` 포함) `view=filtered`: ```json { "value": [ { "name": "svc-0001-example", "properties": { "displayName": "예시 파트너 구독", "state": "active", "scope": "starter", "createdDate": "2026-01-10T02:00:00Z", "expirationDate": "2026-12-31T00:00:00Z" } } ], "nextLink": "..." } ``` `view=summary`: `name`, `properties.state`, `properties.scope`만 포함 ```json { "value": [ { "name": "svc-0001-example", "state": "active", "scope": "starter" } ] } ``` ### 오류 | 상태 코드 | 원인 | |---|---| | 4xx/5xx | ARM 원본 오류 그대로 전달 | --- ## 2. `PATCH /subscriptions/{id}` — 구독 항목 수정 | 항목 | 내용 | |---|---| | Method | `PATCH` | | Path | `/subscriptions/{id}` | | `{id}` | 수정할 구독의 이름(고유 식별자) | ### 요청 body — 변경하고 싶은 필드만 포함 (부분 업데이트) | 필드 | 타입 | 수정 가능 여부 | |---|---|---| | `scope` | string | ✅ | | `displayName` | string | ✅ | | `state` | string (enum, 아래 표 참고) | ✅ | | `stateComment` | string | ✅ | | `allowTracing` | boolean | ✅ | | `expirationDate` | string (ISO 8601) | ✅ (기록용, 자동 차단 아님) | | `createdDate`, `startDate`, `endDate`, `notificationDate` | - | ❌ 전송해도 무시됨 | 허용된 필드가 하나도 없으면 ARM에 요청을 보내지 않고 **400**을 즉시 반환합니다. ### `state` 값 상세 | 값 | 의미 | 이 상태에서 구독키로 API 호출 가능? | 비고 | |---|---|---|---| | `active` | 정상 활성 | ✅ 가능 | 실제 서비스 중인 정상 상태 | | `suspended` | 관리자가 일시 차단 | ❌ 불가 | 결제 연체, 이상 트래픽 등으로 즉시 차단할 때 사용. 나중에 `active`로 되돌리면 재사용 가능 | | `submitted` | 승인 대기 중 | ❌ 불가 | 개발자가 구독을 요청했지만 아직 승인/거절되지 않은 상태 (Product의 "승인 필요" 옵션 사용 시 발생) | | `rejected` | 승인 거부됨 | ❌ 불가 | 관리자가 `submitted` 요청을 거부한 상태. 이때 `stateComment`에 거부 사유를 남기는 것이 일반적 | | `cancelled` | 취소됨 | ❌ 불가 | 구독자 또는 관리자가 명시적으로 취소. 되돌리려면 다시 `active`로 변경 필요 | | `expired` | 만료 처리됨 | ❌ 불가 | `expirationDate`가 지났다고 **자동으로** 이 상태가 되는 것이 아니라, 누군가(관리자 또는 별도 스케줄러)가 **직접 이 값으로 바꿔야** 실제로 반영됨 | **중요:** `expirationDate`를 설정하는 것과 `state`를 `expired`로 바꾸는 것은 **완전히 별개의 동작**입니다. `expirationDate`는 기록/알림 목적일 뿐이라 그 날짜가 지나도 APIM이 자동으로 키를 막지 않습니다. 실제로 특정 날짜에 접근을 차단하려면, 그 날짜에 맞춰 이 `PATCH` API를 호출해서 `state`를 `suspended` 또는 `expired`로 바꿔주는 **별도의 스케줄러(Azure Function 등)**가 필요합니다. ### 응답 | 상태 코드 | 원인 | |---|---| | 200 | 수정 성공, 수정된 구독 정보 반환 (GET과 동일한 형태, `view` 파라미터 지원) | | 400 | 허용된 필드가 하나도 없음 | | 4xx/5xx (그 외) | ARM 원본 오류 그대로 전달 (예: 존재하지 않는 `{id}` → 404) | ### 예시 ``` PATCH /admin/subscriptions/svc-0001-example { "state": "suspended", "stateComment": "결제 연체로 임시 차단" } ``` --- ## 3. `PUT /subscriptions/{id}` — 구독 신규 등록 | 항목 | 내용 | |---|---| | Method | `PUT` | | Path | `/subscriptions/{id}` | | `{id}` (= `name`) | **네이밍 규칙**: `svc-####-영문명` (숫자 4자리 + 하이픈 + 영문으로 시작하는 이름) — 정규식: `^svc-\d{4}-[a-zA-Z][a-zA-Z0-9-]*$` | ### 요청 body | 필드 | 필수 여부 | 설명 | |---|---|---| | `displayName` | ✅ 필수 | 구독 표시 이름 | | `expirationDate` | ✅ 필수 | ISO 8601 형식 (예: `2026-12-31T00:00:00Z`). 유효한 날짜 형식인지 검증됨 | | `scope` | 선택 | 비어있으면 스코프 없는 standalone 구독으로 생성. 값이 있으면 **실제로 존재하는 리소스인지 ARM에 확인 후 생성** (`/products/{id}`, `/apis/{id}` 등). `/apis`(전체)는 항상 존재하는 것으로 간주해 검증을 건너뜀 | | `state` | 선택 | 기본값 `active`. 지정 시 6개 enum 값 중 하나여야 함 (위 표 참고) | | `primaryKey` | 선택 | 지정 시 해당 값을 그대로 키로 사용. 미지정 시 ARM이 자동 생성 | | `secondaryKey` | 선택 | 위와 동일 | ### 검증 순서 1. **`{id}` 네이밍 규칙** 검사 → 실패 시 즉시 400 2. **`displayName` 존재 여부** → 없으면 400 3. **`expirationDate` 존재 및 형식** → 없거나 파싱 불가하면 400 4. **`state` 값 유효성** (지정한 경우) → 6개 enum에 없으면 400 5. 위 1~4가 모두 통과해야 **`scope` 존재 여부 확인** 단계로 진행 (지정된 경우만, ARM에 실제 조회) 6. 모든 검증 통과 시 ARM에 실제 생성 요청 여러 항목이 동시에 잘못된 경우, 1~4번 검증은 **한 번에 모아서** `errors` 배열로 반환합니다. ### 응답 | 상태 코드 | 원인 | |---|---| | 200 또는 201 | 생성 성공 (ARM이 반환하는 코드 그대로), 생성된 구독 정보 반환 | | 400 | 입력값 검증 실패 (`errors` 배열) 또는 `scope`가 존재하지 않음 | | 4xx/5xx (그 외) | ARM 원본 오류 그대로 전달 (예: 이미 존재하는 `{id}`로 재요청 시 ARM의 처리 결과) | ### 예시 **성공 요청:** ``` PUT /admin/subscriptions/svc-0001-example { "displayName": "예시 파트너 구독", "scope": "/products/starter", "expirationDate": "2026-12-31T00:00:00Z" } ``` **검증 실패 응답 예시 (400):** ```json { "errors": [ "name must match pattern svc-####-EnglishName (e.g. svc-0001-example)", "expirationDate is required" ] } ``` **존재하지 않는 scope 지정 시 (400):** ```json { "error": "scope does not exist: /products/no-such-product" } ```