AlgoLab Blog · 업비트 API 실무 · 2026

업비트 API 에러코드 24개 — 재시도할 것과 멈출 것

업비트 API · 에러 2026-10-03 · 약 8분 읽기 · 알고랩 AlgoLab
한 줄 요약

업비트 API 에러는 HTTP 상태코드가 아니라 응답 JSON의 error.name 문자열로 판정해야 합니다. 주문 계열 400(under_min_total_bid·insufficient_funds_bid·create_bid_error 등)과 인증 계열 401(invalid_query_payload·jwt_verification·nonce_used·no_authorization_ip)은 같은 요청을 다시 보내도 결과가 같으므로 재시도하지 말고 멈춰야 합니다. 재시도해도 되는 것은 429(다음 초 경계까지 대기)·500·market_offline 정도이고, 418은 429를 무시하고 계속 보낸 결과인 일시 차단이라 안내된 시간까지 호출을 완전히 멈춰야 합니다.

목차

  1. 에러 응답의 모양 — name이 숫자일 때와 문자열일 때
  2. 업비트 API 에러코드 24개 전체표
  3. 주문에서 가장 자주 나는 6개
  4. 인증 에러 7개 — JWT·nonce·IP·권한
  5. 429·418·500 — 속도 제한과 차단
  6. 봇의 분기 코드 — 재시도 / 중단 / 알림
  7. 실주문 전에 걸러내기 — /v1/orders/test 와 /v1/orders/chance
  8. 자주 묻는 질문

이 글의 표와 메시지는 업비트 개발자 센터(docs.upbit.com)의 REST API 사용 및 에러 안내·요청 수 제한(Rate Limits)·주문 생성 레퍼런스와 REST API 연동 Best Practice 문서를 2026-10-03에 대조해 정리했습니다. 업비트 봇의 전체 구조는 허브 글 업비트 자동매매 — REST만 쓰면 절반은 손해본다에 있고, 이 글은 그중 "에러가 났을 때 봇이 무엇을 해야 하나" 하나만 다룹니다.

1. 에러 응답의 모양 — name이 숫자일 때와 문자열일 때

업비트는 에러가 나면 error 객체 안에 name과 message를 돌려줍니다. 함정은 API 종류에 따라 name의 타입이 다르다는 점입니다. 공식 문서는 시세 조회(Quotation) API는 정수, 거래·자산(Exchange) API는 문자열을 반환한다고 적고 있습니다.

// Quotation API (시세: /v1/ticker, /v1/candles ...) — name 이 정수
{
  "error": {
    "name": 400,
    "message": "ERROR_MESSAGE"
  }
}

// Exchange API (주문·잔고: /v1/orders, /v1/accounts ...) — name 이 문자열
{
  "error": {
    "name": "insufficient_funds_bid",
    "message": "주문 가능한 금액(KRW)이 부족합니다."
  }
}

비교 전에 str(err.get("name"))로 타입을 맞춰 두십시오.

또 하나, 공식 문서는 out_of_scope 항목에서 "API 도메인(포켓·입출금·주문)마다 HTTP 상태 코드(401, 403)가 일관되지 않을 수 있으니 에러코드 문자열 기준으로 검증하라"고 권장합니다. 상태코드 분기는 큰 묶음(4xx/5xx)에만 쓰고, 실제 대응은 error.name으로 정하라는 뜻입니다.

2. 업비트 API 에러코드 24개 전체표

공식 에러 안내 표(20개)에 POST /v1/orders 레퍼런스의 응답 예시에만 나오는 4개(invalid_time_in_force·over_krw_funds_bid·notfoundmarket·market_offline)를 더했습니다. 마지막 열이 봇이 할 일입니다.

