---
title: 에러 코드
description: v1 API 응답의 code 값마다 HTTP 상태와 발생 조건을 정리했어요.
sidebar:
  order: 2
---

v1 API 응답의 `code`는 처리 결과를 나타내는 문자열이에요. v2 API의 숫자 코드는 [에러 코드](/api-reference/error-codes)를 참고하세요. 오류 응답에서는 `data`가 `null`이에요.

```json
{
  "code": "INVALID_AMOUNT",
  "data": null
}
```

엔드포인트별로 나올 수 있는 코드는 각 API 페이지에도 적혀 있어요.

## 성공 (2xx)

| `code` | HTTP | 설명 |
| --- | --- | --- |
| `OK` | 200 | 요청을 처리했어요. |
| `CREATED` | 201 | 리소스를 만들었어요. |
| `DELETED` | 200 | 리소스를 삭제했어요. |

## 인증 (401)

| `code` | HTTP | 설명 |
| --- | --- | --- |
| `NOT_AUTHORIZED` | 401 | `Authorization` 헤더가 없거나, 키가 틀렸거나, 만료되거나 삭제되거나 비활성화된 키예요. |

## 권한과 상태 (403)

| `code` | HTTP | 설명 |
| --- | --- | --- |
| `INSUFFICIENT_PERMISSIONS` | 403 | API 키의 [계좌 범위](/api-reference/v1#api-키-계좌-제한) 밖의 주문이나 입금에 접근했거나, 읽기 전용 키로 쓰기 요청을 보냈거나, API 키가 아닌 로그인 토큰으로 요청했어요. |
| `LIMIT_EXCEEDS_MAXIMUM` | 403 | 목록 조회의 `limit`이 100을 넘었어요. |
| `INVOICE_ALREADY_PAID` | 403 | 이미 결제 완료된 주문을 삭제하거나 결제 완료 처리하려고 했어요. |
| `BANK_ACCOUNT_NOT_VERIFIED` | 403 | 주문의 `bankAccountIds`에 1원 인증을 마치지 않은 계좌가 있어요. |

## 리소스 없음 (404)

| `code` | HTTP | 설명 |
| --- | --- | --- |
| `INVOICE_NOT_FOUND` | 404 | 주문이 없거나 삭제된 주문이에요. |
| `TRANSACTION_NOT_FOUND` | 404 | 입금이 없어요. |
| `CASH_RECEIPT_NOT_FOUND` | 404 | 현금영수증이 없어요. |
| `BANK_ACCOUNT_NOT_FOUND` | 404 | 주문의 `bankAccountIds`에 없는 계좌 ID가 있어요. |
| `BUSINESS_INFO_NOT_REGISTERED` | 404 | 현금영수증을 발급하려면 대시보드 **설정 > 일반**에서 사업자 정보를 먼저 등록해야 해요. |

## 충돌 (409)

| `code` | HTTP | 설명 |
| --- | --- | --- |
| `INVOICE_ALREADY_EXISTS` | 409 | 입금자명과 금액이 같고 받을 계좌가 겹치는 결제 대기 주문이 이미 있어요. [주문 충돌 규칙](/multi-bank-account-routing#주문-충돌-규칙)을 참고하세요. |
| `CASH_RECEIPT_NOT_REVOCABLE` | 409 | 취소할 수 없는 현금영수증이에요. `ISSUED` 상태만 취소할 수 있어요. |

## 검증 실패 (422)

### 목록 조회

| `code` | HTTP | 설명 |
| --- | --- | --- |
| `INVALID_OFFSET` | 422 | `offset`이 0보다 작아요. |
| `INVALID_LIMIT` | 422 | `limit`이 0 이하예요. |
| `INVALID_DATE_RANGE_FORMAT` | 422 | `dateAfter`나 `dateBefore`가 `YYYY-MM-DD` 형식이 아니에요. |

### 주문 생성

| `code` | HTTP | 설명 |
| --- | --- | --- |
| `INVALID_CUSTOMER_NAME` | 422 | `customer.name`이 1~16자가 아니거나 공백이 있어요. |
| `INVALID_EMAIL` | 422 | `customer.email`이 5~255자가 아니거나 이메일 형식이 아니에요. |
| `INVALID_PHONE_NUMBER` | 422 | `customer.phoneNumber`가 숫자 10~11자리가 아니에요. |
| `INVALID_AMOUNT` | 422 | `amount`가 0 이하예요. |
| `INVALID_DURATION` | 422 | `expireAfter`를 기간으로 읽을 수 없어요. |
| `EXPIRE_AFTER_EXCEEDS_MAXIMUM` | 422 | `expireAfter`가 365일을 넘어요. |
| `METADATA_KEY_VALUE_PAIR_LIMIT_EXCEEDED` | 422 | `metadata`가 5쌍을 넘어요. |
| `METADATA_KEY_LENGTH_LIMIT_EXCEEDED` | 422 | `metadata` 키가 64자를 넘어요. |
| `METADATA_VALUE_LENGTH_LIMIT_EXCEEDED` | 422 | `metadata` 값이 1024자를 넘어요. |
| `INVALID_CASH_RECEIPT_IDENTIFIER` | 422 | `cashReceipt.identifier`가 `cashReceipt.type`에 맞지 않아요. |

### 현금영수증 발급

| `code` | HTTP | 설명 |
| --- | --- | --- |
| `INVALID_CASH_RECEIPT_IDENTIFIER` | 422 | `identifier`가 `type`에 맞지 않아요. 소득공제용은 휴대폰 번호나 현금영수증 카드 번호 숫자 10~18자리, 지출증빙용은 사업자등록번호 숫자 10자리예요. |
| `INVALID_CASH_RECEIPT_AMOUNT` | 422 | `amount`가 1원부터 999,999,999원 사이가 아니에요. |

## 서버 오류 (5xx)

| `code` | HTTP | 설명 |
| --- | --- | --- |
| `INTERNAL_SERVER_ERROR` | 500 | 서버에서 오류가 났어요. 잠시 뒤 다시 시도하세요. |
| `POPBILL_API_FAILED` | 500 | 현금영수증 발급 대행사(팝빌) 호출이 실패했어요. 현금영수증 발급과 취소에서 나와요. |

## 요청 형식 오류 (400)

| `code` | HTTP | 설명 |
| --- | --- | --- |
| `BAD_REQUEST` | 400 | JSON 본문을 읽을 수 없거나, 필수 본문 필드가 없거나, `type`, `status` 같은 열거형 값이 목록에 없거나, `paid` 같은 불리언 쿼리가 `true`, `false`가 아니에요. |

```json
{
  "code": "BAD_REQUEST",
  "data": null
}
```

## 코드 이름 규칙

- `INVALID_*`: 요청 값이 검증을 통과하지 못했어요. 대부분 422예요.
- `*_NOT_FOUND`: 리소스가 없어요. 404예요.
- `*_ALREADY_*`: 이미 처리된 상태라 다시 처리할 수 없어요. 403이나 409예요.
- `*_EXCEEDS_MAXIMUM`, `*_LIMIT_EXCEEDED`: 허용 범위를 넘었어요.

## 처리 예시

```js Node.js
const res = await fetch(url, options);
const { code, data } = await res.json();

switch (code) {
  case "OK":
  case "CREATED":
  case "DELETED":
    return data;

  case "NOT_AUTHORIZED":
    throw new Error("API 키를 확인하세요.");

  case "INVALID_AMOUNT":
  case "INVALID_CUSTOMER_NAME":
  case "INVALID_EMAIL":
  case "INVALID_PHONE_NUMBER":
    throw new ValidationError(code);

  case "INTERNAL_SERVER_ERROR":
  case "POPBILL_API_FAILED":
    return retryLater();

  default:
    // 새로 추가된 코드일 수 있어요. 기록하고 일반 오류로 처리하세요.
    logger.warn({ code }, "unknown PaySync code");
    throw new Error(`PaySync error: ${code}`);
}
```
