오류 코드
실패한 요청은 error 객체로 답합니다. 코드로 분기하고, 재시도 여부는 응답이 알려 줍니다.
오류 응답에는 error 객체가 있습니다. code 로 프로그램이 분기하고, message 는 사람이 읽는 설명이라 로그와 서비스문의에 씁니다.
retryable 이 true 면 같은 요청을 다시 보낼 의미가 있고, false 면 요청이나 설정을 고쳐야 합니다. 같은 code 안에서 상황을 더 가르고 싶을 때만 선택 필드 reason 을 봅니다.
사유 칸이 없는 응답 (HTTP 429)
{
"error": {
"code": "POLICY_QUOTA_EXCEEDED",
"message": "데모 환경의 오늘 쓸 수 있는 사용 토큰을 모두 썼습니다. 9월 11일 0시(한국 시간)에 다시 채워지며, 기다리는 데 비용은 들지 않습니다. 정식 환경은 이 한도를 세지 않으며 PRO 또는 멀티계정 요금제에서 쓸 수 있습니다.",
"retryable": false,
"provider_error_ref": null,
"trace_id": "trc_5f0c2a9e8d7b4c1f9a3e6b2d4c8f1a07"
}
}reason 이 붙는 응답 (HTTP 403)
{
"error": {
"code": "POLICY_ENV_FORBIDDEN",
"message": "데모 환경은 사용 신청이 승인된 뒤 쓸 수 있습니다. 콘솔 프로필의 환경 메뉴에서 신청해 주세요.",
"retryable": false,
"provider_error_ref": null,
"trace_id": "trc_5f0c2a9e8d7b4c1f9a3e6b2d4c8f1a07",
"reason": "demo_access_not_approved"
}
}오류 본문의 모양
| 본문 모양 | 이럴 때 | error 를 읽는 곳 |
|---|---|---|
| {"error": {...}} | 오류 응답의 기본 모양입니다. 인증, 권한, 요금제, 환경, 한도 검사에서 거절된 요청과 작업 제출과 조회 오류가 모두 이 모양이고, 위 예시가 그렇습니다. Idempotency-Key 를 빠뜨렸을 때의 IDEMPOTENCY_KEY_REQUIRED 나 없는 작업을 조회할 때의 JOB_NOT_FOUND 도 같습니다. | error.code |
| {"error": {..., "fields": [...]}} (HTTP 422) | 요청 본문의 필드 형식이 틀렸을 때입니다(필수 필드 빠짐, 문자열이어야 할 값이 객체나 숫자). error.code 는 VALIDATION_INVALID_FIELD 이고, error.fields 배열의 각 항목이 어느 필드가 왜 틀렸는지 알려 줍니다. 보낸 값은 되돌려 주지 않습니다. | error.code, 필드별 안내는 error.fields 항목의 loc(위치)와 msg(이유) |
| {"detail": "문자열"} | 없는 주소의 404 처럼 오류 코드가 없는 응답입니다. error 는 없습니다. | HTTP 상태와 detail 문장을 그대로 기록 |
코드에서는 본문 최상위 error 를 읽으면 됩니다. error 가 없으면(없는 주소의 404 등) HTTP 상태와 detail 문장을 그대로 기록합니다.
응답 필드
| 필드 | 설명 |
|---|---|
| code | 아래 표에 있는 값 중 하나입니다. 분기는 이 값으로 합니다. |
| message | 사람이 읽는 문장입니다. 키·권한·요금제·환경·한도·동의 관련 거절은 바로 읽을 수 있는 한국어로 오고, 그 밖의 오류는 원인을 적은 기술 문구(영문일 수 있음)로 올 수 있습니다. 문구는 바뀔 수 있어 분기에 쓰지 않습니다. 최종 사용자 화면에는 code 에 맞춘 자체 문구를 보여 주세요. |
| retryable | 같은 요청을 다시 보낼 의미가 있는지 알려 줍니다. 이 값이 최종입니다 — 되돌릴 수 없는 작업(발행·신고·신청)의 실패는 아래 코드별 표와 달리 항상 false 로 옵니다. |
| provider_error_ref | 실패한 지점을 가리키는 추적 값입니다. 보통 상품이름.원인 꼴이고(예: hometax.read_timeout), 없으면 null 입니다. |
| trace_id | 이 요청의 추적 아이디입니다. 서비스문의에 남길 때 그대로 알려 주세요. |
| fields | 선택. HTTP 422 요청 본문 검증 오류에만 있습니다. 어느 필드가 왜 틀렸는지 알려 주는 목록이고 항목마다 loc(위치), msg(이유), type(종류)이 있습니다. 보낸 값은 되돌려 주지 않습니다. |
| reason | 선택. 같은 code 가 여러 상황을 뜻할 때 상황을 가르는 기계용 값입니다. 화면에 보여 주는 값이 아니고, 해당하는 오류에만 있으며 없으면 칸 자체가 없습니다. code 로 먼저 분기하고 더 세분해야 할 때만 보세요. MCP 는 JSON-RPC 오류의 error.data.reason 에 같은 값이 실립니다. |
reason 값
| 코드 | reason | 언제 |
|---|---|---|
| POLICY_ENV_FORBIDDEN | key_env_not_allowed | 요청한 환경이 이 키에 없습니다. 정식은 유료 요금제를, 데모는 사용 신청 승인을 따라 기존 키에 더해지므로 키를 다시 발급하지 않아도 됩니다. |
| POLICY_ENV_FORBIDDEN | demo_access_not_approved | 데모(real_test) 사용 신청이 승인되기 전에 호출했습니다. 콘솔 프로필의 환경 메뉴에서 신청합니다. |
| POLICY_ENV_FORBIDDEN | organization_required | 데모 환경 요청에는 조직 정보가 필요한데 조직에 속하지 않은 계정으로 호출했습니다. |
| POLICY_PAID_REQUIRED | production_requires_paid | 유료 요금제를 이용 중이 아닌데 정식(production) 환경을 호출했습니다. 스탠다드는 데모 환경에서 그대로 쓸 수 있습니다. |
| POLICY_PAID_REQUIRED | subscription_paused | 결제가 일시 중지되어 요청을 처리할 수 없습니다. 콘솔 결제 관리에서 결제 상태를 확인합니다. |
| POLICY_PAID_REQUIRED | invalid_subscription_status | 요금제 상태를 확인하지 못했습니다. 잠시 뒤 다시 시도하고, 계속되면 서비스문의로 알려 주세요. |
| AUTHZ_FORBIDDEN | oauth_scope_missing | MCP 연결(OAuth)에 도구를 실행할 권한이 없습니다. AI 클라이언트에서 연결을 다시 승인합니다. |
| AUTHZ_FORBIDDEN | oauth_token_mcp_only | AI 앱 연결(MCP)용으로 받은 OAuth 토큰을 서버 연동(REST)에 썼습니다. 다시 승인해도 풀리지 않으며, 서버 연동에는 콘솔에서 발급한 API 키(HTTP Basic)를 씁니다. |
| POLICY_PROVIDER_FORBIDDEN | available_in_other_env | 이 요금제에 있는 상품인데 요청한 환경에서는 쓸 수 없습니다. 요금제를 바꿀 필요 없이, 메시지가 알려 주는 환경(샌드박스 sandbox, 데모 real_test, 정식 production 중 하나)의 X-Env-Scope 값으로 바꿔 다시 보냅니다. |
| POLICY_PROVIDER_FORBIDDEN | not_sold_yet | 아직 제공하지 않는 기능을 호출했습니다. 어느 요금제에도 아직 없는 기능입니다. 제공 계획은 서비스문의로 문의합니다. |
| POLICY_PROVIDER_FORBIDDEN | sale_ended | 제공이 종료된 기능을 호출했습니다. 팔다가 내린 기능입니다. 대신 쓸 수 있는 기능이 있는지 서비스문의로 문의합니다. |
| RESOURCE_CONFLICT | material_replacement_not_confirmed | 이미 등록된 계정과 같은 아이디인데 비밀번호가 다릅니다. 바꾸려는 것이 맞다면 요청 본문에 confirm 을 true 로 넣어 다시 보냅니다. 그대로 진행하면 저장된 비밀번호가 바뀌고 그 연동으로 열어 둔 기관 세션이 모두 끊어집니다. |
표에 없는 reason 이 오면 무시하고 code 만으로 처리합니다 — 값이 더해질 수 있습니다. 콘솔 화면이 쓰는 값(조직·멤버·초대·환경 신청)은 개발자 API 로 오지 않아 싣지 않았습니다.
이 문서에는 아직 판매하지 않는 기능도 미리 실려 있습니다. 그런 기능을 부르면 POLICY_PROVIDER_FORBIDDEN 에 reason not_sold_yet 로 거절되고, 팔다가 내린 기능은 sale_ended 로 거절됩니다. 둘 다 요금제를 바꾸거나 추가 구매해도 풀리지 않습니다 - 살 수 있는 요금제가 없는 기능이라서입니다. 이 단락은 문서의 설명이고 서버가 보내는 문장이 아닙니다. 서버 문장은 응답의 message 를 그대로 읽습니다.
자주 만나는 오류
| 코드 | HTTP | 재시도 | 할 일 |
|---|---|---|---|
| AUTH_CREDENTIAL_MISSING | 401 | 무의미 | Authorization 헤더를 넣습니다. |
| AUTH_CREDENTIAL_INVALID | 401 | 무의미 | client_id 와 client_secret, Base64 인코딩을 확인하고, 폐기된 키라면 콘솔에서 새 키로 바꿉니다. |
| POLICY_ENV_FORBIDDEN | 403 | 무의미 | X-Env-Scope 값과 키가 쓸 수 있는 환경을 맞춥니다. |
| POLICY_PAID_REQUIRED | 402 | 무의미 | 정식 환경은 유료 플랜을 이용 중일 때만 호출할 수 있습니다. |
| POLICY_QUOTA_EXCEEDED | 429 | 무의미 | 데모 일일 한도입니다. 다음 날 0시(KST)에 초기화됩니다. |
| POLICY_RATE_LIMIT_EXCEEDED | 429 | 가능 | 잠시 기다렸다 다시 호출합니다. |
| IDEMPOTENCY_KEY_REQUIRED | 400 | 무의미 | Idempotency-Key 헤더를 넣습니다. |
| IDEMPOTENCY_KEY_CONFLICT | 409 | 무의미 | 내용이 다른 요청이므로 새 키로 보냅니다. |
| VALIDATION_INVALID_FIELD | 400, 422 | 무의미 | 본문·헤더 형식과 필드 이름 규칙을 확인합니다. HTTP 422 이면 error.fields 가 틀린 필드를 알려 줍니다. 작업이 접수(202)된 뒤 이 코드로 끝나고 provider_error_ref 가 missing_account_link_id 로 끝나면, 본문이 아니라 등록한 자격증명이 없는 것입니다. |
| RESOURCE_NOT_FOUND | 404 | 무의미 | account_link_id 가 이 키를 만든 사용자가 등록한 것이고 아직 사용 중인지 확인합니다. |
| JOB_NOT_FOUND | 404 | 무의미 | 작업 아이디와 X-Env-Scope 가 제출할 때와 같은지 확인합니다. |
| JOB_INVALID_TRANSITION | 409 | 무의미 | 작업이 SUCCEEDED 가 된 뒤에 결과를 조회합니다. |
| PROVIDER_AUTH_FAILED | 502 | 무의미 | 등록한 자격증명이 아직 유효한지 확인하고 다시 등록합니다. |
| PROVIDER_SESSION_EXPIRED | 502 | 무의미 | 세션 로그인 작업을 다시 실행한 뒤 이어서 호출합니다. |
| PROVIDER_OUTCOME_UNCONFIRMED | 502 | 무의미 | 다시 보내지 말고 기관 화면에서 처리 여부를 먼저 확인합니다. |
| COMPLIANCE_TOS_VERSION_OUTDATED | 403 | 무의미 | 콘솔 기본정보에서 약관에 다시 동의합니다. |
PROVIDER_OUTCOME_UNCONFIRMED 는 되돌릴 수 없는 작업(발행·신고·신청)을 보냈는데 기관이 받았는지 확인하지 못한 상태입니다. 다시 보내면 두 번 처리될 수 있으니, 오류 메시지에 확인할 기관 화면이 적혀 있으면 그 화면에서, 없으면 해당 기관 사이트에서 결과를 먼저 확인합니다.
전체 코드
아래 표는 호출자가 받을 수 있는 코드 전부입니다. 결제·운영 화면 전용 코드는 넣지 않았습니다.
표의 재시도 값은 코드 자체의 성격입니다. 되돌릴 수 없는 작업(발행·신고·신청)이 실패하면 요청이 이미 기관에 닿았을 수 있어, 응답의 retryable 은 그와 상관없이 false 로 옵니다 — 응답 값을 따르세요.
인증·권한
| 코드 | HTTP | 재시도 | 설명 |
|---|---|---|---|
| AUTH_CREDENTIAL_MISSING | 401 | 무의미 | 인증 헤더 누락 |
| AUTH_CREDENTIAL_INVALID | 401 | 무의미 | client_credential 또는 JWT 위조/만료 |
| AUTH_JWT_EXPIRED | 401 | 무의미 | JWT 만료(콘솔 사용자/관리자) |
| AUTH_REQUIRED | 401 | 무의미 | 기관 세션 재료(쿠키/토큰) 없음 — MCP session login 필요 |
| AUTHZ_FORBIDDEN | 403 | 무의미 | 자원 접근 권한 없음 |
| AUTHZ_ADMIN_ROLE_REQUIRED | 403 | 무의미 | 운영자 역할 필요 |
정책·한도
| 코드 | HTTP | 재시도 | 설명 |
|---|---|---|---|
| POLICY_TIER_FORBIDDEN | 403 | 무의미 | 지금 요금제에서는 쓸 수 없는 요청입니다. |
| POLICY_ENV_FORBIDDEN | 403 | 무의미 | 이 키로 쓸 수 없는 환경(샌드박스, 데모, 정식 중 하나)을 호출했습니다. |
| POLICY_PAID_REQUIRED | 402 | 무의미 | 정식 환경을 쓸 수 있는 유료 요금제(PRO 또는 멀티계정 요금제)가 아니거나 결제가 일시 중지된 상태입니다. |
| POLICY_QUOTA_EXCEEDED | 429 | 무의미 | 테스트 환경 일 100 사용 토큰 한도 초과(KST 00:00 리셋). 단위는 회가 아니다 — 액션마다 깎이는 토큰이 다르고 기본값은 1 이다 |
| POLICY_RATE_LIMIT_EXCEEDED | 429 | 가능 | 분당 요청 수 한도 초과 |
| POLICY_PROVIDER_FORBIDDEN | 403 | 무의미 | 요금제/상품에서 허용되지 않는 provider/action |
| POLICY_FEATURE_DISABLED | 403 | 무의미 | Feature Flag 비활성 |
요청 검증
| 코드 | HTTP | 재시도 | 설명 |
|---|---|---|---|
| RESOURCE_NOT_FOUND | 404 | 무의미 | 자원 미존재 |
| RESOURCE_CONFLICT | 409 | 무의미 | 자원 상태 충돌 |
| RESOURCE_GONE | 410 | 무의미 | 자원이 폐기됨(soft delete 등) |
| VALIDATION_INVALID_FIELD | 400, 422 | 무의미 | 형식이나 타입이 틀렸습니다. 상태가 둘이고 뜻이 다릅니다: 라우트나 도메인 검사에서 걸리면 400, 요청 본문 검증에서 걸리면 422 이고 error.fields 에 필드별 안내가 옵니다. 코드만 보고 분기하지 말고 HTTP 상태도 함께 보세요. |
| VALIDATION_MISSING_FIELD | 400 | 무의미 | 필수 필드 누락 |
| VALIDATION_SEMANTIC_ERROR | 422 | 무의미 | 형식은 맞으나 의미가 잘못됨 |
| VALIDATION_ENV_SCOPE_MISMATCH | 422 | 무의미 | env_scope가 자원/요청 간 불일치 |
| CREDENTIAL_PAYLOAD_UNREADABLE | 400 | 가능 | 연동 저장 때 암호화된 입력을 서버 키로 풀지 못함(오래 열어 둔 화면이 옛 공개키로 암호화 등 - 입력 오류가 아니다). 화면을 새로 고친 뒤 다시 입력하면 풀린다 |
| CREDENTIAL_OWNERSHIP_DENIED | 400 | 무의미 | 지정한 연동 계정(account_link_id)을 찾을 수 없거나 이 계정의 것이 아님. 상품 로그인 라우트(400)와 Job 실행 중 확인(FAILED)에서 난다. 콘솔 연동 목록의 계정 ID 를 확인한다. Job 제출 때의 같은 확인은 존재 여부를 숨기려고 RESOURCE_NOT_FOUND(404)다 |
자격증명 한도
| 코드 | HTTP | 재시도 | 설명 |
|---|---|---|---|
| ACCOUNT_LINK_QUOTA_EXCEEDED | 409 | 무의미 | 통합사(desktop_agent) Plan의 max_active_account_links 시트 한도 초과(용량 충돌 — 시간창 레이트리밋이 아니므로 429 대신 409, SeatLimitPort, AccountLinkService.link) |
멱등성·작업
| 코드 | HTTP | 재시도 | 설명 |
|---|---|---|---|
| IDEMPOTENCY_KEY_CONFLICT | 409 | 무의미 | 같은 Idempotency-Key로 다른 본문이 들어옴 |
| IDEMPOTENCY_KEY_REQUIRED | 400 | 무의미 | 멱등성 요구 엔드포인트에 키 누락 |
| JOB_INVALID_TRANSITION | 409 | 무의미 | 허용되지 않는 상태 전이 |
| JOB_NOT_FOUND | 404 | 무의미 | 존재하지 않는 job_id |
| JOB_TIMEOUT | 504 | 가능 | Job 처리 타임아웃(전체 시한 초과) |
| JOB_ACTION_NOT_REGISTERED | 400 | 무의미 | 미등록 (provider, action) 쌍 |
쓰기 확인
| 코드 | HTTP | 재시도 | 설명 |
|---|---|---|---|
| WRITE_CONFIRMATION_REQUIRED | 400 | 무의미 | 확인 발급물 없이 쓰기를 요청했다. 응답의 error.confirmation_token 에 새 토큰, error.preview 에 미리보기가 실린다 — 그 내용을 고객에게 보여 주고 같은 본문의 params.confirmation_token 에 그 토큰을 넣어 다시 호출한다. MCP 는 -32602 + data.confirmation_token |
| WRITE_CONFIRMATION_INVALID | 409 | 무의미 | 없음 · 만료(10분) · 이미 소비 · 본문/호출자/plane 불일치. 어느 조건인지 알리지 않는다(오라클 금지). 토큰 없이 다시 호출하면 새 확인을 받는다 |
외부 기관
| 코드 | HTTP | 재시도 | 설명 |
|---|---|---|---|
| PROVIDER_TIMEOUT_CONNECT | 504 | 가능 | connect 단계 타임아웃 |
| PROVIDER_TIMEOUT_READ | 504 | 가능 | read 단계 타임아웃 |
| PROVIDER_TIMEOUT_OVERALL | 504 | 가능 | 전체 시한 초과 |
| PROVIDER_IP_REJECTED | 502 | 가능 | 기관에서 IP 차단/리젝(403/429/connection_reset/tls_fail) |
| PROVIDER_UNAVAILABLE | 503 | 가능 | 기관 서비스 장애 |
| PROVIDER_BAD_RESPONSE | 502 | 무의미 | 기관 응답이 계약과 불일치 |
| PROVIDER_AUTH_FAILED | 502 | 무의미 | 기관 자체 인증 실패(자격증명 만료 등) |
| PROVIDER_SESSION_EXPIRED | 502 | 무의미 | 기관 세션 만료(예: NPS Nexacro ErrorCode=-2001, NHIS MainNoXecure HTTP 2xx + 빈 본문) |
| PROVIDER_BUSINESS_ERROR | 200 | 무의미 | 기관 HTTP 200 + 비즈 거부 |
| PROVIDER_VALIDATION_ERROR | 200 | 무의미 | 기관 4xx 검증 거부 |
| PROVIDER_UPSTREAM_ERROR | 200 | 가능 | 기관 5xx 재시도 소진 후 FAILED |
| PROVIDER_OUTCOME_UNCONFIRMED | 502 | 무의미 | 되돌릴 수 없는 기관 쓰기(발행·신고·신청)를 보냈으나 승낙도 거절도 확인하지 못함. 재시도 금지 — 이미 처리됐을 수 있어 이중 처리가 된다. 메시지에 확인 화면을 싣는다 |
| PROVIDER_PREREQUISITE_REQUIRED | 409 | 무의미 | 고객이 기관 쪽에서 아직 하지 않은 일(가입 · 이용 동의 · 인증서 등록)이 있어 못 한다. 보낸 값은 맞으므로 400 이 아니고, 우리 쪽에서 기다린다고 풀리지 않으므로 재시도도 아니다. 어느 코드가 그것인지와 고객에게 할 말은 상품이 선언한다 |
점검·내부
| 코드 | HTTP | 재시도 | 설명 |
|---|---|---|---|
| INTERNAL_ERROR | 500 | 무의미 | 분류 불가 내부 오류 |
| INTERNAL_DB_ERROR | 500 | 가능 | DB 처리 실패 |
| INTERNAL_INVARIANT_VIOLATED | 500 | 무의미 | 불변식 위반(트리거/AuthPolicy 방어, severity=CRITICAL) |
| MAINTENANCE_UPSTREAM | 503 | 가능 | 상류 의존 점검 |
| SERVICE_UNAVAILABLE | 503 | 가능 | 그 환경에 아직 활성 암호화 키가 없다 |
약관·컴플라이언스
| 코드 | HTTP | 재시도 | 설명 |
|---|---|---|---|
| COMPLIANCE_TOS_VERSION_OUTDATED | 403 | 무의미 | 필수 약관 미동의 또는 consent_version 불일치 |
| COMPLIANCE_PAYMENT_CONSENT_MISSING | 403 | 무의미 | 결제 도메인 진입 시 결제 관련 약관 미동의 |
| COMPLIANCE_PROVIDER_CONSENT_MISSING | 403 | 무의미 | (레거시) 외부 기관 provider_data_share 미동의 |
| COMPLIANCE_DELETION_IN_PROGRESS | 403 | 무의미 | 주체가 진행 중인 삭제 요청에 묶여 호출 차단 |
| COMPLIANCE_INVALID_TRANSITION | 409 | 무의미 | 컴플라이언스 리소스의 비허용 상태 전이 시도 |
| COMPLIANCE_DUPLICATE_REQUEST | 409 | 무의미 | 동일 target에 활성 컴플라이언스 요청 존재 |
| COMPLIANCE_SOLE_OWNER_BLOCK | 409 | 무의미 | 단독 OWNER 사용자 삭제 요청 승인 시도 차단 |
| COMPLIANCE_RETENTION_PURGE_FAILED | 500 | 무의미 | deletion-triggered retention purge 실행 실패 |
작업이 실패했을 때
작업이 FAILED 나 TIMEOUT 으로 끝나면 상태 응답의 error 에 code · message · retryable · provider_error_ref 가 같은 뜻으로 담깁니다. trace_id 대신 기관 쪽 HTTP 상태(upstream_status)가 붙습니다. HTTP 응답이 200 이어도 작업은 실패일 수 있으니 상태를 함께 확인합니다 — 작업 처리 흐름.