HTTPerror.name뜻봇의 대응
400create_ask_error · create_bid_error주문 요청 정보가 올바르지 않음중단 · 파라미터 조합 수정
400insufficient_funds_ask · insufficient_funds_bid매도/매수 가능 잔고 부족중단 · 잔고 재조회 후 수량 재계산
400under_min_total_ask · under_min_total_bid최소 주문 금액 미달중단 · 주문 전 금액 필터
400over_krw_funds_bid최대 주문 가능 금액(예시: 1,000,000,000 KRW) 초과중단 · 분할 주문
400invalid_time_in_force주문 조건(ioc·fok·post_only) 조합 오류중단 · 코드 수정
400validation_error필수 파라미터 누락중단 · 코드 수정
400invaild_parameter / invalid_parameter잘못된 파라미터 (철자 주의 — 아래 설명)중단 · 코드 수정
400duplicated_identifier이미 쓴 identifier중단 · ID 생성 규칙 수정
400notfoundmarket없는 페어(예: KRW-BTCs)중단 · 종목 목록 갱신
400withdraw_address_not_registered허용되지 않은 출금 주소중단 (출금 봇)
401invalid_query_payloadJWT 페이로드 오류중단 · query_hash 생성 점검
401jwt_verificationJWT 검증 실패중단 · 서명·키 점검
401expired_access_keyAPI 키 만료중단 · 키 재발급 알림
401nonce_used이미 사용된 nonce토큰 새로 서명 후 1회 재시도
401no_authorization_ip등록되지 않은 IP중단 · 서버 IP 점검
401no_authorization_token인증 헤더 누락중단 · 코드 수정
403out_of_scope키에 해당 권한 없음중단 · 키 권한 확인
403open_api_withdraw_locked출금 안심차단 미해지중단 · 앱에서 해제
403market_offline시스템 점검 중대기 후 재시도
404pocket_not_found · currency_not_found포켓/자산 코드 없음중단 · UUID·대문자 코드 확인
429—초당 요청 한도 초과다음 초 경계까지 대기 후 재시도
418—429 누적으로 일시 차단전면 정지 · 차단 시간 후 재개
500—서버 내부 오류·점검짧은 백오프 후 재시도

철자 함정 — 공식 에러 안내 표에는 invaild_parameter(i와 a가 뒤바뀐 철자)로, POST /v1/orders 응답 예시에는 invalid_parameter로 적혀 있습니다. 어느 쪽이 실제로 오는지 문서만으로는 단정할 수 없으므로, 분기 코드에는 두 철자를 모두 넣어 두십시오.

3. 주문에서 가장 자주 나는 6개

① under_min_total_bid — 5,000원 미만

원화(KRW) 마켓의 최소 주문 가능 금액은 공식 문서 기준 5,000 KRW입니다(BTC·USDT 마켓은 별도 문서). 분할 매수 봇이 "잔고의 10%씩"처럼 비율로 금액을 정하면 잔고가 줄어든 뒤 어느 순간부터 이 에러가 반복됩니다. 주문 직전에 총액을 계산해 5,000원 미만이면 아예 보내지 않는 필터가 정답이고, 마켓별 최소값은 GET /v1/orders/chance 응답의 min_total로 확인할 수 있습니다.

② insufficient_funds_bid — 잔고가 있는데 부족하다고 할 때

주문 가능 금액은 GET /v1/accounts의 balance이고, 미체결 주문에 묶인 돈은 locked로 따로 잡힙니다. 지정가 매수를 걸어 둔 채 같은 원화로 또 매수하면 이 에러가 납니다. 매수 수수료(/v1/orders/chance의 bid_fee)도 총액에 더해져야 하므로, 잔고를 100% 쓰는 주문은 수수료만큼 모자라 거절되기 쉽습니다.

③ create_bid_error — 시장가 주문 파라미터 조합

공식 문서가 직접 드는 원인은 "시장가 주문임에도 가격을 입력하는 경우"입니다. 업비트의 시장가는 매수와 매도가 다른 ord_type을 씁니다.

주문ord_type넣는 값넣지 않는 값
지정가 매수/매도limitprice + volume—
시장가 매수priceprice = 원화 총액volume
시장가 매도marketvolume = 코인 수량price
최유리 지정가best매수 price / 매도 volume + time_in_force반대쪽 값

④ invalid_time_in_force — post_only와 SMP

post_only는 ord_type=limit에서만 쓸 수 있고 smp_type과 함께 쓸 수 없습니다. ioc·fok는 지정가와 최유리 지정가에서 사용합니다. 자전거래 방지 옵션은 업비트 SMP 글에 정리해 두었습니다.

⑤ duplicated_identifier — 실패한 주문의 ID도 재사용 불가

identifier는 계정 내 전체 주문 기준으로 고유해야 하고, 생성·체결 여부와 관계없이 한 번 쓴 값은 다시 쓸 수 없으며 최대 64자입니다. 그래서 같은 identifier로 재전송하면 이중 주문 대신 이 에러가 나고, 첫 요청의 접수 여부는 identifier로 조회해 확인합니다.

