{
  "openapi": "3.1.0",
  "info": {
    "title": "Kits Push API",
    "version": "v1a",
    "description": "contracts/ 에서 생성된다. 직접 고치지 마세요.\n\nPOST /v1/messages 요청은 message-fields.yaml에서, 그 밖의 exact 요청·응답은 endpoints.yaml에서 생성한다. 나머지 엔드포인트는 경로·권한·오류 응답만 선언한다. 틀린 스키마보다 없는 스키마가 낫다."
  },
  "servers": [
    {
      "url": "https://api.kits.example"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "components": {
    "securitySchemes": {
      "projectApiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "gsk_live_… / gsk_test_… server-only credential for an immutable allowlist of 2–8 apps in one project environment. Accepted only by /v1/project-messages; cannot enroll native SDKs or call app-scoped APIs. Existing sk_/pk_ credentials are unchanged."
      },
      "notificationReceipt": {
        "type": "http",
        "scheme": "bearer",
        "description": "Short-lived, server-authenticated capability for RECEIVED on one dispatched notification only. Never an API key or device token. Read only inside an explicitly configured iOS NSE; never log or forward it to a payload-provided origin."
      },
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Authorization: Bearer sk_live_… · sk_test_… (서버) 또는 pk_live_… · pk_test_… (앱). 키 하나가 앱 환경 하나에 고정된다(REQ-NFR-006).\n\n`pk_` 는 앱 번들에 넣어도 되는 키다. 가질 수 있는 권한이 `installations:enroll` 하나뿐이고 DB 가 그것을 CHECK 으로 못박는다.\n\n각 작업이 요구하는 권한은 그 작업의 `security` 에 있다. 권한 이름은 키 안에 들어 있지 않고 서버가 자격증명에서 조회한다.\n\n| 범위 | 무엇을 할 수 있는가 |\n|---|---|\n| `installations:enroll` | 설치본을 하나 새로 만든다 — 앱에 넣는 공개 키(pk_)가 갖는 유일한 권한 |\n| `messages:send` | 발송을 만든다 |\n| `broadcasts:send` | 이 앱 환경의 모두에게 보낸다 |\n| `messages:read` | 발송 상태와 수신 가능 여부를 읽는다 |\n| `identity:read` | 앱 사용자를 공개 id 나 고객사 식별자로 조회한다 |\n| `identity:write` | 설치·앱 사용자·구독과 푸시 주소를 만들고 바꾼다 |\n| `consent:read` | 마케팅 동의 현황을 읽는다 |\n| `consent:write` | 알림 권한 관측·수신 설정·마케팅 동의를 기록한다 |\n| `events:read` | 등록된 이벤트 정의를 읽는다 |\n| `events:write` | 이벤트 정의를 등록하고 이벤트를 적재한다 |\n| `attributes:read` | 등록된 속성 정의를 읽는다 |\n| `attributes:write` | 속성 정의를 등록하고 사용자 속성을 넣는다 |\n| `provider-connections:read` | 연결한 공급자와 그 상태를 읽는다 |\n| `provider-connections:write` | 공급자 연결을 만들고 검증한다 |\n| `credentials:rotate` | 공급자 자격증명을 회전한다. 틀리면 그 테넌트의 푸시가 멈춘다 |\n| `dispatch-control:read` | 발송이 멈춰 있는지와 현재 제어 세대를 읽는다 |\n| `dispatch-control:write` | 발송을 멈추고 재개하며, 결과불명 발송을 수동 종결한다 |"
      },
      "deviceToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "Authorization: Bearer it_… — 설치본을 등록할 때 응답으로 받는 값이다.\n\n이 토큰은 **그 기기 자신에 대해서만** 쓸 수 있다. 다른 설치본의 id 를 주면 404 다 — 없는 설치본과 남의 설치본이 같은 답을 받는다.\n\n등록을 부를 때마다 새 값이 나오고 옛 값은 즉시 죽는다. 401 을 받으면 다시 등록해서 새 토큰을 받고 한 번 재시도하면 된다.\n\nAPI 키를 앱에 넣지 않고도 주소 등록·권한 보고·수신설정을 할 수 있는 이유다."
      }
    },
    "headers": {
      "OptionalRetryAfter": {
        "description": "응답에 포함될 경우 재시도 전 최소 대기 시간을 나타내는 양의 delta-seconds. 오류 code에 따라 생략될 수 있다.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 9007199254740991
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "request_id",
              "retryable",
              "field_errors",
              "docs_url"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "UNAUTHENTICATED",
                  "PERMISSION_DENIED",
                  "RESOURCE_NOT_FOUND",
                  "IDEMPOTENCY_KEY_REUSED",
                  "IDEMPOTENCY_IN_PROGRESS",
                  "IDENTITY_REBIND_CONFLICT",
                  "IDENTITY_REBIND_IN_PROGRESS",
                  "DELIVERY_NOT_RESOLVABLE",
                  "DISPATCH_CONTROL_STALE",
                  "PROVIDER_CONNECTION_EXISTS",
                  "PROVIDER_CREDENTIAL_IDENTITY_PENDING",
                  "PROVIDER_CREDENTIAL_RECOVERY_REQUIRED",
                  "PROVIDER_CREDENTIAL_RECOVERY_NOT_REQUIRED",
                  "PROVIDER_CREDENTIAL_RECOVERY_IN_PROGRESS",
                  "MALFORMED_REQUEST",
                  "FIELD_REQUIRED",
                  "METHOD_NOT_ALLOWED",
                  "REQUEST_TOO_LARGE",
                  "PAYLOAD_TOO_LARGE",
                  "RESERVED_DATA_KEY",
                  "UNKNOWN_FIELD",
                  "FIELD_TYPE_MISMATCH",
                  "ATTRIBUTE_KEY_NOT_REGISTERED",
                  "EVENT_NAME_NOT_REGISTERED",
                  "DEFINITION_LIMIT_EXCEEDED",
                  "INSTALLATION_LIMIT_EXCEEDED",
                  "RESERVED_NAMESPACE",
                  "PROVIDER_CREDENTIAL_INVALID",
                  "PROVIDER_ENVIRONMENT_MISMATCH",
                  "RATE_LIMITED",
                  "INTERNAL_ERROR",
                  "DISPATCH_PAUSED",
                  "RESOURCE_ARCHIVED"
                ]
              },
              "message": {
                "type": "string"
              },
              "request_id": {
                "type": "string"
              },
              "retryable": {
                "type": "boolean"
              },
              "field_errors": {
                "type": "array",
                "maxItems": 100,
                "items": {
                  "$ref": "#/components/schemas/FieldError"
                }
              },
              "docs_url": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "FieldError": {
        "type": "object",
        "required": [
          "field",
          "code",
          "message"
        ],
        "properties": {
          "field": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255
          },
          "code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "message": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          }
        }
      },
      "MessageRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "target",
          "classification",
          "title",
          "body"
        ],
        "properties": {
          "send_purpose": {
            "type": "string",
            "enum": [
              "STANDARD",
              "TEST"
            ],
            "default": "STANDARD",
            "description": "Admission metadata, not a notification payload field. TEST sends real pushes with unchanged authorization and consent rules. Included in the idempotent intent; omitted and STANDARD are equivalent."
          },
          "personalize": {
            "type": "boolean",
            "default": false,
            "description": "명시적으로 켠 경우에만 제목·본문·부제의 {{user.key|기본값}} 또는 {{tag.key|기본값}}을 delivery 생성 시 치환하고 고정한다 (REQ-MSG-002)"
          },
          "track_receipts": {
            "type": "boolean",
            "default": false,
            "description": "track_events와 함께 켠 경우 iOS NSE의 제한된 수신 확인 capability를 추가한다. 실제 표시 증명이 아니다 (REQ-MSG-003)"
          },
          "track_events": {
            "type": "boolean",
            "default": false,
            "description": "이 메시지의 실제 SDK 수신·클릭 기록을 허용한다. SDK 수집 설정과 저장된 거부를 따르며 실제 표시나 전달을 보장하지 않는다 (REQ-MSG-003)"
          },
          "target": {
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "external_id"
                ],
                "properties": {
                  "external_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255
                  }
                }
              },
              {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "external_ids"
                ],
                "properties": {
                  "external_ids": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 20000,
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 255
                    }
                  }
                }
              },
              {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "installation_id"
                ],
                "properties": {
                  "installation_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255
                  }
                }
              },
              {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "subscription_id"
                ],
                "properties": {
                  "subscription_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255
                  }
                }
              }
            ],
            "description": "누구에게 보내는가. external_id · external_ids(최대 20000) · installation_id · subscription_id 중 하나 (REQ-MSG-001)"
          },
          "classification": {
            "$ref": "#/components/schemas/MessageClassification",
            "description": "서비스 알림인가 마케팅인가. PROMOTIONAL 은 마케팅 동의를 요구한다 (REQ-MSG-003)"
          },
          "display_path": {
            "type": "string",
            "enum": [
              "OS_DISPLAY"
            ],
            "default": "OS_DISPLAY",
            "description": "OS 가 띄우는가 앱이 띄우는가. v1a 는 OS_DISPLAY 만 (REQ-MSG-007)"
          },
          "priority": {
            "$ref": "#/components/schemas/MessagePriority",
            "default": "NORMAL",
            "description": "provider 에게 전달하는 우선순위 (REQ-MSG-004)"
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "알림 제목 (REQ-MSG-002)"
          },
          "body": {
            "type": "string",
            "minLength": 1,
            "maxLength": 1000,
            "description": "알림 본문 (REQ-MSG-002)"
          },
          "default_language": {
            "type": "string",
            "maxLength": 35,
            "pattern": "^[a-z]{2,3}(?:-[A-Z][a-z]{3})?(?:-[A-Z]{2}|-[0-9]{3})?$",
            "description": "기본 제목과 본문의 언어. 생략하면 언어와 무관한 기본 문구로 사용한다 (REQ-MSG-002)"
          },
          "localizations": {
            "type": "object",
            "minProperties": 1,
            "description": "언어별 제목·본문·선택적 iOS 부제. 개수 상한 없이 전체 메시지 64 KiB 및 언어별 발송 용량 제한을 적용한다. 정확한 locale → language → 기본 문구 순서로 선택하고 delivery 생성 시 고정한다 (REQ-MSG-002)",
            "propertyNames": {
              "type": "string",
              "maxLength": 35,
              "pattern": "^[a-z]{2,3}(?:-[A-Z][a-z]{3})?(?:-[A-Z]{2}|-[0-9]{3})?$"
            },
            "additionalProperties": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title",
                "body"
              ],
              "properties": {
                "title": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 200
                },
                "body": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 1000
                },
                "ios_subtitle": {
                  "type": "string",
                  "maxLength": 200
                }
              }
            }
          },
          "data": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "maxLength": 4096
            },
            "maxProperties": 32,
            "propertyNames": {
              "minLength": 1,
              "maxLength": 128
            },
            "description": "앱에 그대로 전달되는 문자열 지도. 키는 1~128자, 값은 최대 4096자이며 provider 예약어는 거부한다 (REQ-MSG-002)"
          },
          "deeplink": {
            "type": "string",
            "maxLength": 2048,
            "description": "알림을 눌렀을 때 열 위치 (REQ-MSG-002)"
          },
          "image_url": {
            "type": "string",
            "maxLength": 2048,
            "format": "uri",
            "pattern": "^[Hh][Tt][Tt][Pp][Ss]?:",
            "description": "알림 이미지 URL. iOS는 앱에 Notification Service Extension 연동 필요 (REQ-MSG-005)"
          },
          "ttl_seconds": {
            "type": "integer",
            "minimum": 0,
            "maximum": 2419200,
            "default": 86400,
            "description": "provider 가 알림을 보관할 시간(초). 0 은 저장하지 말고 한 번만 시도한다 (REQ-MSG-005)"
          },
          "collapse_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "pattern": "^[!-~]+$",
            "description": "공백 없는 printable ASCII 식별자. 같은 값의 이전 알림을 대체하며 FCM 은 동시에 4개까지만 유효하다 (REQ-MSG-005)"
          },
          "ios_subtitle": {
            "type": "string",
            "maxLength": 200,
            "description": "aps.alert.subtitle. 제목과 본문 사이에 뜬다 (REQ-MSG-005)"
          },
          "ios_badge": {
            "type": "integer",
            "minimum": 0,
            "maximum": 999999,
            "description": "aps.badge. 절대값이며 0 은 배지 제거다. 증감 연산은 v1 에 없다 (REQ-MSG-005)"
          },
          "ios_sound": {
            "type": "string",
            "maxLength": 200,
            "description": "aps.sound. \"default\" 이거나 앱 번들의 사운드 파일 이름 (REQ-MSG-005)"
          },
          "ios_thread_id": {
            "type": "string",
            "maxLength": 200,
            "description": "aps.thread-id. 같은 값끼리 알림이 묶인다 (REQ-MSG-005)"
          },
          "ios_category": {
            "type": "string",
            "maxLength": 200,
            "description": "aps.category. 액션 버튼용이며 앱에 카테고리 등록이 필요하다 (REQ-MSG-005)"
          },
          "ios_interruption_level": {
            "$ref": "#/components/schemas/IosInterruptionLevel",
            "description": "aps.interruption-level. critical 은 애플의 별도 승인이 필요하다 (REQ-MSG-005)"
          },
          "ios_relevance_score": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "aps.relevance-score. 알림 요약에서 대표로 뽑을 알림을 정한다 (REQ-MSG-005)"
          },
          "android_channel_id": {
            "type": "string",
            "maxLength": 200,
            "description": "android.notification.channel_id. 앱에 채널 등록이 필요하다 (REQ-MSG-005)"
          },
          "android_notification_tag": {
            "type": "string",
            "maxLength": 200,
            "description": "android.notification.tag. 같은 태그의 기존 알림을 교체한다 (REQ-MSG-005)"
          },
          "android_group": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "pattern": "^(?=.*[^\\p{Z}\\u0009-\\u000d\\u001c-\\u0020])[^\\u0000-\\u001f\\u007f-\\u009f]+$",
            "description": "Android SDK 알림 묶음 ID. 같은 값의 개별 알림을 유지하며 요약으로 묶는다. APP_DISPLAY 지원 SDK에서 클라이언트의 데이터 필드 설정보다 우선하며 알림 태그 교체나 채널 설정과는 다르다 (REQ-MSG-005)"
          },
          "android_color": {
            "type": "string",
            "minLength": 7,
            "maxLength": 7,
            "pattern": "^#[0-9a-fA-F]{6}$",
            "description": "android.notification.color. #rrggbb 형식만 받는다 (REQ-MSG-005)"
          },
          "android_small_icon": {
            "type": "string",
            "maxLength": 200,
            "description": "android.notification.icon. 앱 리소스 이름이며 URL 은 쓸 수 없다 (REQ-MSG-005)"
          },
          "android_sound": {
            "type": "string",
            "maxLength": 200,
            "description": "android.notification.sound. \"default\" 이거나 앱에 번들된 파일 이름 (REQ-MSG-005)"
          },
          "android_visibility": {
            "$ref": "#/components/schemas/AndroidVisibility",
            "description": "android.notification.visibility. 잠금화면 노출 범위 (REQ-MSG-005)"
          }
        },
        "allOf": [
          {
            "if": {
              "properties": {
                "classification": {
                  "const": "PROMOTIONAL"
                }
              },
              "required": [
                "classification"
              ]
            },
            "then": {
              "required": [
                "display_path"
              ]
            }
          }
        ]
      },
      "SubscriptionKind": {
        "type": "string",
        "enum": [
          "IOS_PUSH",
          "ANDROID_PUSH"
        ],
        "description": "REQ-ID-009"
      },
      "SubscriptionState": {
        "type": "string",
        "enum": [
          "ACTIVE",
          "INVALIDATED"
        ],
        "description": "REQ-ID-005, REQ-DLV-006"
      },
      "AddressState": {
        "type": "string",
        "enum": [
          "ACTIVE",
          "SUPERSEDED",
          "INVALIDATED",
          "RETIRED"
        ],
        "description": "REQ-ID-012"
      },
      "AddressType": {
        "type": "string",
        "enum": [
          "FCM_FID",
          "FCM_REGISTRATION_TOKEN_LEGACY",
          "APNS_DEVICE_TOKEN"
        ],
        "description": "REQ-ID-012"
      },
      "CredentialVerificationState": {
        "type": "string",
        "enum": [
          "PENDING",
          "PROPAGATING",
          "VERIFIED",
          "DEGRADED",
          "FAILED"
        ],
        "description": "REQ-PRV-003"
      },
      "CredentialVerificationReason": {
        "type": "string",
        "enum": [
          "OK",
          "MALFORMED_CREDENTIAL",
          "KEY_ID_TEAM_ID_SWAPPED",
          "PROJECT_MISMATCH",
          "AUTHENTICATION_REJECTED",
          "PROPAGATION_PENDING",
          "PROVIDER_UNREACHABLE",
          "DUPLICATE_ACTIVE_PRIVATE_KEY",
          "CREDENTIAL_KEY_IDENTITY_MISMATCH"
        ],
        "description": "REQ-PRV-003"
      },
      "EligibilityResult": {
        "type": "string",
        "enum": [
          "ELIGIBLE",
          "NOT_FOUND",
          "NOT_AUTHORIZED",
          "SUPPRESSED"
        ],
        "description": "REQ-CS-005"
      },
      "IdentityVerificationState": {
        "type": "string",
        "enum": [
          "UNVERIFIED",
          "VERIFIED",
          "EXPIRED"
        ],
        "description": "REQ-ID-008"
      },
      "OsPermissionState": {
        "type": "string",
        "enum": [
          "NOT_DETERMINED",
          "AUTHORIZED",
          "DENIED",
          "PROVISIONAL",
          "EPHEMERAL"
        ],
        "description": "REQ-CS-001"
      },
      "PushPreference": {
        "type": "string",
        "enum": [
          "OPTED_IN",
          "OPTED_OUT"
        ],
        "description": "REQ-CS-002"
      },
      "MarketingConsentState": {
        "type": "string",
        "enum": [
          "GRANTED",
          "WITHDRAWN",
          "UNKNOWN"
        ],
        "description": "REQ-CS-003"
      },
      "MessageClassification": {
        "type": "string",
        "enum": [
          "SERVICE",
          "PROMOTIONAL"
        ],
        "description": "REQ-CS-004"
      },
      "MessagePriority": {
        "type": "string",
        "enum": [
          "NORMAL",
          "HIGH"
        ],
        "description": "REQ-MSG-006"
      },
      "DisplayPath": {
        "type": "string",
        "enum": [
          "OS_DISPLAY",
          "APP_DISPLAY"
        ],
        "description": "REQ-MSG-006"
      },
      "MessageOrigin": {
        "type": "string",
        "enum": [
          "TRANSACTIONAL_API",
          "DASHBOARD_BROADCAST"
        ],
        "description": "REQ-MSG-001"
      },
      "DeliveryState": {
        "type": "string",
        "enum": [
          "PENDING",
          "READY",
          "LEASED",
          "IN_FLIGHT",
          "RETRY_SCHEDULED",
          "ACCEPTED",
          "REJECTED_PERMANENT",
          "OUTCOME_UNKNOWN",
          "SUPPRESSED"
        ],
        "description": "REQ-MSG-012"
      },
      "SuppressionReason": {
        "type": "string",
        "enum": [
          "HIERARCHY_ARCHIVED",
          "MESSAGE_STOPPED",
          "ADDRESS_INVALID",
          "NATIVE_CAPABILITY_MISSING",
          "IDENTITY_CHANGED",
          "OS_PERMISSION_MISSING",
          "APP_OPTED_OUT",
          "MARKETING_CONSENT_MISSING",
          "MESSAGE_EXPIRED",
          "DISPATCH_PAUSED"
        ],
        "description": "REQ-CS-005"
      },
      "FailureClass": {
        "type": "string",
        "enum": [
          "ADDRESS_PERMANENTLY_INVALID",
          "CREDENTIAL_PROBLEM",
          "REQUEST_ERROR",
          "TRANSIENT",
          "OUTCOME_UNKNOWN",
          "PRECONDITION_STALE",
          "RETRY_BUDGET_EXHAUSTED"
        ],
        "description": "REQ-DLV-005"
      },
      "IosInterruptionLevel": {
        "type": "string",
        "enum": [
          "passive",
          "active",
          "time-sensitive",
          "critical"
        ],
        "description": "REQ-MSG-005"
      },
      "AndroidVisibility": {
        "type": "string",
        "enum": [
          "PUBLIC",
          "PRIVATE",
          "SECRET"
        ],
        "description": "REQ-MSG-005"
      },
      "MetricAvailability": {
        "type": "string",
        "enum": [
          "MEASURED",
          "NOT_MEASURABLE"
        ],
        "description": "REQ-DLV-002"
      },
      "AddressInvalidationReason": {
        "type": "string",
        "enum": [
          "ADDRESS_PERMANENTLY_INVALID",
          "OPERATOR_REVOKED"
        ],
        "description": "REQ-DLV-005"
      },
      "IdempotencyOperation": {
        "type": "string",
        "enum": [
          "messages.send",
          "broadcasts.send",
          "installations.register",
          "addresses.register",
          "identity.rebind",
          "consent.record",
          "events.ingest",
          "attributes.set",
          "provider-credentials.rotate",
          "provider-credentials.recover"
        ],
        "description": "REQ-MSG-010"
      }
    }
  },
  "paths": {
    "/v1/project-messages": {
      "post": {
        "summary": "통합 서버 키에 지정한 같은 프로젝트 환경의 앱들에 원자적으로 발송 접수",
        "operationId": "postProjectMessages",
        "tags": [
          "message"
        ],
        "security": [
          {
            "projectApiKey": [
              "messages:send"
            ]
          }
        ],
        "x-kits-requirement": "REQ-MSG-001",
        "parameters": [
          {
            "name": "Kits-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "pattern": "^[\\x20-\\x7E]+$"
            },
            "description": "같은 키와 같은 canonical 요청은 mutation을 반복하지 않고 최초 결과를 재생한다"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/MessageRequest"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "target": {
                        "oneOf": [
                          {
                            "type": "object",
                            "additionalProperties": false,
                            "required": [
                              "external_id"
                            ],
                            "properties": {
                              "external_id": {
                                "type": "string",
                                "minLength": 1
                              }
                            }
                          },
                          {
                            "type": "object",
                            "additionalProperties": false,
                            "required": [
                              "external_ids"
                            ],
                            "properties": {
                              "external_ids": {
                                "type": "array",
                                "minItems": 1,
                                "maxItems": 20000,
                                "items": {
                                  "type": "string",
                                  "minLength": 1
                                }
                              }
                            }
                          }
                        ]
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "원자적 접수 결과. 실제 기기 수신 보장이 아니며 동일 키의 재시도는 24시간 동안 같은 결과를 반환한다. 대상에게 활성 구독이 없는 앱은 applications에서 빠지고 메시지를 만들지 않는다(모든 앱이 비면 키의 앱 하나만 0건으로 남고, 같은 키에서는 늘 같은 앱이다).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "id",
                    "origin",
                    "accepted_at",
                    "idempotency",
                    "targeted_subscriptions",
                    "ignored_fields",
                    "status_url",
                    "applications"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "pattern": "^pmsg_[0-9a-f]{32}$"
                    },
                    "origin": {
                      "type": "string",
                      "const": "TRANSACTIONAL_API"
                    },
                    "accepted_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "idempotency": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "replayed"
                      ],
                      "properties": {
                        "replayed": {
                          "type": "boolean"
                        }
                      }
                    },
                    "targeted_subscriptions": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "The sum of the listed applications' targeted_subscriptions. Unreachable (INVALIDATED) push subscriptions are not targeted."
                    },
                    "ignored_fields": {
                      "type": "array",
                      "description": "The sorted union of the listed applications' ignored_fields. An app that is left out (no active subscription) contributes none.",
                      "maxItems": 64,
                      "uniqueItems": true,
                      "items": {
                        "type": "string"
                      }
                    },
                    "status_url": {
                      "type": "string",
                      "pattern": "^/v1/project-messages/pmsg_[0-9a-f]{32}$"
                    },
                    "applications": {
                      "type": "array",
                      "description": "One entry per allowlisted app whose target had at least one active subscription when the message was accepted. An app with none is left out and creates no message. When every app has none, exactly one of the key's apps is listed with targeted_subscriptions 0 (always the same app for a given key), so the receipt always has an id and accepted_at.",
                      "minItems": 1,
                      "maxItems": 8,
                      "items": {
                        "type": "object",
                        "additionalProperties": false,
                        "required": [
                          "app_environment_id",
                          "platform",
                          "message_id",
                          "targeted_subscriptions",
                          "ignored_fields"
                        ],
                        "properties": {
                          "app_environment_id": {
                            "type": "string",
                            "pattern": "^aenv_[A-Za-z0-9_-]{1,123}$"
                          },
                          "platform": {
                            "type": "string",
                            "enum": [
                              "ANDROID",
                              "IOS"
                            ]
                          },
                          "message_id": {
                            "type": "string",
                            "pattern": "^msg_[A-Za-z0-9_-]{1,123}$"
                          },
                          "targeted_subscriptions": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Active push subscriptions of the target in this app when the message was accepted. A subscription whose push address the provider permanently rejected, an operator or the API revoked, or that moved to another installation is INVALIDATED and is not counted or sent to; it becomes active again when that installation registers a new token."
                          },
                          "ignored_fields": {
                            "type": "array",
                            "maxItems": 64,
                            "uniqueItems": true,
                            "items": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/project-messages/{id}": {
      "get": {
        "summary": "같은 통합 키가 접수한 결과를 24시간 동안 조회. 실시간 기기 전달 상태가 아니다.",
        "operationId": "getProjectMessages",
        "tags": [
          "message"
        ],
        "security": [
          {
            "projectApiKey": [
              "messages:read"
            ]
          }
        ],
        "x-kits-requirement": "REQ-DLV-001",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "앱별 message_id를 포함한 최초 접수 영수증. 각 메시지는 해당 앱의 대시보드 발송 이력에서 조회한다.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "id",
                    "origin",
                    "accepted_at",
                    "idempotency",
                    "targeted_subscriptions",
                    "ignored_fields",
                    "status_url",
                    "applications"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "pattern": "^pmsg_[0-9a-f]{32}$"
                    },
                    "origin": {
                      "type": "string",
                      "const": "TRANSACTIONAL_API"
                    },
                    "accepted_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "idempotency": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "replayed"
                      ],
                      "properties": {
                        "replayed": {
                          "type": "boolean"
                        }
                      }
                    },
                    "targeted_subscriptions": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "The sum of the listed applications' targeted_subscriptions. Unreachable (INVALIDATED) push subscriptions are not targeted."
                    },
                    "ignored_fields": {
                      "type": "array",
                      "description": "The sorted union of the listed applications' ignored_fields. An app that is left out (no active subscription) contributes none.",
                      "maxItems": 64,
                      "uniqueItems": true,
                      "items": {
                        "type": "string"
                      }
                    },
                    "status_url": {
                      "type": "string",
                      "pattern": "^/v1/project-messages/pmsg_[0-9a-f]{32}$"
                    },
                    "applications": {
                      "type": "array",
                      "description": "One entry per allowlisted app whose target had at least one active subscription when the message was accepted. An app with none is left out and creates no message. When every app has none, exactly one of the key's apps is listed with targeted_subscriptions 0 (always the same app for a given key), so the receipt always has an id and accepted_at.",
                      "minItems": 1,
                      "maxItems": 8,
                      "items": {
                        "type": "object",
                        "additionalProperties": false,
                        "required": [
                          "app_environment_id",
                          "platform",
                          "message_id",
                          "targeted_subscriptions",
                          "ignored_fields"
                        ],
                        "properties": {
                          "app_environment_id": {
                            "type": "string",
                            "pattern": "^aenv_[A-Za-z0-9_-]{1,123}$"
                          },
                          "platform": {
                            "type": "string",
                            "enum": [
                              "ANDROID",
                              "IOS"
                            ]
                          },
                          "message_id": {
                            "type": "string",
                            "pattern": "^msg_[A-Za-z0-9_-]{1,123}$"
                          },
                          "targeted_subscriptions": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Active push subscriptions of the target in this app when the message was accepted. A subscription whose push address the provider permanently rejected, an operator or the API revoked, or that moved to another installation is INVALIDATED and is not counted or sent to; it becomes active again when that installation registers a new token."
                          },
                          "ignored_fields": {
                            "type": "array",
                            "maxItems": 64,
                            "uniqueItems": true,
                            "items": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/installations/{installation_id}/notification-channels": {
      "get": {
        "summary": "Read the Android notification channels for the authenticated installation's app environment",
        "operationId": "getInstallationsNotificationChannels",
        "tags": [
          "installation"
        ],
        "security": [
          {
            "deviceToken": []
          }
        ],
        "x-kits-requirement": "REQ-NAT-001",
        "parameters": [
          {
            "name": "installation_id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current installation device token only; the path must match that installation. Returns the complete current configuration including archived channels. Revision zero with empty arrays means unconfigured. Android user channel settings take precedence; archiving does not delete OS channels.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "app_environment_id",
                    "revision",
                    "groups",
                    "channels"
                  ],
                  "properties": {
                    "app_environment_id": {
                      "type": "string",
                      "minLength": 6,
                      "maxLength": 128,
                      "pattern": "^aenv_[A-Za-z0-9_-]{1,123}$(?![\\s\\S])"
                    },
                    "revision": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 2147483647
                    },
                    "groups": {
                      "type": "array",
                      "maxItems": 20,
                      "items": {
                        "type": "object",
                        "additionalProperties": false,
                        "required": [
                          "id",
                          "name"
                        ],
                        "properties": {
                          "id": {
                            "type": "string",
                            "maxLength": 40,
                            "pattern": "^kitcgrp_[0-9a-f]{32}$",
                            "description": "Immutable group identifier."
                          },
                          "name": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 100,
                            "pattern": "^(?=[\\s\\S]*\\S)[^\\u0000-\\u001f\\u007f-\\u009f]+$(?![\\s\\S])"
                          }
                        }
                      }
                    },
                    "channels": {
                      "type": "array",
                      "maxItems": 100,
                      "items": {
                        "type": "object",
                        "additionalProperties": false,
                        "required": [
                          "id",
                          "name",
                          "description",
                          "group_id",
                          "importance",
                          "sound",
                          "vibration_enabled",
                          "lights_enabled",
                          "light_color",
                          "show_badge",
                          "lockscreen_visibility",
                          "archived"
                        ],
                        "properties": {
                          "id": {
                            "type": "string",
                            "maxLength": 39,
                            "pattern": "^kitchn_[0-9a-f]{32}$",
                            "description": "Immutable channel identifier."
                          },
                          "name": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 100,
                            "pattern": "^(?=[\\s\\S]*\\S)[^\\u0000-\\u001f\\u007f-\\u009f]+$(?![\\s\\S])"
                          },
                          "description": {
                            "type": "string",
                            "maxLength": 500,
                            "pattern": "^[^\\u0000-\\u001f\\u007f-\\u009f]*$(?![\\s\\S])"
                          },
                          "group_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 40,
                            "pattern": "^kitcgrp_[0-9a-f]{32}$"
                          },
                          "importance": {
                            "type": "string",
                            "enum": [
                              "MIN",
                              "LOW",
                              "DEFAULT",
                              "HIGH"
                            ]
                          },
                          "sound": {
                            "type": "string",
                            "maxLength": 80,
                            "pattern": "^(?:default|none|[a-z][a-z0-9_]{0,79})$(?![\\s\\S])",
                            "description": "default for the system sound, none for silence, or a raw Android resource name."
                          },
                          "vibration_enabled": {
                            "type": "boolean"
                          },
                          "lights_enabled": {
                            "type": "boolean"
                          },
                          "light_color": {
                            "type": "string",
                            "maxLength": 7,
                            "pattern": "^#[0-9A-Fa-f]{6}$"
                          },
                          "show_badge": {
                            "type": "boolean"
                          },
                          "lockscreen_visibility": {
                            "type": "string",
                            "enum": [
                              "PUBLIC",
                              "PRIVATE",
                              "SECRET"
                            ]
                          },
                          "archived": {
                            "type": "boolean"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identity-tokens": {
      "post": {
        "summary": "구형 kitid1 호환용 native 로그인 증명을 발급한다",
        "operationId": "postIdentityTokens",
        "tags": [
          "identity"
        ],
        "security": [
          {
            "apiKey": [
              "identity:write"
            ]
          }
        ],
        "x-kits-requirement": "REQ-ID-001",
        "parameters": [],
        "requestBody": {
          "required": true,
          "description": "구형 kitid1 서버 발급 증명 호환용이며 새 VERIFIED 연동은 고객 개인 키로 JWT를 로컬 서명하고 공개 키만 Dashboard에 등록한다. 로컬 JWT에는 이 HTTP 호출이나 Kits API 키가 필요 없다. 이 구형 경로에서도 고객 서버는 세션을 검증하고 external_id를 그 세션 UID에서 결정하며 요청 UID가 다르면 거절해야 한다. 발급은 재바인딩하지 않으며 멱등성 헤더나 고객 DB가 필요 없다. kitid1 증명은 300초간 정확한 앱·설치본·binding·operation에만 유효하다.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "installation_id",
                  "expected_binding_id",
                  "operation_id",
                  "external_id"
                ],
                "properties": {
                  "installation_id": {
                    "type": "string",
                    "pattern": "^inst_[0-9a-f]{32}$",
                    "maxLength": 37
                  },
                  "expected_binding_id": {
                    "type": "string",
                    "format": "uuid",
                    "maxLength": 36,
                    "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
                  },
                  "operation_id": {
                    "type": "string",
                    "format": "uuid",
                    "maxLength": 36,
                    "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
                  },
                  "external_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "성공",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "token",
                    "expires_at"
                  ],
                  "properties": {
                    "token": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 4096,
                      "description": "Opaque sensitive proof; never log or parse in client applications."
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/installations/{id}/identity": {
      "get": {
        "summary": "자기 설치본의 현재 binding과 마지막 native 작업을 확인한다",
        "operationId": "getInstallationsIdentity",
        "tags": [
          "identity"
        ],
        "security": [
          {
            "deviceToken": []
          }
        ],
        "x-kits-requirement": "REQ-ID-001",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cache-Control no-store. 마지막 native 작업의 결과 binding이 아직 현재 binding일 때만 operation_id가 있다. 이후 server rebind 등이 있었다면 null이다.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "id",
                    "binding_id",
                    "app_user",
                    "operation_id"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "minLength": 1
                    },
                    "binding_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "operation_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "app_user": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "id",
                        "external_id",
                        "anonymous"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "minLength": 1
                        },
                        "external_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "anonymous": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "자기 설치본의 로그인 또는 로그아웃을 조건부 수행한다 (새 작업 분당 20회)",
        "operationId": "postInstallationsIdentity",
        "tags": [
          "identity"
        ],
        "security": [
          {
            "deviceToken": []
          }
        ],
        "x-kits-requirement": "REQ-ID-001",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "SDK는 operation_id와 요청을 보내기 전에 저장하고 직렬화한다. LOGIN은 token 또는 external_id 중 정확히 하나만 허용하고 LOGOUT은 둘 다 금지한다. external_id 요청은 해당 프로젝트 환경에서 BASIC을 명시적으로 켠 경우에만 허용하며 기본값은 VERIFIED다. BASIC은 ID 소유권을 검증하지 않는다. SDK 설정만으로 권한이 생기거나 잘못된 token이 BASIC으로 자동 전환되지 않는다. 모드 변경은 이후 LOGIN에 적용하며 기존 연결을 소급 검증·해제하지 않는다. 권장 token은 고객 서버가 로컬 ES256 서명한 JWT이며 해당 프로젝트 환경의 등록 공개 키로 검증한다. 같은 프로젝트 환경의 앱은 등록 공개 키를 함께 쓰고, iss는 기기 자신의 앱 환경 공개 id여야 한다. 구형 kitid1도 호환한다. 같은 operation과 의미가 같은 요청은 현재 결과 binding에 한해 재생한다. JWT POST는 완료 재생에도 미만료·미폐기 키를 요구하며 만료·폐기 시 기기 인증 GET으로 완료를 확인한다. 구형 kitid1의 서명 검증된 완료 재생 예외는 유지하지만 어느 방식도 만료·폐기된 증명으로 새 작업을 할 수 없다. 이후 binding이 바뀐 요청은 IDENTITY_REBIND_CONFLICT이며 operation의 입력 변경은 IDEMPOTENCY_KEY_REUSED다. 토큰 갱신은 같은 operation을 유지한다.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "operation_id",
                  "expected_binding_id",
                  "action"
                ],
                "properties": {
                  "operation_id": {
                    "type": "string",
                    "format": "uuid",
                    "maxLength": 36,
                    "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
                  },
                  "expected_binding_id": {
                    "type": "string",
                    "format": "uuid",
                    "maxLength": 36,
                    "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
                  },
                  "action": {
                    "type": "string",
                    "enum": [
                      "LOGIN",
                      "LOGOUT"
                    ]
                  },
                  "token": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 4096
                  },
                  "external_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255
                  },
                  "expected_policy_revision": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 2147483647,
                    "description": "AUTO SDK의 LOGIN에서 조회한 정책 revision. 변경 시 IDENTITY_REBIND_CONFLICT이며 새 정책과 현재 binding을 다시 확인한다. 구형 명시 모드 SDK 호환을 위해 선택 사항이고 LOGOUT에는 금지한다."
                  }
                },
                "allOf": [
                  {
                    "if": {
                      "properties": {
                        "action": {
                          "const": "LOGIN"
                        }
                      }
                    },
                    "then": {
                      "oneOf": [
                        {
                          "required": [
                            "token"
                          ],
                          "not": {
                            "required": [
                              "external_id"
                            ]
                          }
                        },
                        {
                          "required": [
                            "external_id"
                          ],
                          "not": {
                            "required": [
                              "token"
                            ]
                          }
                        }
                      ]
                    }
                  },
                  {
                    "if": {
                      "properties": {
                        "action": {
                          "const": "LOGOUT"
                        }
                      }
                    },
                    "then": {
                      "not": {
                        "anyOf": [
                          {
                            "required": [
                              "token"
                            ]
                          },
                          {
                            "required": [
                              "external_id"
                            ]
                          },
                          {
                            "required": [
                              "expected_policy_revision"
                            ]
                          }
                        ]
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cache-Control no-store. 마지막 native 작업의 결과 binding이 아직 현재 binding일 때만 operation_id가 있다. 이후 server rebind 등이 있었다면 null이다.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "id",
                    "binding_id",
                    "app_user",
                    "operation_id"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "minLength": 1
                    },
                    "binding_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "operation_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "app_user": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "id",
                        "external_id",
                        "anonymous"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "minLength": 1
                        },
                        "external_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "anonymous": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/installations/{id}/identity-policy": {
      "get": {
        "summary": "자기 설치본의 현재 binding과 서버가 정한 로그인 인증 정책을 함께 확인한다",
        "operationId": "getInstallationsIdentityPolicy",
        "tags": [
          "identity"
        ],
        "security": [
          {
            "deviceToken": []
          }
        ],
        "x-kits-requirement": "REQ-ID-001",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cache-Control no-store. 기기 인증으로 자기 설치본만 조회하며 binding과 정책을 같은 트랜잭션에서 읽는다. 정책은 프로젝트 환경마다 하나라서 같은 프로젝트 환경의 앱은 같은 mode와 revision을 받고, app_environment_id는 기기 자신의 앱 환경이다. 정책 미설정 시 VERIFIED와 revision 0이다. SDK는 알 수 없는 응답이나 조회 실패를 BASIC으로 해석하지 않는다. 기존 identity 응답은 변경하지 않는다.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "id",
                    "binding_id",
                    "app_user",
                    "operation_id",
                    "identity_policy"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "minLength": 1
                    },
                    "binding_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "operation_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "app_user": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "id",
                        "external_id",
                        "anonymous"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "minLength": 1
                        },
                        "external_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "anonymous": {
                          "type": "boolean"
                        }
                      }
                    },
                    "identity_policy": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "app_environment_id",
                        "mode",
                        "revision"
                      ],
                      "properties": {
                        "app_environment_id": {
                          "type": "string",
                          "pattern": "^aenv_[0-9a-f]{32}$"
                        },
                        "mode": {
                          "type": "string",
                          "enum": [
                            "BASIC",
                            "VERIFIED"
                          ]
                        },
                        "revision": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 2147483647
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/notification-receipts": {
      "post": {
        "summary": "iOS 확장에 발급한 단일 배송 capability로 수신 관측을 기록한다",
        "operationId": "postNotificationReceipts",
        "tags": [
          "identity"
        ],
        "security": [
          {
            "notificationReceipt": []
          }
        ],
        "x-kits-requirement": "REQ-ID-001",
        "parameters": [],
        "requestBody": {
          "required": true,
          "description": "SDK와 메시지의 별도 opt-in이 필요하다. RECEIVED만 허용하며 발급 후 최대 35일, 실제 dispatch 기록이 있는 정확한 배송만 접근한다. 기기 토큰 또는 서버 키로 대체할 수 없다. 표시 증명이 아니며 동일 배송의 SDK 관측과 중복 제거된다.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "delivery_id",
                  "event"
                ],
                "properties": {
                  "delivery_id": {
                    "type": "string",
                    "format": "uuid",
                    "maxLength": 36
                  },
                  "event": {
                    "type": "string",
                    "const": "RECEIVED"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "성공",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "delivery_id",
                    "event",
                    "recorded"
                  ],
                  "properties": {
                    "delivery_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "event": {
                      "type": "string",
                      "const": "RECEIVED"
                    },
                    "recorded": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/installations/{id}:sync-activity-distribution": {
      "post": {
        "summary": "선택적 최근 28일 UTC 시간대별 foreground 활동 스냅샷을 동기화한다",
        "operationId": "postInstallationsSyncActivityDistribution",
        "tags": [
          "identity"
        ],
        "security": [
          {
            "deviceToken": []
          }
        ],
        "x-kits-requirement": "REQ-ID-001",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "binding에 고정된 최대 28개 UTC 날짜를 오름차순으로 전달한다. 각 hours는 24개 정수 foreground 초(0..3600), 미래 시간대는 0이다. 동일 sequence와 동일 본문만 재전송할 수 있으며 신규 스냅샷은 분당 1개다. 본문 형식 검사 뒤 시간대가 서버의 시간대 목록에 있는지 보기 전에 분당 한도를 보므로, 한도 안의 신규 스냅샷은 모르는 시간대여도 429다. 개인별 발송 시간의 비신뢰 힌트로만 사용하며 신원·동의·수신 증명이 아니다. 빈 days는 삭제 스냅샷이다.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "schema_version",
                  "binding_id",
                  "sequence",
                  "timezone",
                  "days"
                ],
                "x-kits-max-utf8-bytes": 16384,
                "properties": {
                  "schema_version": {
                    "type": "integer",
                    "const": 1,
                    "minimum": 1,
                    "maximum": 1
                  },
                  "binding_id": {
                    "type": "string",
                    "format": "uuid",
                    "maxLength": 36
                  },
                  "sequence": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 9007199254740991
                  },
                  "timezone": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64,
                    "description": "IANA time zone; UTC is accepted."
                  },
                  "days": {
                    "type": "array",
                    "maxItems": 28,
                    "items": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "date",
                        "hours"
                      ],
                      "properties": {
                        "date": {
                          "type": "string",
                          "format": "date",
                          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        },
                        "hours": {
                          "type": "array",
                          "minItems": 24,
                          "maxItems": 24,
                          "items": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 3600
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "성공",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "schema_version",
                    "binding_id",
                    "sequence",
                    "applied"
                  ],
                  "properties": {
                    "schema_version": {
                      "type": "integer",
                      "const": 1,
                      "minimum": 1,
                      "maximum": 1
                    },
                    "binding_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "sequence": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 9007199254740991
                    },
                    "applied": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/installations/{id}:record-notification-event": {
      "post": {
        "summary": "명시적 opt-in 배송의 SDK 수신 또는 클릭 관측을 기록한다",
        "operationId": "postInstallationsRecordNotificationEvent",
        "tags": [
          "identity"
        ],
        "security": [
          {
            "deviceToken": []
          }
        ],
        "x-kits-requirement": "REQ-ID-001",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "현재 기기 토큰과 실제 배송 대상이 일치해야 한다. SDK 관측이며 OS 표시 증명이나 현재 사용자 이벤트가 아니다. 메시지 접수 후 35일 이내, 신규 관측 60건/분/설치본. 동일 배송·종류는 최초 기록만 유지한다.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "delivery_id",
                  "event"
                ],
                "properties": {
                  "delivery_id": {
                    "type": "string",
                    "format": "uuid",
                    "maxLength": 36
                  },
                  "event": {
                    "type": "string",
                    "enum": [
                      "RECEIVED",
                      "CLICKED"
                    ]
                  },
                  "occurred_at": {
                    "type": "string",
                    "format": "date-time",
                    "maxLength": 24,
                    "description": "UTC Z timestamp. Untrusted; at most 35 days old or five minutes in the future."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "중복은 recorded=false이며 원래 시각과 관측을 변경하지 않는다",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "delivery_id",
                    "event",
                    "recorded"
                  ],
                  "properties": {
                    "delivery_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "event": {
                      "type": "string",
                      "enum": [
                        "RECEIVED",
                        "CLICKED"
                      ]
                    },
                    "recorded": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/installations/{id}:sync-audience": {
      "post": {
        "summary": "SDK 메타데이터와 누적 foreground 세션 관측을 동기화한다. 동일 sequence 재전송은 누적되지 않는다",
        "operationId": "postInstallationsSyncAudience",
        "tags": [
          "identity"
        ],
        "security": [
          {
            "deviceToken": []
          }
        ],
        "x-kits-requirement": "REQ-ID-001",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "선택적 SDK 관측이며 동의·권한·과금의 근거가 아니다. 미래 시각은 최대 5분, 누적 카운터의 감소는 거절한다. 신규 스냅샷은 초당 1개이고, 본문 형식 검사 뒤 시간대가 서버의 시간대 목록에 있는지 보기 전에 이 한도를 보므로 한도 안의 신규 스냅샷은 모르는 시간대여도 429다",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "sequence",
                  "metadata"
                ],
                "properties": {
                  "sequence": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 9007199254740991
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": false,
                    "properties": {
                      "app_version": {
                        "type": "string",
                        "maxLength": 128,
                        "x-kits-max-utf8-bytes": 512
                      },
                      "language": {
                        "type": "string",
                        "pattern": "^[a-z]{2,3}$"
                      },
                      "locale": {
                        "type": "string",
                        "maxLength": 35,
                        "pattern": "^[a-z]{2,3}(?:-[A-Z][a-z]{3})?(?:-[A-Z]{2}|-[0-9]{3})?$"
                      },
                      "timezone": {
                        "type": "string",
                        "maxLength": 64,
                        "pattern": "^[A-Za-z0-9_+/-]+$",
                        "description": "IANA time zone such as Asia/Seoul"
                      }
                    }
                  },
                  "activity": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "first_session_at",
                      "last_session_at",
                      "session_count",
                      "total_duration_ms"
                    ],
                    "properties": {
                      "first_session_at": {
                        "type": "string",
                        "format": "date-time",
                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,3})?Z$"
                      },
                      "last_session_at": {
                        "type": "string",
                        "format": "date-time",
                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,3})?Z$"
                      },
                      "session_count": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 9007199254740991
                      },
                      "total_duration_ms": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 9007199254740991
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "성공",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "sequence",
                    "applied"
                  ],
                  "properties": {
                    "sequence": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 9007199254740991
                    },
                    "applied": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/installations/{id}:set-client-tags": {
      "post": {
        "summary": "현재 installation binding의 SDK 문자열 태그 전체 스냅샷을 교체한다",
        "operationId": "postInstallationsSetClientTags",
        "tags": [
          "identity"
        ],
        "security": [
          {
            "deviceToken": []
          }
        ],
        "x-kits-requirement": "REQ-ID-001",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "서버 사용자 속성과 별도 namespace. SDK에서 제공한 binding_id가 달라지면 거절한다. 빈 tags는 모두 제거한다",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "sequence",
                  "binding_id",
                  "tags"
                ],
                "properties": {
                  "sequence": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 9007199254740991
                  },
                  "binding_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "tags": {
                    "type": "object",
                    "maxProperties": 64,
                    "propertyNames": {
                      "type": "string",
                      "pattern": "^(?!(kits_|consent_|billing_|entitlement_))[a-z][a-z0-9_]{0,62}$"
                    },
                    "additionalProperties": {
                      "type": "string",
                      "maxLength": 256,
                      "x-kits-max-utf8-bytes": 1024
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "성공",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "sequence",
                    "applied",
                    "binding_id"
                  ],
                  "properties": {
                    "sequence": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 9007199254740991
                    },
                    "applied": {
                      "type": "boolean"
                    },
                    "binding_id": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/installations": {
      "post": {
        "summary": "설치본 등록·갱신. 없으면 익명 사용자를 자동으로 만들고 token rotation은 cooldown으로 제한한다",
        "operationId": "postInstallations",
        "tags": [
          "identity"
        ],
        "security": [
          {
            "apiKey": [
              "identity:write"
            ]
          },
          {
            "apiKey": [
              "installations:enroll"
            ]
          }
        ],
        "x-kits-requirement": "REQ-ID-001",
        "parameters": [],
        "requestBody": {
          "required": true,
          "description": "native 두 필드는 함께 보내며, 둘 다 없으면 legacy contract 0으로 등록한다",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "client_installation_key"
                ],
                "properties": {
                  "client_installation_key": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "platform": {
                    "type": "string",
                    "enum": [
                      "IOS",
                      "ANDROID"
                    ]
                  },
                  "app_version": {
                    "type": "string",
                    "maxLength": 64
                  },
                  "native_contract_version": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1,
                    "enum": [
                      1
                    ]
                  },
                  "native_capabilities": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 11,
                    "uniqueItems": true,
                    "description": "ASCII 오름차순이어야 하며 플랫폼과 충돌하는 주소 capability를 함께 보낼 수 없다",
                    "items": {
                      "type": "string",
                      "enum": [
                        "ANDROID_APP_DISPLAY_V1",
                        "APNS_DEVICE_TOKEN",
                        "DURABLE_SYNC",
                        "FCM_FID",
                        "FCM_REGISTRATION_TOKEN_LEGACY",
                        "FOREGROUND_FORWARD",
                        "IDENTITY_HANDOFF",
                        "NOTIFICATION_CLICK",
                        "PAYLOAD_V1",
                        "PERMISSION_OBSERVATION",
                        "PUSH_PREFERENCE"
                      ]
                    }
                  }
                },
                "oneOf": [
                  {
                    "not": {
                      "anyOf": [
                        {
                          "required": [
                            "native_contract_version"
                          ]
                        },
                        {
                          "required": [
                            "native_capabilities"
                          ]
                        }
                      ]
                    }
                  },
                  {
                    "required": [
                      "native_contract_version",
                      "native_capabilities"
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "새 설치본은 201, cooldown 뒤 기존 설치본 갱신은 200이며 서버가 저장한 native 계약을 echo한다. 같은 installation이 최근 token을 회전했으면 project-environment 설정의 남은 초를 Retry-After에 담은 429 RATE_LIMITED를 반환한다",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "id",
                    "app_user",
                    "binding_id",
                    "device_token",
                    "native_contract"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "minLength": 1
                    },
                    "app_user": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "id",
                        "external_id",
                        "anonymous"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "minLength": 1
                        },
                        "external_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "anonymous": {
                          "type": "boolean"
                        }
                      }
                    },
                    "binding_id": {
                      "type": "string",
                      "minLength": 1
                    },
                    "device_token": {
                      "type": "string",
                      "minLength": 1
                    },
                    "native_contract": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "version",
                        "capabilities"
                      ],
                      "properties": {
                        "version": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 1,
                          "enum": [
                            0,
                            1
                          ]
                        },
                        "capabilities": {
                          "type": "array",
                          "maxItems": 11,
                          "uniqueItems": true,
                          "items": {
                            "type": "string",
                            "enum": [
                              "ANDROID_APP_DISPLAY_V1",
                              "APNS_DEVICE_TOKEN",
                              "DURABLE_SYNC",
                              "FCM_FID",
                              "FCM_REGISTRATION_TOKEN_LEGACY",
                              "FOREGROUND_FORWARD",
                              "IDENTITY_HANDOFF",
                              "NOTIFICATION_CLICK",
                              "PAYLOAD_V1",
                              "PERMISSION_OBSERVATION",
                              "PUSH_PREFERENCE"
                            ]
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/installations/{id}:identify": {
      "post": {
        "summary": "로그인 — external_id 가 가리키는 사용자로 재바인딩한다",
        "operationId": "postInstallationsIdentify",
        "tags": [
          "identity"
        ],
        "security": [
          {
            "apiKey": [
              "identity:write"
            ]
          }
        ],
        "x-kits-requirement": "REQ-ID-002",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          },
          {
            "name": "Kits-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "pattern": "^[\\x20-\\x7E]+$"
            },
            "description": "같은 키와 같은 canonical 요청은 mutation을 반복하지 않고 최초 결과를 재생한다"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "external_id",
                  "expected_binding_id"
                ],
                "properties": {
                  "external_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255
                  },
                  "expected_binding_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "최초 성공의 status와 모든 불변 결과는 같고 idempotency.replayed만 true로 재생된다",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "id",
                    "app_user",
                    "binding_id"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "minLength": 1
                    },
                    "app_user": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "id",
                        "external_id",
                        "anonymous"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "minLength": 1
                        },
                        "external_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "anonymous": {
                          "type": "boolean"
                        }
                      }
                    },
                    "binding_id": {
                      "type": "string",
                      "minLength": 1
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/installations/{id}:logout": {
      "post": {
        "summary": "로그아웃 — 새 익명 사용자로 되돌린다",
        "operationId": "postInstallationsLogout",
        "tags": [
          "identity"
        ],
        "security": [
          {
            "apiKey": [
              "identity:write"
            ]
          }
        ],
        "x-kits-requirement": "REQ-ID-006",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          },
          {
            "name": "Kits-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "pattern": "^[\\x20-\\x7E]+$"
            },
            "description": "같은 키와 같은 canonical 요청은 mutation을 반복하지 않고 최초 결과를 재생한다"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "expected_binding_id"
                ],
                "properties": {
                  "expected_binding_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "최초 성공의 status와 모든 불변 결과는 같고 idempotency.replayed만 true로 재생된다",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "id",
                    "app_user",
                    "binding_id"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "minLength": 1
                    },
                    "app_user": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "id",
                        "external_id",
                        "anonymous"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "minLength": 1
                        },
                        "external_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "anonymous": {
                          "type": "boolean"
                        }
                      }
                    },
                    "binding_id": {
                      "type": "string",
                      "minLength": 1
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/installations/{id}:transfer": {
      "post": {
        "summary": "이미 있는 다른 사용자에게 기기를 이전한다",
        "operationId": "postInstallationsTransfer",
        "tags": [
          "identity"
        ],
        "security": [
          {
            "apiKey": [
              "identity:write"
            ]
          }
        ],
        "x-kits-requirement": "REQ-ID-003",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          },
          {
            "name": "Kits-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "pattern": "^[\\x20-\\x7E]+$"
            },
            "description": "같은 키와 같은 canonical 요청은 mutation을 반복하지 않고 최초 결과를 재생한다"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "external_id",
                  "expected_binding_id"
                ],
                "properties": {
                  "external_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255
                  },
                  "expected_binding_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "최초 성공의 status와 모든 불변 결과는 같고 idempotency.replayed만 true로 재생된다",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "id",
                    "app_user",
                    "binding_id"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "minLength": 1
                    },
                    "app_user": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "id",
                        "external_id",
                        "anonymous"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "minLength": 1
                        },
                        "external_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "anonymous": {
                          "type": "boolean"
                        }
                      }
                    },
                    "binding_id": {
                      "type": "string",
                      "minLength": 1
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/app-users/{id}": {
      "get": {
        "summary": "사용자 현재 상태와 묶인 구독의 bounded keyset page",
        "operationId": "getAppUsers",
        "tags": [
          "identity"
        ],
        "security": [
          {
            "apiKey": [
              "identity:read"
            ]
          }
        ],
        "x-kits-requirement": "REQ-ID-001",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          },
          {
            "name": "subscription_limit",
            "in": "query",
            "required": false,
            "description": "응답에 포함할 구독 page 크기",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "subscription_cursor",
            "in": "query",
            "required": false,
            "description": "직전 응답의 사용자 결속 canonical base64url JSON cursor",
            "schema": {
              "type": "string",
              "maxLength": 384,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/app-users/by-external-id/{external_id}": {
      "get": {
        "summary": "고객사 식별자로 사용자와 묶인 구독의 bounded keyset page를 찾는다",
        "operationId": "getAppUsersByExternalId",
        "tags": [
          "identity"
        ],
        "security": [
          {
            "apiKey": [
              "identity:read"
            ]
          }
        ],
        "x-kits-requirement": "REQ-ID-002",
        "parameters": [
          {
            "name": "external_id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          },
          {
            "name": "subscription_limit",
            "in": "query",
            "required": false,
            "description": "응답에 포함할 구독 page 크기",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "subscription_cursor",
            "in": "query",
            "required": false,
            "description": "직전 응답의 사용자 결속 canonical base64url JSON cursor",
            "schema": {
              "type": "string",
              "maxLength": 384,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/push-subscriptions": {
      "post": {
        "summary": "기기 푸시 주소를 등록한다. 토큰은 봉인해서 저장한다",
        "operationId": "postPushSubscriptions",
        "tags": [
          "address"
        ],
        "security": [
          {
            "apiKey": [
              "identity:write"
            ]
          },
          {
            "deviceToken": []
          }
        ],
        "x-kits-requirement": "REQ-ID-003",
        "parameters": [],
        "requestBody": {
          "required": true,
          "description": "kind·address.type·provider_environment는 provider별 exact union이다",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "installation_id",
                  "kind",
                  "address",
                  "provider_environment"
                ],
                "properties": {
                  "installation_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "IOS_PUSH",
                      "ANDROID_PUSH"
                    ]
                  },
                  "address": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "type",
                      "value"
                    ],
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "FCM_FID",
                          "FCM_REGISTRATION_TOKEN_LEGACY",
                          "APNS_DEVICE_TOKEN"
                        ]
                      },
                      "value": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 256,
                        "pattern": "^(?=.*\\S)[^\\u0000-\\u001F\\u007F\"\\\\]+$",
                        "x-kits-max-utf8-bytes": 256,
                        "description": "공백만인 값은 금지하며 최대 256 UTF-8 byte이고 control·DEL·double quote·backslash는 허용하지 않는다"
                      }
                    }
                  },
                  "provider_environment": {
                    "type": "string",
                    "enum": [
                      "SANDBOX",
                      "PRODUCTION"
                    ]
                  }
                },
                "oneOf": [
                  {
                    "properties": {
                      "kind": {
                        "const": "ANDROID_PUSH"
                      },
                      "address": {
                        "properties": {
                          "type": {
                            "enum": [
                              "FCM_FID",
                              "FCM_REGISTRATION_TOKEN_LEGACY"
                            ]
                          }
                        }
                      },
                      "provider_environment": {
                        "const": "PRODUCTION"
                      }
                    }
                  },
                  {
                    "properties": {
                      "kind": {
                        "const": "IOS_PUSH"
                      },
                      "address": {
                        "properties": {
                          "type": {
                            "const": "APNS_DEVICE_TOKEN"
                          }
                        }
                      },
                      "provider_environment": {
                        "enum": [
                          "SANDBOX",
                          "PRODUCTION"
                        ]
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "새 주소 세대는 201, 현재 주소의 반복 등록은 200이다",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "id",
                    "state",
                    "address_generation",
                    "identity_verification_state",
                    "retired_addresses"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "pattern": "^sub_[0-9a-f]{32}$"
                    },
                    "state": {
                      "type": "string",
                      "enum": [
                        "ACTIVE",
                        "INVALIDATED"
                      ]
                    },
                    "address_generation": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 9007199254740991
                    },
                    "identity_verification_state": {
                      "type": "string",
                      "enum": [
                        "UNVERIFIED",
                        "VERIFIED",
                        "EXPIRED"
                      ]
                    },
                    "retired_addresses": {
                      "type": "array",
                      "maxItems": 100,
                      "items": {
                        "type": "object",
                        "additionalProperties": false,
                        "required": [
                          "subscription_id",
                          "generation",
                          "reason"
                        ],
                        "properties": {
                          "subscription_id": {
                            "type": "string",
                            "pattern": "^sub_[0-9a-f]{32}$"
                          },
                          "generation": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 9007199254740991
                          },
                          "reason": {
                            "type": "string",
                            "enum": [
                              "ADDRESS_LIMIT_EXCEEDED",
                              "TOKEN_TRANSFERRED"
                            ]
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/push-subscriptions:unregister": {
      "post": {
        "summary": "현재 주소와 정확히 일치할 때만 기기 푸시 주소를 등록 해제한다",
        "operationId": "postPushSubscriptionsUnregister",
        "tags": [
          "address"
        ],
        "security": [
          {
            "apiKey": [
              "identity:write"
            ]
          },
          {
            "deviceToken": []
          }
        ],
        "x-kits-requirement": "REQ-ID-012",
        "parameters": [],
        "requestBody": {
          "required": true,
          "description": "주소 원문은 서버에서 SHA-256 지문으로 바꿔 현재 세대와만 대조한다",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "installation_id",
                  "kind",
                  "address"
                ],
                "properties": {
                  "installation_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "IOS_PUSH",
                      "ANDROID_PUSH"
                    ]
                  },
                  "address": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "type",
                      "value"
                    ],
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "FCM_FID",
                          "FCM_REGISTRATION_TOKEN_LEGACY",
                          "APNS_DEVICE_TOKEN"
                        ]
                      },
                      "value": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 256,
                        "pattern": "^(?=.*\\S)[^\\u0000-\\u001F\\u007F\"\\\\]+$",
                        "x-kits-max-utf8-bytes": 256,
                        "description": "공백만인 값은 금지하며 최대 256 UTF-8 byte이고 control·DEL·double quote·backslash는 허용하지 않는다"
                      }
                    }
                  }
                },
                "oneOf": [
                  {
                    "properties": {
                      "kind": {
                        "const": "ANDROID_PUSH"
                      },
                      "address": {
                        "properties": {
                          "type": {
                            "enum": [
                              "FCM_FID",
                              "FCM_REGISTRATION_TOKEN_LEGACY"
                            ]
                          }
                        }
                      }
                    }
                  },
                  {
                    "properties": {
                      "kind": {
                        "const": "IOS_PUSH"
                      },
                      "address": {
                        "properties": {
                          "type": {
                            "const": "APNS_DEVICE_TOKEN"
                          }
                        }
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "일치한 현재 주소를 퇴역시켰는지 여부. stale·duplicate는 false다",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "unregistered"
                  ],
                  "properties": {
                    "unregistered": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/push-subscriptions/{id}/addresses": {
      "post": {
        "summary": "주소 세대를 회전한다",
        "operationId": "postPushSubscriptionsAddresses",
        "tags": [
          "address"
        ],
        "security": [
          {
            "apiKey": [
              "identity:write"
            ]
          }
        ],
        "x-kits-requirement": "REQ-ID-012",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "경로의 구독 kind와 address.type도 서버가 다시 대조한다",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "address",
                  "provider_environment"
                ],
                "properties": {
                  "address": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "type",
                      "value"
                    ],
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "FCM_FID",
                          "FCM_REGISTRATION_TOKEN_LEGACY",
                          "APNS_DEVICE_TOKEN"
                        ]
                      },
                      "value": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 256,
                        "pattern": "^(?=.*\\S)[^\\u0000-\\u001F\\u007F\"\\\\]+$",
                        "x-kits-max-utf8-bytes": 256,
                        "description": "공백만인 값은 금지하며 최대 256 UTF-8 byte이고 control·DEL·double quote·backslash는 허용하지 않는다"
                      }
                    }
                  },
                  "provider_environment": {
                    "type": "string",
                    "enum": [
                      "SANDBOX",
                      "PRODUCTION"
                    ]
                  }
                },
                "oneOf": [
                  {
                    "properties": {
                      "address": {
                        "properties": {
                          "type": {
                            "enum": [
                              "FCM_FID",
                              "FCM_REGISTRATION_TOKEN_LEGACY"
                            ]
                          }
                        }
                      },
                      "provider_environment": {
                        "const": "PRODUCTION"
                      }
                    }
                  },
                  {
                    "properties": {
                      "address": {
                        "properties": {
                          "type": {
                            "const": "APNS_DEVICE_TOKEN"
                          }
                        }
                      },
                      "provider_environment": {
                        "enum": [
                          "SANDBOX",
                          "PRODUCTION"
                        ]
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "새 주소 세대는 201, 현재 주소의 반복 등록은 200이다",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "id",
                    "state",
                    "address_generation",
                    "identity_verification_state",
                    "retired_addresses"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "pattern": "^sub_[0-9a-f]{32}$"
                    },
                    "state": {
                      "type": "string",
                      "enum": [
                        "ACTIVE",
                        "INVALIDATED"
                      ]
                    },
                    "address_generation": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 9007199254740991
                    },
                    "identity_verification_state": {
                      "type": "string",
                      "enum": [
                        "UNVERIFIED",
                        "VERIFIED",
                        "EXPIRED"
                      ]
                    },
                    "retired_addresses": {
                      "type": "array",
                      "maxItems": 100,
                      "items": {
                        "type": "object",
                        "additionalProperties": false,
                        "required": [
                          "subscription_id",
                          "generation",
                          "reason"
                        ],
                        "properties": {
                          "subscription_id": {
                            "type": "string",
                            "pattern": "^sub_[0-9a-f]{32}$"
                          },
                          "generation": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 9007199254740991
                          },
                          "reason": {
                            "type": "string",
                            "enum": [
                              "ADDRESS_LIMIT_EXCEEDED",
                              "TOKEN_TRANSFERRED"
                            ]
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/push-subscriptions/{id}:invalidate": {
      "post": {
        "summary": "지금 살아 있는 주소 세대를 무효화한다",
        "operationId": "postPushSubscriptionsInvalidate",
        "tags": [
          "address"
        ],
        "security": [
          {
            "apiKey": [
              "identity:write"
            ]
          }
        ],
        "x-kits-requirement": "REQ-DLV-006",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/push-subscriptions/{id}/addresses/{generation}:invalidate": {
      "post": {
        "summary": "정확히 그 세대만 무효화한다",
        "operationId": "postPushSubscriptionsAddressesInvalidate",
        "tags": [
          "address"
        ],
        "security": [
          {
            "apiKey": [
              "identity:write"
            ]
          }
        ],
        "x-kits-requirement": "REQ-DLV-006",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          },
          {
            "name": "generation",
            "in": "path",
            "required": true,
            "description": "1..9007199254740991 범위의 주소 세대.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 9007199254740991
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/installations/{id}/permission-observations": {
      "post": {
        "summary": "OS 알림 권한 관측을 기록한다. 고치지 않고 쌓는다",
        "operationId": "postInstallationsPermissionObservations",
        "tags": [
          "consent"
        ],
        "security": [
          {
            "apiKey": [
              "consent:write"
            ]
          },
          {
            "deviceToken": []
          }
        ],
        "x-kits-requirement": "REQ-CS-001",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/installations/{id}:set-push-preference": {
      "post": {
        "summary": "앱 안 알림 설정을 바꾼다",
        "operationId": "postInstallationsSetPushPreference",
        "tags": [
          "consent"
        ],
        "security": [
          {
            "apiKey": [
              "consent:write"
            ]
          },
          {
            "deviceToken": []
          }
        ],
        "x-kits-requirement": "REQ-CS-002",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/app-users/{id}/marketing-consents": {
      "post": {
        "summary": "마케팅 동의를 기록한다. 철회도 새로 쌓는다",
        "operationId": "postAppUsersMarketingConsents",
        "tags": [
          "consent"
        ],
        "security": [
          {
            "apiKey": [
              "consent:write"
            ]
          }
        ],
        "x-kits-requirement": "REQ-CS-003",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "summary": "마케팅 동의 이력의 bounded keyset page",
        "operationId": "getAppUsersMarketingConsents",
        "tags": [
          "consent"
        ],
        "security": [
          {
            "apiKey": [
              "consent:read"
            ]
          }
        ],
        "x-kits-requirement": "REQ-CS-003",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "페이지 크기",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "직전 응답의 canonical base64url JSON 사용자 결속 cursor; 서명된 비밀값은 아님",
            "schema": {
              "type": "string",
              "maxLength": 256,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "현재 상태와 동의 이력 한 쪽",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "app_user_id",
                    "current",
                    "history",
                    "next_cursor"
                  ],
                  "properties": {
                    "app_user_id": {
                      "type": "string"
                    },
                    "current": {
                      "oneOf": [
                        {
                          "type": "null"
                        },
                        {
                          "type": "object",
                          "additionalProperties": false,
                          "required": [
                            "state",
                            "occurred_at"
                          ],
                          "properties": {
                            "state": {
                              "type": "string",
                              "enum": [
                                "GRANTED",
                                "WITHDRAWN"
                              ]
                            },
                            "occurred_at": {
                              "type": "string",
                              "format": "date-time"
                            }
                          }
                        }
                      ]
                    },
                    "history": {
                      "type": "array",
                      "maxItems": 200,
                      "items": {
                        "type": "object",
                        "additionalProperties": false,
                        "required": [
                          "state",
                          "source",
                          "policy_version",
                          "actor_type",
                          "occurred_at",
                          "recorded_at"
                        ],
                        "properties": {
                          "state": {
                            "type": "string",
                            "enum": [
                              "GRANTED",
                              "WITHDRAWN"
                            ]
                          },
                          "source": {
                            "type": "string"
                          },
                          "policy_version": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "actor_type": {
                            "type": "string"
                          },
                          "occurred_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "recorded_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/app-users/{id}/delivery-eligibility": {
      "get": {
        "summary": "지금 이 사람에게 보낼 수 있나, 안 되면 왜",
        "operationId": "getAppUsersDeliveryEligibility",
        "tags": [
          "consent"
        ],
        "security": [
          {
            "apiKey": [
              "messages:read"
            ]
          }
        ],
        "x-kits-requirement": "REQ-CS-005",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          },
          {
            "name": "classification",
            "in": "query",
            "required": false,
            "description": "판정할 메시지 분류",
            "schema": {
              "type": "string",
              "enum": [
                "SERVICE",
                "PROMOTIONAL"
              ],
              "default": "SERVICE"
            }
          },
          {
            "name": "subscription_state",
            "in": "query",
            "required": false,
            "description": "판정할 구독 상태 범위. 명시하면 paginated mode다",
            "schema": {
              "type": "string",
              "enum": [
                "ALL",
                "ACTIVE",
                "INACTIVE"
              ],
              "default": "ALL"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "페이지 크기. cursor와 함께 사용한다",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "직전 응답의 next_cursor. 사용자·분류·구독 상태에 결속된다",
            "schema": {
              "type": "string",
              "maxLength": 512,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "현재 구독별 발송 가능성의 bounded page",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "app_user_id",
                    "classification",
                    "subscription_state",
                    "subscriptions",
                    "next_cursor",
                    "checked",
                    "not_checked",
                    "withheld"
                  ],
                  "properties": {
                    "app_user_id": {
                      "type": "string",
                      "pattern": "^usr_[0-9a-f]{32}$"
                    },
                    "classification": {
                      "type": "string",
                      "enum": [
                        "SERVICE",
                        "PROMOTIONAL"
                      ]
                    },
                    "subscription_state": {
                      "type": "string",
                      "enum": [
                        "ALL",
                        "ACTIVE",
                        "INACTIVE"
                      ]
                    },
                    "subscriptions": {
                      "type": "array",
                      "maxItems": 1000,
                      "items": {
                        "type": "object",
                        "additionalProperties": false,
                        "required": [
                          "id",
                          "eligible"
                        ],
                        "properties": {
                          "id": {
                            "type": "string",
                            "pattern": "^sub_[0-9a-f]{32}$"
                          },
                          "eligible": {
                            "type": "boolean"
                          },
                          "blocked_by": {
                            "type": "string",
                            "enum": [
                              "ADDRESS_INVALID",
                              "NATIVE_CAPABILITY_MISSING",
                              "OS_PERMISSION_MISSING",
                              "APP_OPTED_OUT",
                              "MARKETING_CONSENT_MISSING"
                            ]
                          },
                          "permission_state": {
                            "type": "string",
                            "enum": [
                              "NOT_DETERMINED",
                              "AUTHORIZED",
                              "DENIED",
                              "PROVISIONAL",
                              "EPHEMERAL"
                            ]
                          },
                          "observed_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "maxLength": 512,
                      "pattern": "^[A-Za-z0-9_-]+$"
                    },
                    "marketing_consent": {
                      "oneOf": [
                        {
                          "type": "null"
                        },
                        {
                          "type": "object",
                          "additionalProperties": false,
                          "required": [
                            "state",
                            "occurred_at"
                          ],
                          "properties": {
                            "state": {
                              "type": "string",
                              "enum": [
                                "GRANTED",
                                "WITHDRAWN"
                              ]
                            },
                            "occurred_at": {
                              "type": "string",
                              "format": "date-time"
                            }
                          }
                        }
                      ]
                    },
                    "checked": {
                      "type": "array",
                      "maxItems": 6,
                      "items": {
                        "type": "string",
                        "enum": [
                          "SUBSCRIPTION_ACTIVE",
                          "ADDRESS_PRESENT",
                          "NATIVE_CAPABILITY",
                          "OS_PERMISSION",
                          "APP_PREFERENCE",
                          "MARKETING_CONSENT"
                        ]
                      }
                    },
                    "not_checked": {
                      "type": "array",
                      "maxItems": 4,
                      "items": {
                        "type": "string",
                        "enum": [
                          "IDENTITY_CHANGED",
                          "MESSAGE_EXPIRED",
                          "DISPATCH_PAUSED",
                          "RETRY_BUDGET_EXHAUSTED"
                        ]
                      }
                    },
                    "withheld": {
                      "type": "array",
                      "maxItems": 2,
                      "items": {
                        "type": "string",
                        "enum": [
                          "MARKETING_CONSENT",
                          "OS_PERMISSION"
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/messages": {
      "post": {
        "summary": "발송 접수. 한 사람 또는 여러 사람(최대 20000). 실제 전송은 워커가 한다",
        "description": "Optional top-level send_purpose accepts STANDARD (default) or TEST. TEST is a real send with identical permission, consent and delivery gates; it is recorded separately in Delivery history. Purpose is bound to the idempotent intent and never placed in the provider payload.",
        "operationId": "postMessages",
        "tags": [
          "message"
        ],
        "security": [
          {
            "apiKey": [
              "messages:send"
            ]
          }
        ],
        "x-kits-requirement": "REQ-MSG-001",
        "parameters": [
          {
            "name": "Kits-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "pattern": "^[\\x20-\\x7E]+$"
            },
            "description": "같은 키와 같은 canonical 요청은 mutation을 반복하지 않고 최초 결과를 재생한다"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageRequest"
              }
            }
          }
        },
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라"
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/broadcasts": {
      "post": {
        "summary": "이 앱의 모두에게 발송. 대상 행은 뒤에서 만들어진다",
        "description": "Optional top-level send_purpose accepts STANDARD (default) or TEST. TEST is a real send with identical permission, consent and delivery gates; it is recorded separately in Delivery history. Purpose is bound to the idempotent intent and never placed in the provider payload.",
        "operationId": "postBroadcasts",
        "tags": [
          "message"
        ],
        "security": [
          {
            "apiKey": [
              "broadcasts:send"
            ]
          }
        ],
        "x-kits-requirement": "REQ-MSG-015",
        "parameters": [
          {
            "name": "Kits-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "pattern": "^[\\x20-\\x7E]+$"
            },
            "description": "같은 키와 같은 canonical 요청은 mutation을 반복하지 않고 최초 결과를 재생한다"
          }
        ],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/messages/{id}": {
      "get": {
        "summary": "요청 단위 결과 요약",
        "operationId": "getMessages",
        "tags": [
          "message"
        ],
        "security": [
          {
            "apiKey": [
              "messages:read"
            ]
          }
        ],
        "x-kits-requirement": "REQ-DLV-001",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/messages/{id}/deliveries": {
      "get": {
        "summary": "대상별 결과",
        "operationId": "getMessagesDeliveries",
        "tags": [
          "message"
        ],
        "security": [
          {
            "apiKey": [
              "messages:read"
            ]
          }
        ],
        "x-kits-requirement": "REQ-DLV-005",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "pagination을 명시할 때의 페이지 크기. cursor만 주면 50, query가 전혀 없으면 1000건 이하 legacy 전체 응답이다",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "서버가 직전 응답의 next_cursor로 발급한 불투명·메시지 결속 cursor",
            "schema": {
              "type": "string",
              "maxLength": 1024,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/deliveries": {
      "get": {
        "summary": "사람이 닫아야 하는 미결 발송 목록",
        "operationId": "getDeliveries",
        "tags": [
          "operations"
        ],
        "security": [
          {
            "apiKey": [
              "dispatch-control:read"
            ]
          }
        ],
        "x-kits-requirement": "REQ-MSG-012",
        "parameters": [
          {
            "name": "state",
            "in": "query",
            "required": true,
            "description": "v1a에서 조회할 수 있는 유일한 상태",
            "schema": {
              "type": "string",
              "enum": [
                "OUTCOME_UNKNOWN"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "페이지 크기",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "서버가 직전 응답에서 발급한 불투명 cursor",
            "schema": {
              "type": "string",
              "maxLength": 256,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "미결 발송 한 쪽과 10,000건 상한의 queue 크기",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "items",
                    "next_cursor",
                    "total",
                    "total_exact"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "maxItems": 200,
                      "items": {
                        "type": "object",
                        "additionalProperties": false,
                        "required": [
                          "message_id",
                          "subscription_id",
                          "classification",
                          "attempt_count",
                          "created_at"
                        ],
                        "properties": {
                          "message_id": {
                            "type": "string",
                            "pattern": "^msg_[A-Za-z0-9_-]{24}$"
                          },
                          "subscription_id": {
                            "type": "string",
                            "pattern": "^sub_[0-9a-f]{32}$"
                          },
                          "classification": {
                            "type": "string",
                            "enum": [
                              "SERVICE",
                              "PROMOTIONAL"
                            ]
                          },
                          "attempt_count": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 2147483647
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "provider_status": {
                            "type": "integer",
                            "minimum": 100,
                            "maximum": 599
                          },
                          "provider_code": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 64,
                            "description": "authoritative provider와 HTTP status에 결속된 customer-safe code"
                          }
                        }
                      }
                    },
                    "next_cursor": {
                      "oneOf": [
                        {
                          "type": "string",
                          "maxLength": 256,
                          "pattern": "^[A-Za-z0-9_-]+$"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 10000
                    },
                    "total_exact": {
                      "type": "boolean",
                      "description": "false면 total은 정확한 합계가 아니라 최소 10,000건이라는 하한"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/messages/{id}:stop": {
      "post": {
        "summary": "이 메시지의 남은 발송을 멈춘다. 앱 전체를 끄지 않고",
        "operationId": "postMessagesStop",
        "tags": [
          "message"
        ],
        "security": [
          {
            "apiKey": [
              "dispatch-control:write"
            ]
          }
        ],
        "x-kits-requirement": "REQ-CS-009",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/messages/{id}/deliveries/{subscription_id}:resolve-unknown": {
      "post": {
        "summary": "결과불명 발송을 운영자가 수동으로 종결한다",
        "operationId": "postMessagesDeliveriesResolveUnknown",
        "tags": [
          "message"
        ],
        "security": [
          {
            "apiKey": [
              "dispatch-control:write"
            ]
          }
        ],
        "x-kits-requirement": "REQ-MSG-012",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          },
          {
            "name": "subscription_id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/app-environments/{id}/dispatch-control": {
      "get": {
        "summary": "지금 멈춰 있는가와 현재 제어 세대",
        "operationId": "getAppEnvironmentsDispatchControl",
        "tags": [
          "operations"
        ],
        "security": [
          {
            "apiKey": [
              "dispatch-control:read"
            ]
          }
        ],
        "x-kits-requirement": "REQ-CS-005",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/app-environments/{id}/address-breaker": {
      "get": {
        "summary": "주소 대량 삭제 차단기가 걸려 있는가와 걸린 근거",
        "operationId": "getAppEnvironmentsAddressBreaker",
        "tags": [
          "operations"
        ],
        "security": [
          {
            "apiKey": [
              "dispatch-control:read"
            ]
          }
        ],
        "x-kits-requirement": "REQ-DLV-008",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/app-environments/{id}:reset-address-breaker": {
      "post": {
        "summary": "차단기를 푼다. 대량 주소 삭제를 다시 켜는 일이라 사유가 필요하다",
        "operationId": "postAppEnvironmentsResetAddressBreaker",
        "tags": [
          "operations"
        ],
        "security": [
          {
            "apiKey": [
              "dispatch-control:write"
            ]
          }
        ],
        "x-kits-requirement": "REQ-DLV-008",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/app-environments/{id}:pause-dispatch": {
      "post": {
        "summary": "긴급 정지. 세대를 올려 대기 중인 것도 막는다",
        "operationId": "postAppEnvironmentsPauseDispatch",
        "tags": [
          "operations"
        ],
        "security": [
          {
            "apiKey": [
              "dispatch-control:write"
            ]
          }
        ],
        "x-kits-requirement": "REQ-CS-005",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/app-environments/{id}:resume-dispatch": {
      "post": {
        "summary": "발송 재개",
        "operationId": "postAppEnvironmentsResumeDispatch",
        "tags": [
          "operations"
        ],
        "security": [
          {
            "apiKey": [
              "dispatch-control:write"
            ]
          }
        ],
        "x-kits-requirement": "REQ-CS-005",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/push-provider-connections": {
      "post": {
        "summary": "공급자 자격증명을 등록한다. 원문은 봉인되어 저장된다",
        "operationId": "postPushProviderConnections",
        "tags": [
          "credential"
        ],
        "security": [
          {
            "apiKey": [
              "provider-connections:write"
            ]
          }
        ],
        "x-kits-requirement": "REQ-PRV-001",
        "parameters": [],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "summary": "연결 health와 최신 후보·실제 active 버전·dispatch readiness를 조회한다",
        "operationId": "getPushProviderConnections",
        "tags": [
          "credential"
        ],
        "security": [
          {
            "apiKey": [
              "provider-connections:read"
            ]
          }
        ],
        "x-kits-requirement": "REQ-PRV-002",
        "parameters": [],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/push-provider-connections/{id}:rotate-credential": {
      "post": {
        "summary": "PENDING 후보 버전을 더한다. 검증 PASS 전까지 기존 active로 계속 발송한다",
        "operationId": "postPushProviderConnectionsRotateCredential",
        "tags": [
          "credential"
        ],
        "security": [
          {
            "apiKey": [
              "credentials:rotate"
            ]
          }
        ],
        "x-kits-requirement": "REQ-PRV-004",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          },
          {
            "name": "Kits-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "pattern": "^[\\x20-\\x7E]+$"
            },
            "description": "같은 키와 같은 canonical 요청은 mutation을 반복하지 않고 최초 결과를 재생한다"
          }
        ],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/push-provider-connections/{id}:recover-credential": {
      "post": {
        "summary": "기존 active 키의 신원을 복원할 수 없는 터미널 상태에서만 명시적 복구 후보를 생성한다",
        "operationId": "postPushProviderConnectionsRecoverCredential",
        "tags": [
          "credential"
        ],
        "security": [
          {
            "apiKey": [
              "credentials:rotate"
            ]
          }
        ],
        "x-kits-requirement": "REQ-PRV-004",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          },
          {
            "name": "Kits-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "pattern": "^[\\x20-\\x7E]+$"
            },
            "description": "같은 키와 같은 canonical 요청은 mutation을 반복하지 않고 최초 결과를 재생한다"
          }
        ],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/push-provider-connections/{id}:verify": {
      "post": {
        "summary": "다시 확인해 달라고 요청한다. 확인은 워커가 한다",
        "operationId": "postPushProviderConnectionsVerify",
        "tags": [
          "credential"
        ],
        "security": [
          {
            "apiKey": [
              "provider-connections:write"
            ]
          }
        ],
        "x-kits-requirement": "REQ-PRV-003",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/attribute-definitions/{key}": {
      "put": {
        "summary": "속성 키와 타입을 등록한다",
        "operationId": "putAttributeDefinitions",
        "tags": [
          "collect"
        ],
        "security": [
          {
            "apiKey": [
              "attributes:write"
            ]
          }
        ],
        "x-kits-requirement": "REQ-ATTR-002",
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/attribute-definitions": {
      "get": {
        "summary": "등록된 속성 정의의 bounded keyset 목록",
        "operationId": "getAttributeDefinitions",
        "tags": [
          "collect"
        ],
        "security": [
          {
            "apiKey": [
              "attributes:read"
            ]
          }
        ],
        "x-kits-requirement": "REQ-ATTR-002",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "페이지 크기",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "서버가 직전 응답에서 발급한 속성 정의 전용 불투명 cursor",
            "schema": {
              "type": "string",
              "maxLength": 256,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "속성 정의 한 쪽",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "maxItems": 200,
                      "items": {
                        "type": "object",
                        "additionalProperties": false,
                        "required": [
                          "key",
                          "value_type",
                          "created_at",
                          "retired_at"
                        ],
                        "properties": {
                          "key": {
                            "type": "string",
                            "pattern": "^[A-Za-z][A-Za-z0-9_]{0,62}$"
                          },
                          "value_type": {
                            "type": "string",
                            "enum": [
                              "STRING",
                              "NUMBER",
                              "BOOLEAN",
                              "DATETIME",
                              "STRING_SET"
                            ]
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "retired_at": {
                            "oneOf": [
                              {
                                "type": "string",
                                "format": "date-time"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          }
                        }
                      }
                    },
                    "next_cursor": {
                      "oneOf": [
                        {
                          "type": "string",
                          "maxLength": 256,
                          "pattern": "^[A-Za-z0-9_-]+$"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/app-users/{id}:set-attributes": {
      "post": {
        "summary": "속성 값을 기록한다. 더 새로운 관측만 이긴다",
        "operationId": "postAppUsersSetAttributes",
        "tags": [
          "collect"
        ],
        "security": [
          {
            "apiKey": [
              "attributes:write"
            ]
          }
        ],
        "x-kits-requirement": "REQ-ATTR-001",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/event-definitions/{name}": {
      "put": {
        "summary": "이벤트 이름을 등록한다",
        "operationId": "putEventDefinitions",
        "tags": [
          "collect"
        ],
        "security": [
          {
            "apiKey": [
              "events:write"
            ]
          }
        ],
        "x-kits-requirement": "REQ-ATTR-003",
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "description": "`/` 와 `:` 는 퍼센트 인코딩해야 한다.",
            "schema": {
              "type": "string",
              "pattern": "^[^/:]+$"
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/event-definitions": {
      "get": {
        "summary": "등록된 이벤트 정의의 bounded keyset 목록",
        "operationId": "getEventDefinitions",
        "tags": [
          "collect"
        ],
        "security": [
          {
            "apiKey": [
              "events:read"
            ]
          }
        ],
        "x-kits-requirement": "REQ-ATTR-003",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "페이지 크기",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "서버가 직전 응답에서 발급한 이벤트 정의 전용 불투명 cursor",
            "schema": {
              "type": "string",
              "maxLength": 256,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "이벤트 정의 한 쪽",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "maxItems": 200,
                      "items": {
                        "type": "object",
                        "additionalProperties": false,
                        "required": [
                          "name",
                          "created_at",
                          "retired_at"
                        ],
                        "properties": {
                          "name": {
                            "type": "string",
                            "pattern": "^[A-Za-z][A-Za-z0-9_]{0,62}$"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "retired_at": {
                            "oneOf": [
                              {
                                "type": "string",
                                "format": "date-time"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          }
                        }
                      }
                    },
                    "next_cursor": {
                      "oneOf": [
                        {
                          "type": "string",
                          "maxLength": 256,
                          "pattern": "^[A-Za-z0-9_-]+$"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/app-events": {
      "post": {
        "summary": "이벤트를 배치로 기록한다. 클라이언트 id 로 중복을 거른다",
        "operationId": "postAppEvents",
        "tags": [
          "collect"
        ],
        "security": [
          {
            "apiKey": [
              "events:write"
            ]
          }
        ],
        "x-kits-requirement": "REQ-ATTR-003",
        "parameters": [],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공. 정확한 상태 코드와 본문 모양은 계약 문서 §5 를 보라",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/registry/enums": {
      "get": {
        "summary": "상태값 레지스트리 전체",
        "operationId": "getRegistryEnums",
        "tags": [
          "registry"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-kits-requirement": "REQ-MSG-002",
        "parameters": [],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/registry/error-codes": {
      "get": {
        "summary": "오류 코드 레지스트리 전체",
        "operationId": "getRegistryErrorCodes",
        "tags": [
          "registry"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-kits-requirement": "REQ-MSG-002",
        "parameters": [],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/registry/message-fields": {
      "get": {
        "summary": "메시지 필드 레지스트리 전체",
        "operationId": "getRegistryMessageFields",
        "tags": [
          "registry"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-kits-requirement": "REQ-MSG-002",
        "parameters": [],
        "responses": {
          "401": {
            "description": "Bearer credential이 없거나 유효하지 않다",
            "headers": {
              "WWW-Authenticate": {
                "required": true,
                "description": "구체적인 credential 상태를 노출하지 않는 Bearer challenge",
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "2XX": {
            "description": "성공",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "4XX": {
            "description": "요청이 거절됐다",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "우리 쪽 오류",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/OptionalRetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "x-kits-generated-by": "Generated by deploy/lightsail/docs/render.mjs"
}
