# REST API ## REST 요청 규칙 - 공개(public) 엔드포인트(주로 시세 API)는 API 키가 필요 없습니다. - 비공개(private) 엔드포인트는 `X-KAPI-KEY` 헤더, `timestamp` 파라미터, `signature` 파라미터가 필요합니다. - `GET`/`DELETE`: 입력 값을 URL query string으로 보냅니다. - `POST`: 입력 값을 `application/x-www-form-urlencoded` 본문으로 보내고 `Content-Type: application/x-www-form-urlencoded` 헤더를 포함합니다. - 파라미터 순서는 무관하지만, 서명 대상 문자열은 실제로 전송되는 인코딩된 문자열(서명 자체 제외)과 정확히 일치해야 하며, 마지막에 `signature`를 덧붙입니다. - query와 body 모두에 파라미터가 있다면 두 문자열을 그대로 이어붙여(`queryString + bodyString`, 중간에 `&` 추가 없이) 서명합니다. 가능하면 서명 대상 파라미터를 한 쪽에 모아두세요. 서명 입력 예시 (query + body 결합): ```text timestamp=1719232467910symbol=btc_krw ``` ## Rate Limit 응답 헤더로 잔여 quota를 추적합니다. - `Ratelimit: limit=50, remaining=48, reset=1` - `Ratelimit-Policy: 50;w=1` - HTTP 429과 함께 `Retry-After`가 반환될 수 있습니다. | API 그룹 | 제한 | |---|---:| | 공개 REST | IP당 초당 50 요청 | | 주문 신청 | 계정당 초당 30 요청 | | 주문 취소 | 계정당 초당 30 요청 | | 입출금 신청 | 계정당 초당 5 요청 | | 그 외 비공개 REST | 계정당 초당 50 요청 | HTTP 429를 받으면 `Retry-After` 또는 `Ratelimit` reset 시점까지 기다린 뒤 재시도합니다. ## Timestamp 검증 윈도우 비공개 요청에는 다음을 포함합니다. - `timestamp`: 현재 Unix 시각(ms). - `recvWindow`: 선택값. 유효 윈도우(ms). 기본 `5000`, 최대 `60000`. 서버는 다음 조건을 만족할 때에만 요청을 수락합니다. ```text serverTime - timestamp <= recvWindow timestamp < serverTime + 1000 ``` 클라이언트 시각이 어긋나면 `EXCEED_TIME_WINDOW` 오류가 반환됩니다. 이 윈도우는 **비대칭**입니다 — `recvWindow`는 과거 방향만 넓히고, 미래 경계는 `recvWindow`로 넓힐 수 없는 고정 `+1000` ms입니다 — 따라서 시각이 서버보다 조금이라도 *앞서면* 모든 서명 요청이 실패하며 `recvWindow`를 키워도 소용이 없습니다. 원시 호스트 시각이 아니라 Korbit 서버 시각(`GET /v2/time`)을 기준으로 서명하세요. 복원력 recipe 6을 참고하세요. ## HMAC-SHA256 헬퍼 Node.js 예제. 실제로 전송할 `URLSearchParams`를 그대로 서명한 뒤 `signature`를 덧붙입니다. ```js import crypto from "node:crypto"; const BASE_URL = "https://api.korbit.co.kr"; const apiKey = process.env.KORBIT_API_KEY; const apiSecret = process.env.KORBIT_API_SECRET; function signHmac(encodedParams) { return crypto.createHmac("sha256", apiSecret).update(encodedParams, "utf8").digest("hex"); } async function korbitPrivate(method, path, params = {}) { const clean = Object.fromEntries( Object.entries(params).filter(([, v]) => v !== undefined && v !== null && v !== "") ); const p = new URLSearchParams({ ...clean, timestamp: String(Date.now()) }); const signature = signHmac(p.toString()); p.append("signature", signature); const headers = { "X-KAPI-KEY": apiKey }; let url = `${BASE_URL}${path}`; const init = { method, headers }; if (method === "GET" || method === "DELETE") { url += `?${p.toString()}`; } else { headers["Content-Type"] = "application/x-www-form-urlencoded"; init.body = p.toString(); } const res = await fetch(url, init); const json = await res.json().catch(() => ({})); if (!res.ok || json.success === false) { const err = new Error(`Korbit API error ${res.status}`); err.status = res.status; // 숫자 HTTP 상태 코드 err.code = json?.error?.message; // 심볼릭 코드, 예: "DUPLICATE_CLIENT_ORDER_ID" err.body = json; throw err; } return json.data ?? json; } ``` 실패 시 Korbit은 `{ "success": false, "error": { "code": , "message": "" } }`를 반환합니다. 분기 판단에 쓰는 심볼릭 코드(`EXCEED_TIME_WINDOW`, `DUPLICATE_CLIENT_ORDER_ID`, `NO_BALANCE` 등)는 `error.message`이며, `error.code`가 **아닙니다** — `error.code`는 숫자 HTTP 상태 코드가 반복된 값입니다. 최상위에는 심볼릭 값이 없습니다. 위 헬퍼는 이 심볼릭 코드를 `err.code`로 노출하므로 호출부는 그 값으로 분기하면 됩니다. ## ED25519 헬퍼 개인키는 PEM 문자열로 소스 외부에 보관합니다. ```js import crypto from "node:crypto"; const apiKey = process.env.KORBIT_API_KEY; const privateKeyPem = process.env.KORBIT_ED25519_PRIVATE_KEY; function signEd25519(encodedParams) { const sig = crypto.sign(null, Buffer.from(encodedParams), privateKeyPem); return sig.toString("base64"); } ``` `URLSearchParams`에 Base64 서명을 append 하면 URL 인코딩이 자동으로 적용됩니다. ## 주문 넣기 `POST /v2/orders`에 `symbol`, `side`(`buy`/`sell`), `orderType`을 보냅니다. 주문 크기를 지정하는 필드는 주문 유형에 따라 다르며, 이 부분이 가장 흔한 실수 지점이므로 정확히 맞춰야 합니다: | orderType | 필수 사이징 필드 | 생략 | |---|---|---| | `limit` | `price` **와** `qty` | `amt` | | `market` / `best` **매수(buy)** | `amt`(상대자산 매수 금액 — quote currency, `symbol`의 두 번째 항목) | `price`, `qty` | | `market` / `best` **매도(sell)** | `qty`(기준자산 수량 — base currency, `symbol`의 첫 번째 항목) | `price`, `amt` | 즉, `btc_krw`를 50,000 KRW어치 시장가 매수할 때는 `amt=50000`을 보내고 `qty`와 `price`는 **보내지 않습니다**. `amt`는 해당 페어의 상대자산 단위입니다. 0.01 BTC 시장가 매도는 `qty=0.01`만 보내고 `amt`/`price`는 생략합니다. `best`(BBO) 주문은 추가로 `timeInForce`와 `bestNth`가 필요합니다. `clientOrderId`(`[0-9a-zA-Z.:_-]{1,36}` 형식)를 넣으면 멱등성이 보장됩니다. 같은 `clientOrderId`로 여러 번 요청해도 한 번만 처리되며, 이후 `GET /v2/orders?clientOrderId=...`로 조회할 수 있습니다. 주문이 성공하면 부여된 `orderId`가 반환되며, 주문의 나머지 상태는 후속 `GET /v2/orders`로 읽습니다(아래 참고). `clientOrderId`는 사실상 해제되지 않으므로, 서로 다른 주문에 같은 ID를 재사용하지 말고 신청마다 충돌에 강한 ID를 새로 발급해 영속화하세요 — 리질리언스 #1 참고. ## 주문 및 체결 읽기 `GET /v2/orders`(`orderId` 또는 `clientOrderId`로 조회)는 체결 상태를 포함한 주문 정보를 반환합니다. 금액 필드는 문자열이므로 문자열/decimal로 유지하세요. | 필드 | 의미 | 비고 | |---|---|---| | `qty` | 주문 수량 | 시장가 매수(=`amt`로 사이징)에서는 없음 | | `amt` | 매수 금액(시장가 매수) | 상대자산, 예: KRW | | `filledQty` | 체결된 수량 | 체결 전에는 `"0"` | | `filledAmt` | 체결된 상대자산 금액 | 예: 사용/수령한 KRW | | `avgPrice` | 평균 체결가 | **선택적 — 체결이 생기기 전에는 없음.** 가드 없이 읽지 말 것 | | `status` | 주문 상태 | 아래 주문 상태 참고(`partiallyFilled` 등) | 체결을 안전하게 다루려면 `avgPrice`가 없을 수 있다고 보고(필요하고 없을 때는 `filledAmt / filledQty`를 decimal로 계산해 도출하며, "미체결"은 평균가 없음으로 처리 — 절대 `NaN`이 되지 않게), 미체결 잔량은 `qty - filledQty`로 계산하되 음수가 되지 않게 합니다. `partiallyFilled` 주문은 `filledQty`가 `qty`보다 작으므로, 후속 동작은 원래 요청 수량이 아니라 `filledQty`/`filledAmt` 기준으로 사이징하세요. ## 잔고 `GET /v2/balance`(선택적으로 `currencies=btc,eth`)는 자산별 객체 배열을 반환합니다. 모든 수량은 문자열입니다: | 필드 | 의미 | |---|---| | `currency` | 자산명, 예: `krw`, `btc` | | `balance` | 총합 = `available + tradeInUse + withdrawalInUse` | | `available` | 지금 즉시 거래/출금 가능한 수량 | | `tradeInUse` | 미체결 주문에 묶인 수량 | | `withdrawalInUse` | 출금 대기에 묶인 수량 | | `avgPrice` | 평균 매수가(선택적) | 신규 주문은 `balance`가 아니라 `available` 기준으로 사이징하세요 — 그 차이는 이미 미체결 주문이나 출금에 묶여 있습니다. ## REST 엔드포인트 아래 표는 YAML 스펙의 그룹 순서대로 정리되어 있습니다. 각 엔드포인트의 필수 파라미터와 응답 스키마는 본 문서 하단에 링크된 그룹별 레퍼런스 파일에서 확인하세요. ### 시세 | Method | 경로 | 권한 | 용도 | |---|---|---|---| | `GET` | [`/v2/tickers`](/llms/ko/rest_api/quotation.md#get-_v2_tickers) | (공개) | 하나 또는 여러 거래쌍의 현재가(Ticker) 정보를 조회합니다. | | `GET` | [`/v2/orderbook`](/llms/ko/rest_api/quotation.md#get-_v2_orderbook) | (공개) | 단일 거래쌍의 호가 정보를 조회합니다. | | `GET` | [`/v2/trades`](/llms/ko/rest_api/quotation.md#get-_v2_trades) | (공개) | 최근 체결 내역을 조회합니다. | | `GET` | [`/v2/candles`](/llms/ko/rest_api/quotation.md#get-_v2_candles) | (공개) | 시세 캔들스틱 정보를 조회합니다. | | `GET` | [`/v2/currencyPairs`](/llms/ko/rest_api/quotation.md#get-_v2_currencyPairs) | (공개) | 거래지원 거래쌍 목록을 각 거래쌍의 통화 구성 및 주문금액 한도와 함께 조회합니다. | | `GET` | [`/v2/tickSizePolicy`](/llms/ko/rest_api/quotation.md#get-_v2_tickSizePolicy) | (공개) | 지정한 거래쌍의 호가 정책 및 오더북 모아보기 단위를 조회합니다. | ### 주문 | Method | 경로 | 권한 | 용도 | |---|---|---|---| | `GET` | [`/v2/orders`](/llms/ko/rest_api/trading.md#get-_v2_orders) | `readOrders` | 주문 ID(orderId) 또는 사용자 지정 주문 ID(clientOrderId)를 이용해 개별 주문 정보를 조회합니다. | | `GET` | [`/v2/openOrders`](/llms/ko/rest_api/trading.md#get-_v2_openOrders) | `readOrders` | 단일 거래쌍의 미체결 주문 목록을 조회합니다. | | `GET` | [`/v2/allOrders`](/llms/ko/rest_api/trading.md#get-_v2_allOrders) | `readOrders` | 단일 거래쌍의 최근 주문 목록을 조회합니다. 주문 생성 시각 기준으로 최근 36시간의 주문 내역만 조회 가능합니다. | | `GET` | [`/v2/myTrades`](/llms/ko/rest_api/trading.md#get-_v2_myTrades) | `readOrders` | 단일 거래쌍의 최근 체결 목록을 조회합니다. 최근 36시간의 체결 내역만 조회 가능합니다. | | `POST` | [`/v2/orders`](/llms/ko/rest_api/trading.md#post-_v2_orders) | `writeOrders` | 신규 주문을 생성합니다. | | `DELETE` | [`/v2/orders`](/llms/ko/rest_api/trading.md#delete-_v2_orders) | `writeOrders` | 주문 취소 요청을 전송합니다. | ### 자산 | Method | 경로 | 권한 | 용도 | |---|---|---|---| | `GET` | [`/v2/balance`](/llms/ko/rest_api/asset.md#get-_v2_balance) | `readBalances` | 내가 가지고 있는 자산 목록을 조회합니다. | ### 가상자산 입금 | Method | 경로 | 권한 | 용도 | |---|---|---|---| | `GET` | [`/v2/coin/depositAddresses`](/llms/ko/rest_api/deposit-crypto.md#get-_v2_coin_depositAddresses) | `readDeposits` | 가상자산 입금 주소 목록을 조회합니다. | | `GET` | [`/v2/coin/depositAddress`](/llms/ko/rest_api/deposit-crypto.md#get-_v2_coin_depositAddress) | `readDeposits` | 개별 가상자산의 입금 주소를 조회합니다. | | `POST` | [`/v2/coin/depositAddress`](/llms/ko/rest_api/deposit-crypto.md#post-_v2_coin_depositAddress) | `writeDeposits` | 가상자산을 입금할 주소를 발급합니다. 입금 주소가 이미 존재한다면 신규 발급 없이 기존 입금 주소를 응답합니다. | | `GET` | [`/v2/coin/recentDeposits`](/llms/ko/rest_api/deposit-crypto.md#get-_v2_coin_recentDeposits) | `readDeposits` | 최근 가상자산 입금 내역을 조회합니다. | | `GET` | [`/v2/coin/deposit`](/llms/ko/rest_api/deposit-crypto.md#get-_v2_coin_deposit) | `readDeposits` | 가상자산 입금 진행 상태를 조회합니다. | ### 가상자산 출금 | Method | 경로 | 권한 | 용도 | |---|---|---|---| | `GET` | [`/v2/coin/withdrawableAddresses`](/llms/ko/rest_api/withdrawal-crypto.md#get-_v2_coin_withdrawableAddresses) | `readWithdrawals` | API 출금 가능 주소로 등록된 주소를 조회합니다. | | `GET` | [`/v2/coin/withdrawableAmount`](/llms/ko/rest_api/withdrawal-crypto.md#get-_v2_coin_withdrawableAmount) | `readWithdrawals` | 출금 가능 수량을 조회합니다. | | `POST` | [`/v2/coin/withdrawal`](/llms/ko/rest_api/withdrawal-crypto.md#post-_v2_coin_withdrawal) | `writeWithdrawals` | 가상자산 출금을 요청합니다. 출금 API를 사용하기 위해서는, 코빗 개발자센터에서 API 출금 허용주소 등록이 필요합니다. | | `DELETE` | [`/v2/coin/withdrawal`](/llms/ko/rest_api/withdrawal-crypto.md#delete-_v2_coin_withdrawal) | `writeWithdrawals` | 가상자산 출금을 취소합니다. | | `GET` | [`/v2/coin/recentWithdrawals`](/llms/ko/rest_api/withdrawal-crypto.md#get-_v2_coin_recentWithdrawals) | `readWithdrawals` | 최근 가상자산 출금내역을 조회합니다. | | `GET` | [`/v2/coin/withdrawal`](/llms/ko/rest_api/withdrawal-crypto.md#get-_v2_coin_withdrawal) | `readWithdrawals` | 요청한 출금의 진행 상황을 조회합니다. | ### 원화 입출금 | Method | 경로 | 권한 | 용도 | |---|---|---|---| | `POST` | [`/v2/krw/sendKrwDepositPush`](/llms/ko/rest_api/krw.md#post-_v2_krw_sendKrwDepositPush) | `writeDeposits` | 코빗 모바일 앱으로 원화 입금 요청 알림을 전송합니다. | | `POST` | [`/v2/krw/sendKrwWithdrawalPush`](/llms/ko/rest_api/krw.md#post-_v2_krw_sendKrwWithdrawalPush) | `writeWithdrawals` | 코빗 모바일 앱으로 원화 출금 요청 알림을 전송합니다. | | `GET` | [`/v2/krw/recentDeposits`](/llms/ko/rest_api/krw.md#get-_v2_krw_recentDeposits) | `readDeposits` | 최근 KRW 입금 내역을 조회합니다. | | `GET` | [`/v2/krw/recentWithdrawals`](/llms/ko/rest_api/krw.md#get-_v2_krw_recentWithdrawals) | `readWithdrawals` | 최근 KRW 출금내역을 조회합니다. | ### 기타 | Method | 경로 | 권한 | 용도 | |---|---|---|---| | `GET` | [`/v2/currencies`](/llms/ko/rest_api/other.md#get-_v2_currencies) | (공개) | 가상자산 정보를 조회합니다. | | `GET` | [`/v2/time`](/llms/ko/rest_api/other.md#get-_v2_time) | (공개) | 서버 시각을 조회합니다. | | `GET` | [`/v2/tradingFeePolicy`](/llms/ko/rest_api/other.md#get-_v2_tradingFeePolicy) | `readOrders` | 현재 회원 계정에 적용되는 거래수수료율을 조회합니다. | | `GET` | [`/v2/currentKeyInfo`](/llms/ko/rest_api/other.md#get-_v2_currentKeyInfo) | 서명 (권한 무관) | 현재 사용중인 API 키의 정보를 조회합니다. | | `GET` | [`/v2/notices`](/llms/ko/rest_api/other.md#get-_v2_notices) | (공개) | 최근 공지사항 20건을 최신순으로 조회합니다. | | `GET` | [`/v2/marketAlerts`](/llms/ko/rest_api/other.md#get-_v2_marketAlerts) | (공개) | 거래쌍별 시장경보제 발동현황을 조회합니다. 현재 경보가 발동된 거래쌍만 반환됩니다. | ## 주문 상태 | 값 | 설명 | |---|---| | `pending` | 주문 접수 대기 중. 잔고가 부족하거나 `timeInForce` 조건에 해당할 경우, 주문이 실패해 `expired` 상태로 될 수 있습니다. | | `open` | 전량 미체결 | | `filled` | 체결 완료 후 주문 종료. 미체결 잔량이 오더북에 남지 않고 반환되는 주문(예: `ioc` 주문, 가격 보호(`pp`) 범위로 잔량이 잘린 주문)은 주문 수량보다 적게 체결된 경우에도 `filled`로 종료됩니다. 실제 체결 수량은 `filledQty`/`filledAmt`로 확인하세요. | | `canceled` | 전량 취소 | | `partiallyFilled` | 부분 체결 | | `partiallyFilledCanceled` | 부분 체결 후 잔량 취소 | | `expired` | 주문 접수 실패 (잔고 부족 또는 timeInForce 조건 해당 시) | ## 오류 코드 | 오류 코드 | 발생 상황 | 경로 | |---|---|---| | `BAD_REQUEST` | 잘못된 입력입니다. | `POST /v2/orders` | | `CANNOT_CANCEL_WITHDRAWAL` | 출금 취소가 불가능합니다. (출금 처리가 이미 시작된 경우 등) | `DELETE /v2/coin/withdrawal` | | `DAILY_LIMIT_EXCEEDED` | 일일 출금 한도를 초과했습니다. | `POST /v2/coin/withdrawal` | | `DUPLICATE_CLIENT_ORDER_ID` | 중복된 clientOrderId입니다. | `POST /v2/orders` | | `FORBIDDEN_WITHDRAWAL_ADDRESS` | 코빗 정책에 따라 출금이 불가능한 주소입니다. | `POST /v2/coin/withdrawal` | | `INVALID_CURRENCY` | 잘못된 가상자산 심볼입니다. | `POST /v2/coin/withdrawal` | | `INVALID_CURRENCY_PAIR` | 잘못된 거래쌍입니다. | `POST /v2/orders` | | `INVALID_USER_STATUS` | 거래가 제한된 계정입니다. 코빗 웹사이트 또는 고객센터를 통해 상태를 확인하세요. | `POST /v2/coin/withdrawal`, `POST /v2/orders` | | `NOT_FOUND` | 출금 정보가 존재하지 않습니다. | `DELETE /v2/coin/withdrawal` | | `NO_BALANCE` | 잔고가 부족합니다. | `POST /v2/coin/withdrawal`, `POST /v2/orders` | | `ONLY_SELL_LIMIT_ORDERS_ALLOWED` | 거래지원 직후에는 지정가 매도 주문만 가능합니다. | `POST /v2/orders` | | `ORDER_ALREADY_CANCELED` | 지정한 주문이 이미 취소되었습니다. | `DELETE /v2/orders` | | `ORDER_ALREADY_EXPIRED` | 지정한 주문은 실패 처리된 주문입니다. | `DELETE /v2/orders` | | `ORDER_ALREADY_FILLED` | 지정한 주문이 이미 전량 체결되었습니다. | `DELETE /v2/orders` | | `ORDER_NOT_FOUND` | 지정한 주문 ID가 존재하지 않습니다. | `DELETE /v2/orders` | | `ORDER_VALUE_TOO_LARGE` | 주문 최대 금액 초과입니다. 주문금액 한도는 마켓별로 정해지며 해당 거래쌍의 상대자산 단위입니다. `GET /v2/currencyPairs`의 `maxOrderValue`, `quoteCurrency`를 참고하세요. 수량*가격(또는 `amt`)을 한도 이하로 변경해 주세요. | `POST /v2/orders` | | `ORDER_VALUE_TOO_SMALL` | 주문 최소 금액 미달입니다. 주문금액 한도는 마켓별로 정해지며 해당 거래쌍의 상대자산 단위입니다. `GET /v2/currencyPairs`의 `minOrderValue`, `quoteCurrency`를 참고하세요. 수량*가격(또는 `amt`)을 한도 이상으로 변경해 주세요. | `POST /v2/orders` | | `PRICE_OVER_UPPER_BOUND` | 거래지원 직후에는 특정한 가격 초과의 호가를 입력할 수 없습니다. | `POST /v2/orders` | | `PRICE_TICK_SIZE_INVALID` | 지정가 입력 단위가 올바르지 않습니다. | `POST /v2/orders` | | `PRICE_UNDER_LOWER_BOUND` | 거래지원 직후에는 특정한 가격 미만의 호가를 입력할 수 없습니다. | `POST /v2/orders` | | `TOO_MANY_OPEN_ORDERS` | 미체결 주문 개수 제한을 초과했습니다. | `POST /v2/orders` | | `TRY_AGAIN` | 주문 접수 처리중입니다. 잠시 후 다시 시도해 주세요. | `DELETE /v2/orders` | | `UNREGISTERED_WITHDRAWAL_ADDRESS` | Open API 출금 허용 주소로 등록되어 있지 않은 주소입니다. | `POST /v2/coin/withdrawal` | | `WITHDRAWAL_ALREADY_FINISHED` | 출금이 이미 완료되었습니다. | `DELETE /v2/coin/withdrawal` | | `WITHDRAWAL_ALREADY_IN_PROGRESS` | 이미 출금 진행 중입니다. 다른 출금 건이 완료된 후 다시 시도해 주세요. | `POST /v2/coin/withdrawal` | | `WITHDRAWAL_SUSPENDED` | 출금이 중단된 상태입니다. | `POST /v2/coin/withdrawal` |