# 오류

> 모든 Kanorio API가 반환하는 공통 오류 형식, 코드와 HTTP 상태 매핑, 그리고 재시도 처리 방법을 설명합니다.

> Markdown version of https://kanorio.com/ko/developers/basics/errors. Append `.md` to any developer docs URL for this format. Index: https://kanorio.com/llms.txt · MCP: https://app.kanorio.com/api/mcp
>
> Translated from the English original: https://kanorio.com/en/developers/basics/errors.md

Management API, Content Delivery API, Customer API는 모두 동일한 형식으로 오류를 반환합니다. 각 코드는 모든 API에서 항상 동일한 HTTP 상태 코드와 매핑되므로, 하나의 오류 핸들러로 모든 API를 처리할 수 있습니다.

```json
{
  "error": {
    "code": "SLUG_TAKEN",
    "message": "Another post already uses this slug.",
    "details": { "slug": "how-we-feed-our-starter" }
  }
}
```

| 필드 | 타입 | 참고 |
|---|---|---|
| `error.code` | string | 안정적이고 기계가 읽을 수 있는 코드입니다. 조건 분기에 사용하십시오. |
| `error.message` | string | 로그 및 개발자를 위한 영어 문장입니다. 언제든지 변경될 수 있으므로 이 값을 기준으로 로직을 작성하지 마십시오. |
| `error.details` | object, optional | `retryAfter`, `availableAt`, `issues`, `requiredScope` 등 코드에 대한 구조화된 컨텍스트입니다. |

성공적인 응답에는 `error`가 포함되지 않으며, API가 성공 여부를 `ok` 플래그로 감싸지도 않습니다.

## 코드별 HTTP 상태

모든 코드는 하나의 상태와 매핑됩니다. 나중에 추가되는 코드도 동일한 규칙을 따르므로, 아직 확인하지 못한 코드라도 상태를 기준으로 처리할 수 있습니다.

| 상태 | 의미 | 예시 |
|---|---|---|
| `400` | 요청이 잘못되었거나 유효성 검사에 실패했습니다 | `VALIDATION`, `INVALID_CURSOR`, `INVALID_`로 시작하는 코드 |
| `401` | 자격 증명이 없거나, 유효하지 않거나, 만료되었거나, 취소되었습니다 | `UNAUTHORIZED`, `UNAUTHENTICATED` |
| `403` | 자격 증명은 유효하지만 권한, 플랜, 역할 또는 모드에 의해 허용되지 않습니다 | `FORBIDDEN`, `OWNER_REQUIRED`, `LIMIT_REACHED`, `LOCKED` |
| `404` | 이 사이트에 리소스가 존재하지 않습니다 | `NOT_FOUND`, `_NOT_FOUND`로 끝나는 코드 |
| `405` | 이 경로에서 지원되지 않는 메서드입니다 | `METHOD_NOT_ALLOWED` |
| `409` | 현재 상태와 충돌합니다(오래된 쓰기, 사용 중인 슬러그, 중복 등) | `CONFLICT`, `SLUG_TAKEN`, `IDEMPOTENCY_CONFLICT` |
| `413` | 업로드 파일이 너무 큽니다 | `FILE_TOO_LARGE` |
| `422` | 입력값은 유효하지만 비즈니스 규칙에 의해 허용되지 않습니다 | `SITE_NOT_PUBLISHED`, `ASSET_IN_USE` |
| `429` | 요청이 너무 많거나 백엔드가 혼잡합니다. 나중에 다시 시도하십시오 | `RATE_LIMITED`, `SEARCH_BUSY`, `ANALYTICS_BUSY` |
| `500` | 예기치 않은 서버 오류가 발생했습니다 | `INTERNAL` |
| `502`, `503` | 업스트림 서비스가 실패했거나 일시적으로 사용할 수 없습니다 | `UPLOAD_FAILED`, `SERVICE_UNAVAILABLE`, `SHARED_SENDER_PAUSED` |