⑥ 호가 단위 위반

원화 마켓은 가격 구간마다 주문 단위가 다릅니다(예: 10원 이상~100원 미만은 0.1원). 15원짜리 코인에 15.01원으로는 주문할 수 없다는 것이 공식 예시입니다. 이때의 error.name은 공식 표에 없으므로 주문 전에 가격을 tick_size(호가 정책 조회 API) 단위로 내림하고 실제 응답을 로그로 남기십시오.

4. 인증 에러 7개 — JWT·nonce·IP·권한

401 계열은 거의 전부 코드나 설정 문제라 재시도로 풀리지 않습니다. 공식 Best Practice 예제의 토큰 생성부는 이렇습니다.

import hashlib, uuid, jwt
from urllib.parse import urlencode, unquote

def make_token(access_key, secret_key, params=None):
    payload = {"access_key": access_key, "nonce": str(uuid.uuid4())}   # 요청마다 새 nonce
    if params:
        query_string = unquote(urlencode(params, doseq=True))         # 실제로 보내는 값과 같아야 함
        payload["query_hash"] = hashlib.sha512(query_string.encode("utf-8")).hexdigest()
        payload["query_hash_alg"] = "SHA512"
    token = jwt.encode(payload, secret_key, algorithm="HS512")
    return token if isinstance(token, str) else token.decode("utf-8")

5. 429·418·500 — 속도 제한과 차단

업비트는 모든 REST 응답 헤더에 그룹과 잔여 요청 수를 실어 보냅니다.

Remaining-Req: group=default; min=1800; sec=29

sec가 이번 1초에 남은 횟수이고, min은 공식 문서에서 Deprecated(참조하지 말 것)로 표시돼 있습니다. 그룹별 한도는 공식 Rate Limits 문서 기준으로 시세 그룹(market·candle·trade·ticker·orderbook) 각 초당 10회(IP 단위), default 초당 30회, order 초당 12회(2026-08-21에 8회에서 상향), order-test 초당 8회, order-cancel-all 2초당 1회입니다. 거래·자산 그룹은 2026-06-25부터 계정이 아니라 포켓 단위로 셉니다.

여러 거래소·증권사 봇에 공통으로 쓰는 스로틀 설계는 API 호출 제한 설계 글에 정리했습니다.

error.name / HTTP 재시도 429 → 다음 초까지 대기 500 · market_offline nonce_used → 새 토큰 중단 + 알림 under_min_total_* insufficient_funds_* jwt · ip · out_of_scope 전면 정지 418 일시 차단 차단 시간까지 모든 호출 금지 반복 시 차단 시간 증가
업비트 에러 3분기 — 공식 에러 안내·Rate Limits·Best Practice 문서 기준 (2026-10-03 대조)

6. 봇의 분기 코드 — 재시도 / 중단 / 알림

위 표를 그대로 코드로 옮기면 이렇습니다. requests 기준이며, 알림 함수는 텔레그램 등 운영 중인 채널로 바꾸면 됩니다.

import time, requests

STOP = {  # 같은 요청을 다시 보내도 결과가 같은 에러 → 멈추고 사람에게 알림
    "create_ask_error", "create_bid_error",
    "insufficient_funds_ask", "insufficient_funds_bid",
    "under_min_total_ask", "under_min_total_bid", "over_krw_funds_bid",
    "invalid_time_in_force", "validation_error",
    "invaild_parameter", "invalid_parameter",          # 공식 문서 철자 두 가지
    "duplicated_identifier", "notfoundmarket",
    "invalid_query_payload", "jwt_verification", "expired_access_key",
    "no_authorization_ip", "no_authorization_token", "out_of_scope",
}

def call(method, url, make_headers, max_retry=3, **kw):
    for attempt in range(max_retry):
        r = requests.request(method, url, headers=make_headers(), timeout=5, **kw)  # 매번 새 토큰
        if r.ok:
            return r.json()
        if r.status_code == 418:
            raise SystemExit("418 일시 차단 — 차단 시간까지 봇 전체 정지: " + r.text)
        if r.status_code == 429:
            time.sleep(1.0 - (time.time() % 1.0) + 0.05)   # 다음 초 경계까지
            continue
        name = str((r.json().get("error") or {}).get("name", ""))
        if name in STOP:
            notify(f"[업비트] {r.status_code} {name} — 재시도 안 함: {r.text}")
            return None
        if name == "nonce_used" or name == "market_offline" or r.status_code >= 500:
            time.sleep(0.5 * (2 ** attempt))
            continue
        notify(f"[업비트] 미분류 에러 {r.status_code} {name}")
        return None
    notify(f"[업비트] 재시도 {max_retry}회 실패: {url}")
    return None

