LinkHarbor API의 오류 응답이 어떻게 구성되는지, 가장 중요한 오류 코드가 무엇을 뜻하는지, 코드에서 어떻게 대응해야 하는지 설명합니다.
API의 모든 오류 응답은 같은 형식입니다. 코드에서는 message의 텍스트가 아니라 error 필드의 오류 코드를 기준으로 처리하세요. 코드는 변하지 않지만, 텍스트는 바뀔 수 있고 계정 언어로 번역됩니다.
오류 응답의 구조
{
"error": "validation_failed",
"message": "Some of the information provided is invalid. Please check and try again.",
"details": [
{ "field": "title", "reason": "minLength", "message": "…" }
],
"request_id": "6b1f0c2e-…"
} - error
- 기계가 읽을 수 있는 코드입니다. 예: not_found. 대부분의 오류에서는 X-Error-Code 헤더에도 들어 있지만, 본문의 필드를 기준으로 하세요.
- message
- 계정 언어로 된 사람이 읽을 수 있는 설명입니다. API 키에서는 워크스페이스 소유자의 언어, 연결된 앱에서는 연결한 사람의 언어입니다. 토큰 검사의 401처럼 유효한 토큰이 없으면 언어는 Accept-Language를 따르고, 그것도 없으면 영어입니다. Content-Language 헤더가 언어를 알려 줍니다.
- details
- 더 알려 줄 내용이 있을 때만 있습니다. 입력값 검증의 422에서는 문제마다 field, reason, message가 있는 목록이고, 비즈니스 로직의 422와 그 밖의 모든 오류에서는 reason, retry_after_sec, issues 목록 같은 내용이 있는 객체입니다. 크기를 넘으면 field, reason, limit이 들어 있고, 413 request_too_large에서는 max_bytes와 max_kb가 들어 있습니다. 따라서 details가 목록인지 확인하세요.
- request_id
- 요청의 ID입니다. X-Request-Id 헤더에도 있습니다. 지원팀에 문의할 때 함께 알려 주세요.
예외: 429 rate_limited 응답에는 details 대신 retry_after_sec 필드가 있으며, 429와 403 insufficient_scope에서는 본문에 request_id가 없습니다. 토큰 검사의 401과 스키마 검사의 422에는 X-Error-Code 헤더가 없으며, 스키마 검사에서는 최상위 message만 번역되고 details 안의 텍스트는 영어로 남습니다. X-Request-Id 헤더는 항상 설정됩니다.
가장 중요한 코드
| HTTP | error | 의미 | 해야 할 일 |
|---|---|---|---|
| 400 | invalid_json | 본문이 유효한 JSON이 아님 | JSON 확인 |
| 400 | bad_request | 잘못된 Content-Type 또는 형식이 잘못된 요청 | Content-Type: application/json 설정 |
| 401 | missing_bearer_token | Authorization 헤더에 토큰이 없음 | Authorization: Bearer … 헤더 설정 |
| 401 | invalid_token | 토큰이 만료되었거나 잘못되었거나, 키가 삭제됨 | 새 토큰을 받고, 그래도 안 되면 키 확인 |
| 401 | invalid_api_key | 토큰 교환에서 키 ID나 시크릿이 틀림 | 인증 정보 확인 |
| 402 | plan_limit_reached | 단축 링크 등의 플랜 한도에 도달 | 업그레이드하거나 항목 삭제 |
| 403 | insufficient_scope | 스코프가 없거나 API 키에 허용되지 않은 엔드포인트 | 알맞은 스코프로 키 만들기 |
| 403 | forbidden | 이 작업에 대한 권한 없음 | 대시보드에서 하기 |
| 403 | feature_not_available | 플랜에 포함되지 않은 기능 | 플랜 확인 |
| 403 | block_type_requires_plan | 더 높은 플랜에서만 쓸 수 있는 블록 유형 | 다른 블록 유형을 고르거나 업그레이드 |
| 403 | template_locked | 현재 플랜에서 쓸 수 없는 프리미엄 템플릿 | GET /v1/templates/available에서 템플릿 고르기 |
| 403 | workspace_suspended, workspace_read_only, workspace_grace_expired | 미결제로 워크스페이스가 잠겼거나 읽기 전용 | 대시보드에서 결제 해결 |
| 404 | not_found | 리소스가 없거나 내 워크스페이스에 속하지 않음 | ID 확인 |
| 409 | conflict | 현재 상태와의 일반적인 충돌 | 상태를 다시 불러오고 입력 변경 |
| 409 | workspace_template_in_use | 워크스페이스 템플릿을 아직 페이지가 사용 중. 개수는 details.count에 있음 | 페이지를 다른 템플릿으로 바꾼 뒤 삭제 |
| 409 | handle_taken, slug_taken | 핸들이나 단축 링크 슬러그가 이미 사용 중 | 다른 값 고르기 |
| 409 | form_in_use, page_not_private, page_not_published, never_published | 상태와의 충돌: 폼이 아직 페이지에 있음, 페이지가 비공개가 아니거나 게시되지 않음, 한 번도 게시되지 않음 | 상태를 바꾼 뒤 다시 시도 |
| 409 | page_limit_reached, link_limit_reached, block_limit_reached, form_limit_reached, asset_limit_reached, asset_storage_limit_reached, workspace_template_limit_reached | 플랜의 개수 또는 저장 공간 한도에 도달 | 항목을 제거하거나 업그레이드 |
| 409 | draft_not_publishable | 초안에 문제가 있음. 목록은 details.issues에 있음 | 문제를 고친 뒤 다시 게시 |
| 413 | request_too_large | 본문이 이 엔드포인트의 허용 크기(64 KB, 1 MB, 2 MB)보다 큼. 한도는 details.max_kb에 있음 | 요청당 데이터를 줄이기(요청 한도 참고) |
| 413 | payload_too_large | 업로드한 파일이 10 MB보다 큼 | 파일 크기 줄이기 |
| 422 | validation_failed | 잘못된 입력 또는 알 수 없는 필드. 자세한 내용은 details에 있음 | details에 설명된 대로 필드 수정 |
| 422 | handle_reserved, target_url_invalid, target_url_scheme, target_url_host | 예약된 핸들, 또는 잘못되었거나 허용되지 않은 대상 URL | 다른 값 고르기 |
| 422 | workspace_template_disabled | 선택한 워크스페이스 템플릿이 비활성화됨 | “도메인 및 템플릿”에서 템플릿을 활성화하거나 다른 템플릿 고르기 |
| 422 | template_css_external, template_section_invalid | 워크스페이스 템플릿: CSS가 외부 서버에서 불러오거나, {{#if}}/{{#unless}}/{{#each}} 섹션의 위치가 잘못됨 | 외부 주소를 제거하거나, 같은 태그나 텍스트 안에서 섹션을 닫기 |
| 422 | custom_css_external | 커스텀 CSS가 외부 서버에서 무언가를 불러옴 | CSS에서 외부 주소 제거 |
| 429 | rate_limited, api_key_locked | 요청이 너무 많거나, 실패한 시도 후 키가 잠김 | Retry-After 또는 details.retry_after_sec만큼 기다리기 |
| 500 | internal_error | 예기치 않은 오류 | 나중에 다시 시도하고 request_id 기록 |
| 503 | service_unavailable | 서비스를 일시적으로 쓸 수 없음 | 잠시 쉰 뒤 다시 시도 |
입력 오류 읽기
422 validation_failed에서는 details에 모든 문제가 따로 나열됩니다. field는 필드의 경로(중첩된 객체는 슬래시로 구분. 예: theme_config/button_style)이고, reason은 어긴 규칙(required, minLength, maxLength, enum, additionalProperties 등)입니다. additionalProperties에서는 message가 어떤 필드를 모르는지 알려 줍니다. 필드 이름의 오타인 경우가 많습니다.
코드에서 권장하는 처리
- 400, 413, 422: 다시 시도하지 말고 요청을 고치기
- 401 invalid_token: 새 토큰을 한 번 받고 다시 시도
- 403: 다시 시도하지 말고 스코프, 플랜, 누가 이 작업을 할 수 있는지 확인
- 404: ID 확인. 삭제한 뒤라면 정상
- 409: 상태를 다시 불러오고 작업이 아직 필요한지 판단
- 429: Retry-After만큼 기다린 뒤 다시 시도(요청 한도 참고)
- 500과 503: 간격을 늘려 가며 몇 번 다시 시도
함께 보면 좋은 글
원하는 내용을 찾지 못했나요?
필요한 내용을 알려 주시면 지원팀이 직접 도와드립니다.