API 레퍼런스의 각 작업에는 반환 가능한 코드와 해당 작업에서의 의미가 나열되어 있습니다. OpenAPI 문서에도 `x-kanorio-errors`로 동일한 목록이 포함되어 있습니다.

## 유효성 검사 오류

`VALIDATION`은 `details.issues` 내의 모든 실패한 필드를 나열합니다. `path`는 점(dot)을 사용하여 중첩 구조를 나타냅니다.

```json
{
  "error": {
    "code": "VALIDATION",
    "message": "Input does not match the capability schema.",
    "details": {
      "issues": [
        { "path": "title", "message": "Required", "code": "invalid_type" }
      ]
    }
  }
}
```

Content Delivery API에서 폼 제출 시 유효하지 않은 답변이 있을 경우 동일한 방식으로 보고되며, `path`에는 질문 ID가 사용됩니다.

## 재시도(Retry)

- `429`, `502`, `503` 오류는 백오프(backoff)를 사용하여 재시도하십시오. 응답에 `Retry-After` 헤더나 `details.retryAfter`가 포함된 경우, 해당 시간(초)만큼 대기하십시오.
- 다른 `4xx` 응답은 수정 없이 재시도하지 마십시오. 먼저 요청을 수정해야 합니다.
- 쓰기 작업 중 `409 CONFLICT`가 발생하면, 읽기 작업 이후 누군가 리소스를 변경했음을 의미합니다. 리소스를 다시 읽고 병합한 다음, 새로운 타임스탬프로 쓰기 요청을 보내십시오.
- 생성 요청은 [멱등성 키](https://kanorio.com/ko/developers/basics/idempotency)를 사용하여 안전하게 재시도할 수 있도록 하십시오.

## 권한 오류

`403` 응답의 `details`에는 무엇이 부족한지 설명되어 있습니다. 예를 들어 토큰에 부족한 `requiredScope`나, 서비스 계정이 계정 수준의 작업을 호출했을 때의 `reason: "principal_not_allowed"` 등이 포함됩니다. 재시도하는 대신 사용자에게 무엇을 수정해야 하는지 안내하십시오.

## 요청 ID(Request IDs)

Content Delivery API와 Customer API 응답에는 `X-Request-Id` 헤더가 포함됩니다. 특정 요청에 대해 지원팀에 문의할 때 이 ID를 함께 제공하십시오.

## 오류 코드

아래 표는 OpenAPI 문서에서 생성된, 각 API가 현재 반환할 수 있는 모든 코드 목록입니다.

### Management API codes

| Code | HTTP status |
| --- | --- |
| `EMPTY_SLUG` | 400 |
| `INVALID_BODY` | 400 |
| `INVALID_CURSOR` | 400 |
| `INVALID_EDITED_PATH` | 400 |
| `INVALID_FORMAT` | 400 |
| `INVALID_ORDER` | 400 |
| `INVALID_SUBDOMAIN` | 400 |
| `UNSUPPORTED_EDITED_PATH` | 400 |
| `VALIDATION` | 400 |
| `UNAUTHENTICATED` | 401 |
| `UNAUTHORIZED` | 401 |
| `CUSTOM_DOMAIN_CHANGE_FORBIDDEN` | 403 |
| `CUSTOM_DOMAIN_LIMIT_REACHED` | 403 |
| `FORBIDDEN` | 403 |
| `LIMIT_REACHED` | 403 |
| `MODERATION_LOCKED` | 403 |
| `OWNER_REQUIRED` | 403 |
| `PLAN_CANNOT_PUBLISH` | 403 |
| `PRO_REQUIRED` | 403 |
| `REDIRECT_LIMIT` | 403 |
| `SUBDOMAIN_CHANGE_FORBIDDEN` | 403 |
| `TOKEN_WEBSITE_SCOPED` | 403 |
| `HEADER_NOT_FOUND` | 404 |
| `HOME_PAGE_NOT_FOUND` | 404 |
| `NOT_FOUND` | 404 |
| `OWNER_NOT_FOUND` | 404 |
| `PAGE_NOT_FOUND` | 404 |
| `PARENT_NOT_FOUND` | 404 |
| `PRODUCT_NOT_FOUND` | 404 |
| `WEBSITE_NOT_FOUND` | 404 |
| `METHOD_NOT_ALLOWED` | 405 |
| `CONFLICT` | 409 |
| `CUSTOM_DOMAIN_TAKEN` | 409 |
| `HAS_CHILDREN` | 409 |
| `HOSTED_ONLY` | 409 |
| `HOSTNAME_CONFLICT` | 409 |
| `IDEMPOTENCY_CONFLICT` | 409 |
| `IDEMPOTENCY_IN_PROGRESS` | 409 |
| `KEY_TAKEN` | 409 |
| `PRIMARY_HEADER_PROTECTED` | 409 |
| `READD_COOLDOWN` | 409 |
| `REDIRECT_CONFLICT` | 409 |
| `SLUG_TAKEN` | 409 |
| `SUBDOMAIN_TAKEN` | 409 |
| `TAKEN` | 409 |
| `BLOCKED` | 422 |
| `BREADCRUMB_PARENT_INVALID` | 422 |
| `CONTENT_VALIDATION_FAILED` | 422 |
| `CORE_SECTION_VIOLATION` | 422 |
| `CUSTOM_DOMAIN_BLOCKED` | 422 |
| `CUSTOM_DOMAIN_HIGH_RISK_TLD` | 422 |
| `CUSTOM_DOMAIN_TOO_LONG` | 422 |
| `DEPTH_EXCEEDED` | 422 |
| `DNS_NOT_PROPAGATED` | 422 |
| `DOMAIN_CHANGE_NOT_ALLOWED` | 422 |
| `EMPTY_CONTENT` | 422 |
| `FOREIGN_PAGE` | 422 |
| `HIGH_RISK_TLD` | 422 |
| `HOME_HAS_HIERARCHY` | 422 |
| `HOME_NOT_CREATABLE` | 422 |
| `HOME_NOT_DELETABLE` | 422 |
| `HOME_NOT_SORTABLE` | 422 |
| `HOME_SLUG_IMMUTABLE` | 422 |
| `LINK_IN_BIO_DEDICATED_ENTRY` | 422 |
| `LINK_IN_BIO_NOT_DUPLICABLE` | 422 |
| `META_NOT_LIVE` | 422 |
| `NO_PUBLIC_URL` | 422 |
| `OWNERSHIP_REQUIRED` | 422 |
| `PAGE_LIMIT_REACHED` | 422 |
| `PARENT_NOT_ALLOWED` | 422 |
| `RESERVED_SEGMENT` | 422 |
| `RESERVED_SLUG` | 422 |
| `RESERVED_SUBDOMAIN` | 422 |
| `SANITIZE_FAILED` | 422 |
| `SEARCH_CONSOLE_AUTH_EXPIRED` | 422 |
| `SEARCH_CONSOLE_NOT_CONNECTED` | 422 |
| `SEARCH_CONSOLE_PERMISSION_DENIED` | 422 |
| `SEARCH_CONSOLE_PROPERTY_NOT_FOUND` | 422 |
| `SEGMENT_TOO_LONG` | 422 |
| `SITEMAP_NOT_REACHABLE` | 422 |
| `SUBDOMAIN_MISSING` | 422 |
| `SYSTEM_PAGE_LOCKED` | 422 |
| `TEMPLATE_PRODUCT_LOCKED` | 422 |
| `ANALYTICS_BUSY` | 429 |
| `DAILY_LIMIT_REACHED` | 429 |
| `RATE_LIMITED` | 429 |
| `SEARCH_BUSY` | 429 |
| `SEARCH_CONSOLE_QUOTA_EXCEEDED` | 429 |
| `STOCK_BUSY` | 429 |
| `INTERNAL` | 500 |
| `SEARCH_CONSOLE_ERROR` | 502 |
| `BUSY` | 503 |
| `SEARCH_CONSOLE_UNAVAILABLE` | 503 |
| `SERVICE_UNAVAILABLE` | 503 |
| `WRITE_FROZEN` | 503 |

### Content Delivery API codes

| Code | HTTP status |
| --- | --- |
| `BAD_HOST` | 400 |
| `FILE_TYPE_NOT_ALLOWED` | 400 |
| `INVALID_ANSWERS` | 400 |
| `INVALID_BODY` | 400 |
| `INVALID_CURSOR` | 400 |
| `INVALID_KIND` | 400 |
| `INVALID_LIMIT` | 400 |
| `INVALID_PATH` | 400 |
| `INVALID_QUERY` | 400 |
| `QUERY_REQUIRED` | 400 |
| `QUERY_TOO_LONG` | 400 |
| `QUERY_TOO_SHORT` | 400 |
| `TOO_MANY_PARAMS` | 400 |
| `UNKNOWN_BOT` | 400 |
| `UNSUPPORTED_LOCALE` | 400 |
| `VALIDATION` | 400 |
| `VERIFICATION_EXPIRED` | 400 |
| `NOT_PREVIEW` | 403 |
| `COLLECTION_NOT_FOUND` | 404 |
| `FORM_NOT_FOUND` | 404 |
| `HEADER_NOT_FOUND` | 404 |
| `ITEM_NOT_FOUND` | 404 |
| `PAGE_NOT_FOUND` | 404 |
| `PRODUCT_NOT_FOUND` | 404 |
| `SITE_NOT_FOUND` | 404 |
| `UNIT_NOT_FOUND` | 404 |
| `DUPLICATE_SUBMISSION` | 409 |
| `FILE_TOO_LARGE` | 413 |
| `RATE_LIMITED` | 429 |
| `SEARCH_BUSY` | 429 |
| `QUERY_TIMEOUT` | 503 |
| `UPLOAD_UNAVAILABLE` | 503 |
| `UPSTREAM_UNAVAILABLE` | 503 |

### Customer API codes

| Code | HTTP status |
| --- | --- |
| `BAD_HOST` | 400 |
| `INVALID_CODE` | 400 |
| `INVALID_CURSOR` | 400 |
| `INVALID_EMAIL` | 400 |
| `INVALID_LIMIT` | 400 |
| `VALIDATION` | 400 |
| `UNAUTHENTICATED` | 401 |
| `BOT_PROTECTION_FAILED` | 403 |
| `DOWNLOAD_NOT_ALLOWED` | 403 |
| `EXPIRED` | 403 |
| `FORBIDDEN` | 403 |
| `LIMIT_REACHED` | 403 |
| `LOCKED` | 403 |
| `NOT_RELEASED` | 403 |
| `NO_ATTEMPTS_LEFT` | 403 |
| `REVIEWS_DISABLED` | 403 |
| `CERTIFICATE_DISABLED` | 404 |
| `DELIVERABLE_NOT_FOUND` | 404 |
| `NOT_FOUND` | 404 |
| `NO_FILE` | 404 |
| `NO_RECEIPT` | 404 |
| `ORDER_NOT_FOUND` | 404 |
| `SITE_NOT_FOUND` | 404 |
| `UNIT_NOT_FOUND` | 404 |
| `WEBSITE_NOT_FOUND` | 404 |
| `METHOD_NOT_ALLOWED` | 405 |
| `ALREADY_REVIEWED` | 409 |
| `FILE_NOT_PROTECTED` | 409 |
| `NOT_A_QUIZ` | 409 |
| `QUIZ_NOT_READY` | 409 |
| `ACCOUNT_BLOCKED` | 422 |
| `ALREADY_REGISTERED` | 422 |
| `CHALLENGE_EXPIRED` | 422 |
| `EMAIL_NOT_VERIFIED` | 422 |
| `METHOD_DISABLED` | 422 |
| `VERIFY_FAILED` | 422 |
| `RATE_LIMITED` | 429 |
| `SHARED_SENDER_PAUSED` | 503 |
| `SIGN_FAILED` | 503 |
| `UPSTREAM_UNAVAILABLE` | 503 |