핵심은 make_headers를 함수로 넘겨 재시도마다 토큰을 새로 서명하는 것(nonce_used 방지)과, 418을 예외로 올려 봇 전체를 세우는 것입니다. 장애 상황별 복구 순서는 봇 장애 복구 플레이북을 함께 보십시오.

7. 실주문 전에 걸러내기 — /v1/orders/test 와 /v1/orders/chance

업비트는 모의투자 서버가 없는 대신 주문 생성 테스트 API(POST /v1/orders/test)를 제공합니다. 공식 설명은 "실제 주문과 동일한 검증 과정을 거치지만 주문이 실제로 생성되지는 않는다"이고, 수수료도 들지 않습니다. 위 400 계열 에러 대부분을 실돈 없이 미리 확인할 수 있다는 뜻입니다.

POST https://api.upbit.com/v1/orders/test
Authorization: Bearer {JWT_TOKEN}
Content-Type: application/json

{"market": "KRW-BTC", "side": "bid", "ord_type": "price", "price": "4000"}
# → 원화 마켓 최소 5,000 KRW 미달이면 under_min_total_bid 가 이 단계에서 걸린다

업비트 봇을 처음 만든다면 파이썬 업비트 봇 30분에서 기본 골격을, 취소·정정 흐름은 업비트 취소 후 재주문 API에서 이어서 볼 수 있습니다. 증권사 쪽 에러 체계와 비교하고 싶다면 KIS API 에러코드 정리가 같은 구조로 되어 있습니다.

에러코드·한도·최소 주문 금액은 업비트 정책에 따라 공지 후 바뀔 수 있습니다. 이 글은 2026-10-03 기준 업비트 개발자 센터 문서를 대조한 것이므로, 실서비스 적용 전 공식 문서를 다시 확인하십시오. 투자 손익에 대한 판단이나 권유가 아니라 프로그램 오류 처리에 관한 기술 정리입니다.

8. 자주 묻는 질문

업비트 API에서 under_min_total_bid 에러는 왜 나나요?

주문 총액이 마켓별 최소 주문 금액보다 작을 때 납니다. 원화(KRW) 마켓의 최소 주문 가능 금액은 업비트 개발자 문서 기준 5,000 KRW입니다. 지정가는 price × volume, 시장가 매수(ord_type=price)는 price 값이 총액이므로 이 값을 주문 전에 계산해 걸러야 합니다. 정책은 바뀔 수 있으니 공식 문서와 GET /v1/orders/chance 응답을 확인하십시오.

잔고가 있는데 insufficient_funds_bid가 뜨는 이유는 무엇인가요?

주문 가능 금액은 보유 원화 전체가 아니라 미체결 주문에 묶인 금액(locked)을 뺀 값입니다. 매수 수수료까지 더한 금액이 남은 balance보다 크면 거절됩니다. GET /v1/accounts의 balance와 locked, GET /v1/orders/chance의 bid_fee를 함께 보고 주문 금액을 정하십시오.

업비트 API 에러가 나면 봇이 재시도해야 하나요?

공식 Best Practice는 사용자 행동 없이 해결되기 어려운 4xx 오류는 즉시 호출을 중단하고 원인을 식별하라고 안내합니다. 재시도 대상은 429(다음 초 경계까지 대기), 500, market_offline 정도이고, 418은 429를 계속 보낸 결과인 일시 차단이라 안내된 차단 시간이 지날 때까지 멈춰야 합니다.

nonce_used 에러는 어떻게 해결하나요?

JWT 페이로드의 nonce는 요청마다 새 값이어야 합니다. 재시도할 때 이전에 만든 JWT 토큰을 그대로 다시 보내면 nonce가 같아 거절됩니다. 재시도마다 uuid4로 nonce를 새로 만들고 query_hash도 다시 계산해 토큰을 새로 서명하십시오.

에러가 나도 멈출 곳에서 멈추는 봇, 만들어 드립니다

전략과 거래소만 알려 주시면 됩니다. 24시간 빠른 답변 가능합니다.

무료 상담 시작하기