# Korbit Open API LLM Guide > Compact, implementation-oriented guide to Korbit Open API v2. Paste this URL into an AI coding agent so the agent can build a Korbit API client, market-data collector, or trading bot without first reading the full developer docs. This is not investment advice. Source: https://docs.korbit.co.kr/. Full per-endpoint reference: see the per-group Markdown files linked at the bottom. You can run everything in this guide against a local sandbox — a no-real-money mock of this API — before switching to production. See the Local Sandbox section at the end of this guide. ## Two ways to integrate Before you start, pick a path: - **Use the official CLI (korbit-cli).** A single static binary that already handles request signing, idempotent order placement, retries, clock-sync, and a local action journal — the exact things this guide warns you to get right. If your environment can run a binary or speak MCP, this is the lower-risk path, and the rest of this guide becomes optional reference. Drive it by shelling out to its commands or by running its MCP server. See the [Build with the official CLI](#build-with-the-official-cli) section below for install and usage. - **Build your own client.** If you are embedding in a runtime where shelling out isn't viable, or you want full control over the wire protocol, implement directly against the reference in this document. Everything you need is here. The official CLI is open source and doubles as a reference implementation — rely on this document first, and read that source whenever a concrete implementation of a pattern would help. See [Using the source as a reference implementation](#using-the-source-as-a-reference-implementation). ## Agent Instructions When building software from this document: - Use this document as the primary API context. Preserve endpoint paths, HTTP methods, parameter names, authentication rules, and enum values exactly. - The API secret / private key is everything — whoever holds it can trade and withdraw. Read it at runtime from the environment or a secret manager (use whatever secure store your environment provides); never hard-code it, balances, or order sizes. - Secrets flow into signing and nowhere else: never log, print, commit, or send the secret, private key, `X-KAPI-KEY`, or `signature` to any chat/notification channel, LLM prompt, third party, or non-Korbit host. If a credential is pasted to you inline, do not persist it — have the user supply it through the environment or a secret store you read at runtime. - Develop and test against the local sandbox (see the Local Sandbox section) first; switch to the production base URLs only after the user has verified the bot's behavior. - Use decimal-safe arithmetic (BigDecimal / strings) for prices, quantities, and amounts in the quote currency (`symbol`'s second segment). Do not use floating point for order math. - Log request IDs, `clientOrderId`, `orderId`, status transitions, and API error codes — but redact credentials and signatures. - Add retry and backoff for network failures and HTTP 429. Do not blindly retry order placement without idempotency via `clientOrderId`. - Generate `clientOrderId` values that are at most 36 characters and match `[0-9a-zA-Z.:_-]{1,36}`. Mint one collision-resistant id per placement (namespace + a timestamp/unique suffix), **persist it in your own store**, and reuse the stored id on retry — don't derive it from the order's price/quantity, since a `clientOrderId` never frees up and a repeat would be rejected (see Resilience #1). - API keys are created by the user in the Korbit Developers portal — key creation requires human identity verification, so an agent cannot create one. In setup instructions, tell the user to scope the key to the minimum permissions the bot needs (e.g. a trading bot needs read and order permissions, not withdrawal) and to set an IP allowlist. - Prefer WebSocket for real-time market and order events. Use REST for snapshots, order placement, cancellation, reconciliation, and recovery after reconnect. ## Base URLs ```text REST: https://api.korbit.co.kr WS public: wss://ws-api.korbit.co.kr/v2/public WS private: wss://ws-api.korbit.co.kr/v2/private ``` All REST timestamps are Unix timestamps in milliseconds. Successful REST responses look like: ```json {"success": true, "data": {}} ``` For list endpoints `data` is an array. For action endpoints (e.g. cancel) a successful response may be `{"success": true}` with no `data`. ## API Keys and Permissions API keys are created in the Korbit Developers portal (https://developers.korbit.co.kr). Keys can be configured with permissions and IP allowlists. Keys are valid for one year from creation. Permission / IP changes can take up to about one minute to apply. Two signing modes are supported: - `HMAC-SHA256`: Korbit provides a secret key. Signature is hex. - `ED25519`: user provides an ED25519 public key at key creation and signs with the private key. Signature is Base64; URL-encode it when sending in query/body. For an `ED25519` key you generate the pair yourself and register only the public key with Korbit; keep the private key out of source control (env var or secret store). Node.js: ```js import { generateKeyPairSync } from "node:crypto"; const { publicKey, privateKey } = generateKeyPairSync("ed25519", { publicKeyEncoding: { type: "spki", format: "pem" }, // register this with Korbit privateKeyEncoding: { type: "pkcs8", format: "pem" }, // keep secret; sign with this }); ``` OpenSSL: ```sh openssl genpkey -algorithm ED25519 -out private_key.pem # keep secret openssl pkey -in private_key.pem -pubout -out public_key.pem # register with Korbit ``` # REST API ## REST Request Rules - Public endpoints (mostly market data) do not require an API key. - Private endpoints require: the `X-KAPI-KEY` header, a `timestamp` parameter, and a `signature` parameter. - `GET` and `DELETE`: send input values as query string parameters. - `POST`: send input values in the body as `application/x-www-form-urlencoded` and include `Content-Type: application/x-www-form-urlencoded`. - Parameter order does not matter, but the signature MUST be computed over the exact encoded string that will be sent (excluding `signature` itself), then `signature` appended. - If a request has both query string and body parameters, sign `queryString + bodyString` with no extra `&` between them. Prefer keeping all signed parameters in one place to avoid mistakes. Example signing input (query string + body, concatenated): ```text timestamp=1719232467910symbol=btc_krw ``` ## Rate Limits Track quota with response headers: - `Ratelimit: limit=50, remaining=48, reset=1` - `Ratelimit-Policy: 50;w=1` - `Retry-After` accompanies HTTP 429. | API group | Limit | |---|---:| | Public REST | 50 req/sec per IP | | Order placement | 30 req/sec per account | | Order cancellation | 30 req/sec per account | | Deposit / withdrawal | 5 req/sec per account | | Other private REST | 50 req/sec per account | On HTTP 429, pause until `Retry-After` or the `Ratelimit` reset window before retrying. ## Timestamp Window For private requests include: - `timestamp`: current Unix time in milliseconds. - `recvWindow`: optional validity window in milliseconds. Default `5000`, maximum `60000`. The server accepts the request only when: ```text serverTime - timestamp <= recvWindow timestamp < serverTime + 1000 ``` If the client clock drifts, Korbit returns `EXCEED_TIME_WINDOW`. This window is **asymmetric** — `recvWindow` widens only the past side, while the future bound is a fixed `+1000` ms — so a clock even slightly *ahead* of the server fails every signed request and raising `recvWindow` cannot help. Sign against Korbit's server clock (from `GET /v2/time`), not the raw host clock; see resilience recipe 6. ## HMAC-SHA256 Helper Node.js example. This signs the exact `URLSearchParams` payload before appending `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; // numeric HTTP status err.code = json?.error?.message; // symbolic code, e.g. "DUPLICATE_CLIENT_ORDER_ID" err.body = json; throw err; } return json.data ?? json; } ``` On failure Korbit returns `{ "success": false, "error": { "code": , "message": "" } }`. The symbolic code you branch on (`EXCEED_TIME_WINDOW`, `DUPLICATE_CLIENT_ORDER_ID`, `NO_BALANCE`, …) is `error.message` — **not** `error.code`, which is the numeric HTTP status repeated. Nothing symbolic appears at the top level. The helper above surfaces the symbolic code as `err.code` so callers branch on that. ## ED25519 Helper The private key should be a PEM string stored outside the source code. ```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"); } ``` When you append the Base64 signature via `URLSearchParams`, the value is URL-encoded automatically. ## Placing Orders `POST /v2/orders` with `symbol`, `side` (`buy`/`sell`), and `orderType`. How you size the order depends on the type — this is the most common mistake, so get it exactly right: | orderType | required sizing field(s) | omit | |---|---|---| | `limit` | `price` **and** `qty` | `amt` | | `market` / `best` **buy** | `amt` (purchase amount in the quote currency — `symbol`'s second segment) | `price`, `qty` | | `market` / `best` **sell** | `qty` (quantity in the base currency — `symbol`'s first segment) | `price`, `amt` | So a market BUY of 50,000 KRW of `btc_krw` sends `amt=50000` and sends **no** `qty` and **no** `price` — `amt` is in the pair's quote currency, whatever that is. A market SELL of 0.01 BTC sends `qty=0.01` and no `amt`/`price`. `best` (BBO) orders additionally require `timeInForce` and `bestNth`. Pass `clientOrderId` (matching `[0-9a-zA-Z.:_-]{1,36}`) to make placement idempotent: repeated requests with the same `clientOrderId` are processed only once, and you can look the order up later with `GET /v2/orders?clientOrderId=...`. A successful placement returns the assigned `orderId`; read the rest of the order's state with a follow-up `GET /v2/orders` (see below). A `clientOrderId` effectively never frees up, so mint a fresh collision-resistant id per placement and persist it rather than reusing one across distinct orders — see Resilience #1. ## Reading Orders and Fills `GET /v2/orders` (by `orderId` or `clientOrderId`) returns the order, including its fill state. Money fields are strings — keep them as strings / decimals. | field | meaning | notes | |---|---|---| | `qty` | ordered quantity | absent for a market buy (which was sized by `amt`) | | `amt` | purchase amount (market buy) | in the quote currency, e.g. KRW | | `filledQty` | quantity filled so far | `"0"` until something fills | | `filledAmt` | quote-currency amount filled so far | e.g. KRW spent/received | | `avgPrice` | average execution price | **optional — absent until there is a fill.** Never read it unguarded | | `status` | order status | see Order Statuses below (`partiallyFilled` etc.) | To act on a fill safely: treat `avgPrice` as possibly missing (derive it as `filledAmt / filledQty` with decimal math when you need it and it is absent, and treat "nothing filled" as no average price — never `NaN`). The unfilled remainder is `qty - filledQty`; never let it go negative. A `partiallyFilled` order has a real `filledQty` smaller than `qty`, so size any follow-on action off `filledQty`/`filledAmt`, not the originally requested size. ## Balances `GET /v2/balance` (optionally `currencies=btc,eth`) returns an array, one object per asset. All amounts are strings: | field | meaning | |---|---| | `currency` | asset name, e.g. `krw`, `btc` | | `balance` | total = `available + tradeInUse + withdrawalInUse` | | `available` | quantity free to trade/withdraw right now | | `tradeInUse` | quantity locked in open orders | | `withdrawalInUse` | quantity locked in pending withdrawals | | `avgPrice` | average purchase price (optional) | Size new orders against `available`, not `balance` — the difference is already committed to open orders or withdrawals. ## REST Endpoints Tables below are grouped exactly as in the YAML spec. Required parameters are listed in the per-group reference files at the bottom of this document. ### Quotation | Method | Path | Permission | Purpose | |---|---|---|---| | `GET` | [`/v2/tickers`](#get-_v2_tickers) | (public) | Get latest price and trading volume for a symbol or symbols. | | `GET` | [`/v2/orderbook`](#get-_v2_orderbook) | (public) | Get orderbook data. | | `GET` | [`/v2/trades`](#get-_v2_trades) | (public) | Get recent trades. | | `GET` | [`/v2/candles`](#get-_v2_candles) | (public) | Get historical candlesticks (klines) data. | | `GET` | [`/v2/currencyPairs`](#get-_v2_currencyPairs) | (public) | Get supported trading pairs with their currencies and order value bounds. | | `GET` | [`/v2/tickSizePolicy`](#get-_v2_tickSizePolicy) | (public) | Get tick size policy and orderbook grouping levels for a trading pair. | ### Trading | Method | Path | Permission | Purpose | |---|---|---|---| | `GET` | [`/v2/orders`](#get-_v2_orders) | `readOrders` | Use either `orderId` or `clientOrderId` to query the status of an individual order. | | `GET` | [`/v2/openOrders`](#get-_v2_openOrders) | `readOrders` | Query the list of open orders for a single trading pair. Only orders with the status `open` or `partiallyFilled` are queried. | | `GET` | [`/v2/allOrders`](#get-_v2_allOrders) | `readOrders` | Query the recent order list for a single trading pair. Only orders created within 36 hours can be queried. | | `GET` | [`/v2/myTrades`](#get-_v2_myTrades) | `readOrders` | Query the recent trades list for a single trading pair. Only trade history from the past 36 hours can be queried. | | `POST` | [`/v2/orders`](#post-_v2_orders) | `writeOrders` | Place a new order. | | `DELETE` | [`/v2/orders`](#delete-_v2_orders) | `writeOrders` | Requests to cancel an open order. | ### Asset | Method | Path | Permission | Purpose | |---|---|---|---| | `GET` | [`/v2/balance`](#get-_v2_balance) | `readBalances` | Get balance. | ### Deposit (Crypto) | Method | Path | Permission | Purpose | |---|---|---|---| | `GET` | [`/v2/coin/depositAddresses`](#get-_v2_coin_depositAddresses) | `readDeposits` | Retrieve the list of cryptocurrency deposit addresses. | | `GET` | [`/v2/coin/depositAddress`](#get-_v2_coin_depositAddress) | `readDeposits` | Get the deposit address for a single cryptocurrency. | | `POST` | [`/v2/coin/depositAddress`](#post-_v2_coin_depositAddress) | `writeDeposits` | Generate a cryptocurrency deposit address. If a deposit address already exists, the existing address will be returned. | | `GET` | [`/v2/coin/recentDeposits`](#get-_v2_coin_recentDeposits) | `readDeposits` | Get recent deposit history. | | `GET` | [`/v2/coin/deposit`](#get-_v2_coin_deposit) | `readDeposits` | Check the status of cryptocurrency deposits. | ### Withdrawal (Crypto) | Method | Path | Permission | Purpose | |---|---|---|---| | `GET` | [`/v2/coin/withdrawableAddresses`](#get-_v2_coin_withdrawableAddresses) | `readWithdrawals` | Retrieve the list of addresses registered for API withdrawals. | | `GET` | [`/v2/coin/withdrawableAmount`](#get-_v2_coin_withdrawableAmount) | `readWithdrawals` | Get the available cryptocurrency withdrawal amount. | | `POST` | [`/v2/coin/withdrawal`](#post-_v2_coin_withdrawal) | `writeWithdrawals` | Request for cryptocurrency withdrawal. You need to register your withdrawal addresses for use with API in order to use this feature. | | `DELETE` | [`/v2/coin/withdrawal`](#delete-_v2_coin_withdrawal) | `writeWithdrawals` | Cancel a cryptocurrency withdrawal. | | `GET` | [`/v2/coin/recentWithdrawals`](#get-_v2_coin_recentWithdrawals) | `readWithdrawals` | Get recent cryptocurrency withdrawal history. | | `GET` | [`/v2/coin/withdrawal`](#get-_v2_coin_withdrawal) | `readWithdrawals` | Get the status of the requested withdrawal. | ### Deposit/Withdrawal (KRW) | Method | Path | Permission | Purpose | |---|---|---|---| | `POST` | [`/v2/krw/sendKrwDepositPush`](#post-_v2_krw_sendKrwDepositPush) | `writeDeposits` | Send a notification for KRW deposit requests to your Korbit mobile app. | | `POST` | [`/v2/krw/sendKrwWithdrawalPush`](#post-_v2_krw_sendKrwWithdrawalPush) | `writeWithdrawals` | Send a notification for KRW withdrawal requests to your Korbit mobile app. | | `GET` | [`/v2/krw/recentDeposits`](#get-_v2_krw_recentDeposits) | `readDeposits` | Get recent KRW deposit history. | | `GET` | [`/v2/krw/recentWithdrawals`](#get-_v2_krw_recentWithdrawals) | `readWithdrawals` | Get recent KRW withdrawal history. | ### Other Endpoints | Method | Path | Permission | Purpose | |---|---|---|---| | `GET` | [`/v2/currencies`](#get-_v2_currencies) | (public) | Get cryptocurrencies information. | | `GET` | [`/v2/time`](#get-_v2_time) | (public) | Get the current server time. | | `GET` | [`/v2/tradingFeePolicy`](#get-_v2_tradingFeePolicy) | `readOrders` | Get the trading fee rates applied to your account. | | `GET` | [`/v2/currentKeyInfo`](#get-_v2_currentKeyInfo) | signed (any key) | Get current API Key's information. | | `GET` | [`/v2/notices`](#get-_v2_notices) | (public) | Get the 20 most recent Korbit notices (announcements), most recent first. | | `GET` | [`/v2/marketAlerts`](#get-_v2_marketAlerts) | (public) | Get the current market alert (Market Warning System, 시장경보제) status for each trading pair. Returns only pairs that currently have active alerts. | ## Order Statuses | Value | Meaning | |---|---| | `pending` | Order pending. When the balance is insufficient or timeInForce condition is triggered, the order may fail and change to the `expired` status. | | `open` | Fully unfilled | | `filled` | Execution closed. An order whose unfilled remainder is returned instead of resting on the book (e.g. an `ioc` order, or a price-protected (`pp`) order trimmed by the protection range) also closes as `filled` even when less than the requested quantity executed. Confirm the executed amount with `filledQty`/`filledAmt`. | | `canceled` | Fully canceled | | `partiallyFilled` | Partially filled | | `partiallyFilledCanceled` | Partially filled and remaining amount canceled | | `expired` | Order submission failed (due to insufficient balance or timeInForce conditions) | ## Error Codes | Error | When | Path | |---|---|---| | `BAD_REQUEST` | Bad request. | `POST /v2/orders` | | `CANNOT_CANCEL_WITHDRAWAL` | The withdrawal cannot be canceled (likely because it's being processed) | `DELETE /v2/coin/withdrawal` | | `DAILY_LIMIT_EXCEEDED` | You have exceeded the daily withdrawal limit. | `POST /v2/coin/withdrawal` | | `DUPLICATE_CLIENT_ORDER_ID` | Request rejected due to duplicate `clientOrderId`. | `POST /v2/orders` | | `FORBIDDEN_WITHDRAWAL_ADDRESS` | Withdrawals to the address is forbidden due to policy. | `POST /v2/coin/withdrawal` | | `INVALID_CURRENCY` | Invalid currency | `POST /v2/coin/withdrawal` | | `INVALID_CURRENCY_PAIR` | Invalid symbol. | `POST /v2/orders` | | `INVALID_USER_STATUS` | Trading has been temporarily restricted according to Korbit's policy. | `POST /v2/coin/withdrawal`, `POST /v2/orders` | | `NOT_FOUND` | The withdrawal cannot be found | `DELETE /v2/coin/withdrawal` | | `NO_BALANCE` | Insufficient balance. | `POST /v2/coin/withdrawal`, `POST /v2/orders` | | `ONLY_SELL_LIMIT_ORDERS_ALLOWED` | Only limit sell orders are allowed during the initial listing period. | `POST /v2/orders` | | `ORDER_ALREADY_CANCELED` | Already canceled order | `DELETE /v2/orders` | | `ORDER_ALREADY_EXPIRED` | Already expired order | `DELETE /v2/orders` | | `ORDER_ALREADY_FILLED` | Already filled order | `DELETE /v2/orders` | | `ORDER_NOT_FOUND` | Not found order | `DELETE /v2/orders` | | `ORDER_VALUE_TOO_LARGE` | Order value exceeds the market's maximum. The bounds are per market, denominated in the pair's quote currency: see `maxOrderValue` and `quoteCurrency` in `GET /v2/currencyPairs`. Adjust `qty` * `price` (or `amt`) below it. | `POST /v2/orders` | | `ORDER_VALUE_TOO_SMALL` | Order value is below the market's minimum. The bounds are per market, denominated in the pair's quote currency: see `minOrderValue` and `quoteCurrency` in `GET /v2/currencyPairs`. Adjust `qty` * `price` (or `amt`) above it. | `POST /v2/orders` | | `PRICE_OVER_UPPER_BOUND` | Above the upper price limit during the initial listing period. | `POST /v2/orders` | | `PRICE_TICK_SIZE_INVALID` | Invalid tick size. | `POST /v2/orders` | | `PRICE_UNDER_LOWER_BOUND` | Below the lower price limit during the initial listing period. | `POST /v2/orders` | | `TOO_MANY_OPEN_ORDERS` | Order quantity limit exceeded. | `POST /v2/orders` | | `TRY_AGAIN` | The order is currently being processed. Please try again in a few moments later. | `DELETE /v2/orders` | | `UNREGISTERED_WITHDRAWAL_ADDRESS` | The address hasn't been registered as an OpenAPI withdrawal address. | `POST /v2/coin/withdrawal` | | `WITHDRAWAL_ALREADY_FINISHED` | The withdrawal has already been finished | `DELETE /v2/coin/withdrawal` | | `WITHDRAWAL_ALREADY_IN_PROGRESS` | A withdrawal is already in progress. Please try again after the current transaction is completed. | `POST /v2/coin/withdrawal` | | `WITHDRAWAL_SUSPENDED` | Withdrawal suspended | `POST /v2/coin/withdrawal` | ## Resilience & Safety Patterns These are the patterns a trading client must follow to avoid duplicate orders, double exposure, and mispriced orders. They build on the `korbitPrivate` helper above and use only documented fields. None of this changes when individual endpoints change, so treat it as fixed guidance. korbit-cli implements every pattern below; each links to the file that does, so you can read a working version. See also [Using the source as a reference implementation](#using-the-source-as-a-reference-implementation). ### 1. Idempotent, collision-safe order placement *korbit-cli: [`internal/ids/ids.go`](https://github.com/korbit-official/korbit-cli/blob/master/internal/ids/ids.go), [`internal/ops/op_place.go`](https://github.com/korbit-official/korbit-cli/blob/master/internal/ops/op_place.go)* A network timeout does **not** tell you whether the order was placed. Never blindly resend a placement — that risks a duplicate order. Make placement idempotent with a `clientOrderId`, which Korbit processes **only once**: a retried request that actually reached the server is collapsed server-side. A sound `clientOrderId` has to satisfy **two** properties at once, and they pull in opposite directions: - **Stable across retries of the same order** — every retry of *one intended order*, even after a crash and restart, must carry the *same* id, or the retry double-places. An id held only in memory is lost on restart; a freshly generated one would double-place. - **Unique across distinct orders** — two *different* orders must never share an id. A `clientOrderId` effectively **never frees up**, so any scheme that can repeat an id across genuinely-different orders will eventually have a perfectly valid order rejected with `DUPLICATE_CLIENT_ORDER_ID`. Get both by **minting once and persisting** — not by deriving the id from the order's contents: - When the strategy decides to place an order, mint a collision-resistant id. The simplest safe choice is a **UUIDv7** (`crypto.randomUUIDv7()` in Node) — it embeds a unix-ms timestamp (so ids stay time-ordered), is globally unique, and is exactly 36 chars, fitting `[0-9a-zA-Z.:_-]{1,36}`. If you prefer a readable scheme instead — a short **namespace** + a compact **placement id** + a **timestamp/random suffix** — assemble it from bounded parts and **validate the length** (≤36); never `.slice()` it to fit, or a long id can truncate and collapse two distinct placements onto the same value. - **Persist that id in your own store *before* you send the request**, keyed by your internal placement id. On any retry or restart, **reload and reuse the persisted id** — never regenerate it. - **Do not derive the id from the order's business identity** — a hash of `symbol`+`side`+`price`+`qty`, or your internal ids alone, with no time/uniqueness component. Because the id never frees up, the next genuinely-distinct order that happens to repeat those fields — a grid level revisited, a fixed-size recurring buy — collides with the old one and is rejected. The timestamp/uniqueness suffix plus your durable store are what prevent that. - Treat a `DUPLICATE_CLIENT_ORDER_ID` error as **success** — it means a prior attempt already registered *this* order. Look it up (recipe 2) instead of resending. ```js import crypto from "node:crypto"; // Mint a collision-resistant id once per placement decision, persist it, then // reuse the stored value on every retry/restart — never regenerate it. function clientOrderIdFor(store, placementId) { const existing = store.get(placementId); // your durable store (DB/kv), keyed by YOUR id if (existing) return existing; // a retry/restart reuses the persisted id const cid = crypto.randomUUIDv7(); // UUIDv7: 36 chars, time-ordered, fits the charset — no truncation store.set(placementId, cid); // persist BEFORE sending the order return cid; } ``` ```js async function placeIdempotent(order, clientOrderId, { maxRetries = 5 } = {}) { for (let attempt = 0; ; attempt++) { try { return await korbitPrivate("POST", "/v2/orders", { ...order, clientOrderId }); } catch (err) { // Already placed by an earlier attempt — reconcile, do not resend. if (err.code === "DUPLICATE_CLIENT_ORDER_ID") { return await korbitPrivate("GET", "/v2/orders", { symbol: order.symbol, clientOrderId }); } // Retry only transient failures, with backoff. (`err.code` is the symbolic // code from the error envelope's `error.message` — see the REST guide.) const transient = !err.status || err.status === 429 || err.status >= 500 || err.code === "TRY_AGAIN"; if (!transient || attempt >= maxRetries) throw err; const waitMs = err.status === 429 ? rateLimitWaitMs(err) : 2 ** attempt * 200; await sleep(waitMs); } } } ``` ### 2. Recovering from a lost placement response *korbit-cli: [`internal/ops/op_place.go`](https://github.com/korbit-official/korbit-cli/blob/master/internal/ops/op_place.go)* If a placement times out, decide whether it landed **before** resubmitting. - **If you set a `clientOrderId`** (always do for placements): query it directly. ```js const found = await korbitPrivate("GET", "/v2/orders", { symbol, clientOrderId }); ``` If found, the order exists — do not resend. Note: orders in `expired` or `canceled` status are no longer searchable by `clientOrderId` roughly 3 days after they close, and the same `clientOrderId` may be reusable after that window. - **If you did not set a `clientOrderId`** (e.g. recovering an externally placed order): list recent orders and match on `symbol` + `side` + `price` + `qty`, constrained to a **recent time window** using `createdAt`, before concluding the order is absent. ```js async function findRecentMatch({ symbol, side, price, qty }, sinceMs) { const orders = await korbitPrivate("GET", "/v2/orders", { symbol }); // recent orders for the symbol return orders.find( (o) => o.side === side && o.price === price && // compare as decimal strings, never as numbers o.qty === qty && Number(o.createdAt) >= sinceMs ) ?? null; } ``` This match has a residual race: an order placed microseconds before the timeout may not yet be visible, and two orders with identical fields in the same window are indistinguishable. Prefer `clientOrderId` whenever you control placement; use field matching only for recovery. ### 3. Decimal-safe order math *korbit-cli: [`internal/ops/tick.go`](https://github.com/korbit-official/korbit-cli/blob/master/internal/ops/tick.go)* Prices, quantities, and amounts in the quote currency (`symbol`'s second segment) are **decimal strings** in the API. Using binary floating point silently corrupts them (e.g. `0.1 + 0.2`), producing rejected or mispriced orders. - Keep prices/quantities/amounts as strings or a decimal library (BigDecimal, decimal.js); never round-trip through JS `number`. - **Snap the price to the symbol's tick size** before sending. Fetch `GET /v2/tickSizePolicy?symbol=`; from the returned `tickSizePolicy` array, pick the entry with the **largest `priceGte` that is ≤ your order price**, and use its `tickSize`. Both `priceGte` and `tickSize` are decimal strings. - **Enforce the pair's order value bounds** on `price × qty` (or `amt`) before sending. Read them from `GET /v2/currencyPairs`: `minOrderValue` / `maxOrderValue`, denominated in that entry's `quoteCurrency`. The bounds are **per pair**, so read each pair's own rather than hardcoding one constant. Either field is **omitted when the pair publishes no such bound** — then skip that check and let the server decide; an omission is not a guarantee that the order value is unconstrained. ```js import { Decimal } from "decimal.js"; // or any decimal lib async function snapPrice(symbol, rawPrice) { const [policy] = await korbitPrivate("GET", "/v2/tickSizePolicy", { symbol }); const price = new Decimal(rawPrice); // Largest priceGte <= price wins; the array is ascending by priceGte. const tier = policy.tickSizePolicy .filter((t) => price.gte(t.priceGte)) .sort((a, b) => new Decimal(a.priceGte).cmp(b.priceGte)) .pop(); const tickSize = new Decimal(tier.tickSize); // Floor to the nearest tick so the price is a valid multiple. return price.div(tickSize).floor().mul(tickSize).toFixed(); } // Cache the pair list — it changes with listings, not with your orders. async function marketRules(symbol) { const pairs = await korbitPrivate("GET", "/v2/currencyPairs"); const rules = pairs.find((p) => p.symbol === symbol); if (!rules) throw new Error(`unknown symbol ${symbol}`); return rules; // { symbol, status, baseCurrency, quoteCurrency, minOrderValue?, maxOrderValue? } } // An absent bound is a figure the pair does not publish — skip that check (never read it // as 0) and let the server decide; it is not a promise the value is unconstrained. function assertOrderValue(price, qty, rules) { const value = new Decimal(price).mul(qty); const { minOrderValue, maxOrderValue, quoteCurrency } = rules; if (minOrderValue !== undefined && value.lt(minOrderValue)) { throw new Error(`Order value ${value} below the ${minOrderValue} ${quoteCurrency} minimum`); } if (maxOrderValue !== undefined && value.gt(maxOrderValue)) { throw new Error(`Order value ${value} above the ${maxOrderValue} ${quoteCurrency} maximum`); } } assertOrderValue(price, qty, await marketRules(symbol)); ``` ### 4. Order-state, fees, and acceptance gotchas *korbit-cli: [`internal/ops/account_ops.go`](https://github.com/korbit-official/korbit-cli/blob/master/internal/ops/account_ops.go) (fees), [`internal/stream/state/state.go`](https://github.com/korbit-official/korbit-cli/blob/master/internal/stream/state/state.go) (status)* A few behaviors are easy to miss and lead to rejected orders, wrong position accounting, or a stuck bot: - **Know which currency the buy fee is charged in — it changes both sizing and fill accounting.** Fetch the symbol's fee policy with `GET /v2/tradingFeePolicy?symbol=` and read `buyFeeCurrency`: - **Quote-fee pair** (`buyFeeCurrency` equals the quote currency — `krw` on a `*_krw` pair): the exchange reserves the fee *on top of* the notional, so a buy needs `price × qty × (1 + maxFeeRate)` available. Sizing against the bare `price × qty` gets the order rejected for insufficient balance even when the notional fits. - **Coin-fee pair** (`buyFeeCurrency` is the base currency — `symbol`'s first segment, e.g. `btc`, `eth`, `usdt`, `usdc`): reserve only `price × qty` of the quote currency (no extra headroom), but the fee is deducted from the **coin you buy** — you receive about `qty × (1 − takerFeeRate)`, not the full `qty`. Don't assume the filled quantity is available to sell; read the actual balance (or subtract the fee) before placing a follow-up sell, or it fails for insufficient balance. - **Map the full status taxonomy, across REST and WebSocket.** The WebSocket order stream reports a fully-unfilled resting order as `unfilled`; it is the same live state as REST `open`. `pending` is **not** committed yet — it can still fail to `expired`, so never count it as a resting order. To decide whether an order is still live, check whether its status is one of the three open states — `pending`, `open`, `partiallyFilled` — and treat **any other** status as terminal; don't match a fixed list of terminal states, which can silently miss ones added later. - **A placement response means *accepted*, not *resting*.** An order whose `timeInForce` is `po`, `ioc`, or `fok` can be canceled immediately with no fill, yet the placement still returns an order id. Confirm the order's actual status before treating it as live. - **Use current, not delayed, data to reconcile.** `/v2/allOrders` may lag a few seconds; to decide whether a just-placed order landed, query `/v2/openOrders` or `/v2/orders` instead. - **History endpoints are windowed and capped.** `/v2/allOrders` and `/v2/myTrades` cover only the last 36 hours and return at most `limit` records, newest first (`startTime` inclusive, `endTime` exclusive). To page a larger window, set the next `endTime` to the oldest `createdAt`/`tradedAt` received **+1 ms** and de-duplicate by `orderId`/`tradeId`. Since `endTime` is exclusive, the +1 ms re-includes that last timestamp so same-millisecond ties aren't skipped, and dedup drops the boundary records you already have. - **WebSocket streams can redeliver events after a snapshot or reconnect.** Trade IDs increase monotonically per pair (though not contiguously), so drop any `tradeId` at or below the highest already seen for that pair. Key order updates by `orderId` and apply them idempotently. ### 5. Sizing back-to-back orders to the edge of your balance (local holds) *korbit-cli: [`internal/stream/state/localhold.go`](https://github.com/korbit-official/korbit-cli/blob/master/internal/stream/state/localhold.go)* Neither the `myAsset` stream nor `GET /v2/balance` reflects the funds an order reserves until that reservation has been applied and observed — there is a window between sending a placement and seeing its effect. Size a second order against `available` inside that window and you spend the same funds twice and get a **self-inflicted** `NO_BALANCE`. (This is separate from the exchange's own asynchronous rejection — see the expiry note below.) Close the window with a **local hold**: before sending, subtract the order's reservation from a local running `available`, and size follow-up orders against that net figure — never against the raw feed value. The reservation is the same amount recipe 4 already tells you the exchange locks — a sell holds the base `qty`; a buy holds the quote notional (`amt`, or `price × qty`), **plus** the `(1 + maxFeeRate)` headroom on quote-fee pairs — so no new math is needed here. Key each hold by the order's `clientOrderId`. This layer only ever **reduces your local view of available — never what you send**, so a wrong hold costs at most some idle capital or one `NO_BALANCE` (the no-hold status quo), never a bad order. That makes it safe to bias releasing *early*: **release a hold on the first of** — - **the order appearing on the `myOrder` stream** (any status — from then the server's own reservation, or a rejected order's lack of one, is authoritative); - **a failed or unresolved place call** (if you can't confirm the order is in flight, release); - **a private-stream reconnect or drop** (the reconnect snapshot re-baselines balances and old holds can no longer be matched — clear them all); - **a TTL backstop** of a few place round-trips, so no hold is ever permanent if a release signal is lost. Two limits. The `myOrder` and `myAsset` events for one order can arrive in either order, so key the release on `myOrder` and expect a brief transient double-count or gap that self-corrects as balance frames arrive. And this only removes the *self-inflicted* rejections: even an accepted order (you got an `orderId`) can still be **expired** later for insufficient balance, reported only on the order stream — so also release on expiry, and treat expiry as a real balance signal, not just the placement result. ### 6. Keeping the signing clock inside Korbit's window *korbit-cli: [`internal/clock/syncer.go`](https://github.com/korbit-official/korbit-cli/blob/master/internal/clock/syncer.go)* Every signed request carries a `timestamp` that Korbit accepts only inside an **asymmetric** window (see "Timestamp Window" in the REST guide): `recvWindow` (default 5 s, max 60 s) widens only the **past** side, while the future side is a **fixed 1 second** that `recvWindow` cannot extend. The failure mode bots miss follows directly: a local clock running even ~1 second **ahead** of Korbit's server rejects **every** signed request with `EXCEED_TIME_WINDOW`, and raising `recvWindow` does nothing — only pulling the `timestamp` back toward server time fixes it. So keep your host clock disciplined with NTP (chronyd/ntpd, or your platform's time-sync service), and confirm it is actually **running and converged** — a properly synced host stays within milliseconds of true time, far inside the fixed `+1 s` future bound, so no per-request server-time call is needed. Only if you can't guarantee a synced clock, sign against **Korbit's server clock** instead: call `GET /v2/time`, derive your offset, and stamp `timestamp` from `serverTime + offset` — biased slightly into the **past** (e.g. by half your round-trip) so network jitter can't push a request across the `+1 s` bound. Either way, stamp `timestamp` and compute the signature as the **last step before the request goes on the wire** — after any queue wait, rate-limit throttle, or retry backoff. Sign up front and then sit in a queue and the timestamp ages into the past; once the delay exceeds `recvWindow` you get `EXCEED_TIME_WINDOW` even with a perfectly synced clock. Treat `EXCEED_TIME_WINDOW` as a **pre-execution** rejection: Korbit refuses it at the clock gate *before* the request reaches the matching engine, so nothing happened. Unlike a network timeout or `5xx` — which are **ambiguous** (the order may have executed; reconcile with recipe 2, don't blindly resend) — it is always safe to correct your clock — re-sync NTP, or re-measure the offset against `/v2/time` — and resend the **same** request, money-movers included. ### 7. Scope every private call to the right sub-account (`accountSeq`) *korbit-cli: [`internal/accountseq/accountseq.go`](https://github.com/korbit-official/korbit-cli/blob/master/internal/accountseq/accountseq.go)* Private REST calls and private WebSocket subscriptions take an `accountSeq` sub-account selector (defaults to `1`, the main account). It is not only a placement input: the read-back that reconciles a just-placed order (recipe 2) and the `myOrder`/`myTrade` subscription that watches its fills **must use the same `accountSeq` you placed with**. Query or subscribe with the wrong one and the order or fill simply does not appear — an **empty result, not an error** — so a mis-scoped order looks lost when it is only being read on the wrong sub-account. Key your local order and balance state by `accountSeq` as well, so a multi-account bot never cross-attributes a fill or balance. The sub-accounts a key may use are listed in `allowedAccountSeqs` from `GET /v2/currentKeyInfo`; a value outside that set is rejected. (Deposit and withdrawal endpoints operate on the main account only — `accountSeq` `1`.) ## WebSocket API Use the public WebSocket for market data and the private WebSocket for account events: ```text wss://ws-api.korbit.co.kr/v2/public wss://ws-api.korbit.co.kr/v2/private ``` Private connections sign exactly like REST: `timestamp` and `signature` go in the URL query string; `X-KAPI-KEY` goes in the connection header. Subscribe and unsubscribe with messages of the form: ```json [ {"requestId": 1, "method": "subscribe", "type": "ticker", "symbols": ["btc_krw"]} ] ``` Data messages do not have a `status` field. Control messages do: ```json {"requestId": 1, "status": "success"} {"requestId": 1, "status": "fail", "code": "INVALID_SYMBOL", "message": "..."} {"status": "error", "message": "..."} ``` Public messages can be dropped under load — periodically reconcile against REST snapshots if correctness matters, and on reconnect refetch a fresh REST snapshot (e.g. `/v2/orderbook`) before trusting incremental updates again, since you may have missed messages while disconnected. Private messages are not dropped, but the socket can be force-closed; on reconnect, reconcile orders and balances via REST. | Channel | Auth | Purpose | |---|---|---| | [`ticker`](#method-subscribe_type-ticker) | (public) | Streams latest pricing information for a symbol. | | [`orderbook`](#method-subscribe_type-orderbook) | (public) | Streams orderbook data for a symbol. Up to 30 prices are available for each side. | | [`trade`](#method-subscribe_type-trade) | (public) | Streams real-time trades. The subscription snapshot carries only the latest trade(s), not full trade history — use REST GET /v2/trades for recent history. | | [`myOrder`](#method-subscribe_type-myOrder) | `readOrders` | Streams the changes in my orders. | | [`myTrade`](#method-subscribe_type-myTrade) | `readOrders` | Streams trades on my orders. | | [`myAsset`](#method-subscribe_type-myAsset) | `readBalances` | Streams changes to my balances in real time. | ## Quick Examples Fetch ticker: ```sh curl 'https://api.korbit.co.kr/v2/tickers?symbol=btc_krw,eth_krw' ``` Fetch candles: ```sh curl 'https://api.korbit.co.kr/v2/candles?symbol=btc_krw&interval=60&limit=100' ``` Place a limit order using the helper above: ```js await korbitPrivate("POST", "/v2/orders", { symbol: "btc_krw", side: "buy", orderType: "limit", price: "100000000", qty: "0.001", timeInForce: "gtc", // A UUIDv7 (`crypto.randomUUIDv7()`) minted once for this placement and persisted // (see Resilience #1): a retry/restart reuses the stored value; distinct orders never collide. clientOrderId: "019eabcf-7f2e-7587-979c-d67bde2b8967" }); ``` Cancel by `clientOrderId`: ```js await korbitPrivate("DELETE", "/v2/orders", { symbol: "btc_krw", // the same clientOrderId you stored for this order (above) clientOrderId: "019eabcf-7f2e-7587-979c-d67bde2b8967" }); ``` ## Bot Implementation Checklist For a trading bot, implement: 1. Config loader: symbol list, strategy settings, credential source. 2. Public market data adapter: REST snapshot + WebSocket updates. 3. Private account adapter: balance, open orders, recent fills, private WebSocket events. 4. Order manager: idempotent `clientOrderId`, placement, cancellation, terminal-state reconciliation. 5. Rate limiter: separate buckets for public, place order, cancel order, deposit/withdrawal, and other private calls. 6. Persistence: durable store for `clientOrderId`, `orderId`, latest status, fills, bot decisions. 7. Recovery: on startup and on WebSocket reconnect, fetch `/v2/openOrders`, `/v2/allOrders`, `/v2/myTrades`, `/v2/balance`. 8. Observability: structured logs and metrics for latency, API errors, rate-limit usage, fills. 9. Kill switch: env or file-based switch that immediately stops new orders and optionally cancels open bot-owned orders. Minimum safe live-trading flow: ```text load config -> verify keys with /v2/currentKeyInfo -> sync server time -> load balances -> load open orders -> start public WS -> start private WS -> place only clientOrderId-tagged orders -> reconcile every placement/cancel via REST and private WS ``` ## Full Reference The complete per-endpoint parameter and response reference follows in the next section — this file is self-sufficient. The same content is also split into smaller per-group files if you only need one domain: - [`rest_api/quotation.md`](#rest-·-quotation) - [`rest_api/trading.md`](#rest-·-trading) - [`rest_api/asset.md`](#rest-·-asset) - [`rest_api/deposit-crypto.md`](#rest-·-deposit-crypto) - [`rest_api/withdrawal-crypto.md`](#rest-·-withdrawal-crypto) - [`rest_api/krw.md`](#rest-·-depositwithdrawal-krw) - [`rest_api/other.md`](#rest-·-other-endpoints) - [`websocket_api/public.md`](#websocket-·-public-type) - [`websocket_api/private.md`](#websocket-·-private-type) # Build with the official CLI korbit-cli is a single static binary (Go, nothing to install at runtime) that exposes the whole Korbit Open API v2 — market data, trading, balances, deposits/withdrawals, and key management — as commands. It is built first as a stable tool surface for AI agents, and is equally usable by humans. Why an agent should prefer it: the CLI already does the things the rest of this guide tells you to implement carefully — request signing, idempotent order placement (every order carries a `clientOrderId`, reused on retry so a lost response can never double-place), auto-retry with backoff, clock-sync, decimal-safe sizing validated before anything is sent, and a local action journal of everything it did. Keys live in the CLI's own keystore; the private key never leaves the machine and is never printed. If you take this path, most of this guide becomes optional reference. ## When to use it - **Use the CLI** when your environment can run a binary or speak the Model Context Protocol (MCP). This is the recommended default for most agents — it removes the whole class of signing, idempotency, and retry mistakes by construction. - **Build your own client** (the rest of this guide) when you are embedding in a runtime where shelling out isn't viable, or you need full control over the wire protocol. ## Install One line — downloads the latest release, verifies its SHA-256, and puts `korbit` on your PATH: ```sh # Linux / macOS curl -fsSL https://docs.korbit.co.kr/install.sh | sh ``` ```powershell # Windows (PowerShell) irm https://docs.korbit.co.kr/install.ps1 | iex ``` An installed binary updates itself in place with `korbit self update` and checks its own health with `korbit self doctor`. To manage it yourself instead, download a release binary or `go install github.com/korbit-official/korbit-cli@latest` (which produces a `korbit-cli` binary — rename to `korbit` if you like). The command name in help and examples follows the binary's filename, so the `korbit …` examples below stay correct whichever name you install it under. A quickstart and the full command reference live in the repository: https://github.com/korbit-official/korbit-cli ## Driving it Two ways to drive the CLI; both run the same validated, signed, journaled path: - **Shell commands.** Invoke the commands and parse their output — pass `--json` for one machine-readable document per command. Run `korbit commands --json` for the full machine-readable catalog of every command, flag, enum, and exit code; prefer it over guessing the surface. - **MCP server.** For hosts that speak MCP (Codex, Claude Desktop, Claude Code, and others), run the bundled MCP server to expose every endpoint as an MCP tool — same validation, signing, and retries as the commands: ```sh korbit mcp serve --key ``` The CLI also bundles an Agent Skill that teaches the safe workflow and safety rules across its use cases — setup, the dry-run-first order flow, idempotent placement, monitoring, funding, and debugging. Install it with the CLI (`korbit agent skill install`), for Claude, Codex, or both. ## Sandbox and safety The CLI can run the same local sandbox described in the next section (`korbit sandbox start`; add `--paper --fresh` for paper trading on live production market data), so you can develop against a mock before touching production. Order placement is idempotent by default, money-moving writes are never auto-retried, and every signed call is journaled locally and replayable. One boundary to respect: the CLI's scripting hooks for building a streaming bot are experimental and may change — do not build long-lived bots on them yet, and treat them as throwaway one-shot commands until they are marked stable. ## Using the source as a reference implementation This part is for the other path — **building your own client**, not driving the CLI. korbit-cli is open source, and its source is a working reference for the hard parts of this guide: it implements the same signed, validated, idempotent path in Go, so you can read how each concern is handled and port it. Repository: https://github.com/korbit-official/korbit-cli. Where each concern lives (paths relative to the repo root): | Concern | Source | |---|---| | Request signing (HMAC-SHA256 / ED25519) | [`internal/korbit/client.go`](https://github.com/korbit-official/korbit-cli/blob/master/internal/korbit/client.go) | | `clientOrderId` minting (UUIDv7, charset) | [`internal/ids/ids.go`](https://github.com/korbit-official/korbit-cli/blob/master/internal/ids/ids.go) | | Order placement + lost-response reconcile | [`internal/ops/op_place.go`](https://github.com/korbit-official/korbit-cli/blob/master/internal/ops/op_place.go) | | Retry / backoff, HTTP 429 | [`internal/korbit/retry.go`](https://github.com/korbit-official/korbit-cli/blob/master/internal/korbit/retry.go) | | Tick-size snapping, decimal math | [`internal/ops/tick.go`](https://github.com/korbit-official/korbit-cli/blob/master/internal/ops/tick.go) | | Fee headroom, minimum-notional checks | [`internal/ops/account_ops.go`](https://github.com/korbit-official/korbit-cli/blob/master/internal/ops/account_ops.go), [`internal/ops/preplace.go`](https://github.com/korbit-official/korbit-cli/blob/master/internal/ops/preplace.go) | | Real-time account state via WebSocket — orders, balances, fills, no REST polling | [`internal/stream/state/state.go`](https://github.com/korbit-official/korbit-cli/blob/master/internal/stream/state/state.go) | | Local in-flight balance holds | [`internal/stream/state/localhold.go`](https://github.com/korbit-official/korbit-cli/blob/master/internal/stream/state/localhold.go) | | Signing-clock sync (`/v2/time`, drift) | [`internal/clock/syncer.go`](https://github.com/korbit-official/korbit-cli/blob/master/internal/clock/syncer.go) | | Sub-account (`accountSeq`) scoping | [`internal/accountseq/accountseq.go`](https://github.com/korbit-official/korbit-cli/blob/master/internal/accountseq/accountseq.go) | | WebSocket stream, reconnect, backfill | [`internal/stream/session.go`](https://github.com/korbit-official/korbit-cli/blob/master/internal/stream/session.go) | **Rate limiting is one thing to build yourself.** korbit-cli handles rate limits **reactively only**: on HTTP 429 it honors `Retry-After` (else backs off) within a per-call budget, then surfaces the error ([`internal/korbit/retry.go`](https://github.com/korbit-official/korbit-cli/blob/master/internal/korbit/retry.go)). It does not track your request rate to stay *under* the limit in advance. If your bot needs proactive rate-limit accounting — per-bucket budgeting across public, order, and cancel calls (see [Rate Limits](#rate-limits)) — implement it in your own client. # Local Sandbox > A self-hosted **mock** of the Korbit Open API v2 (REST + public/private WebSocket) for building and testing a bot with no real money and no real account. It is not the production server. This is not investment advice — see the sandbox disclaimer below. ## What it is The sandbox is a single self-contained file that runs on Node.js (version 24 or newer) with nothing to install. It serves the same REST endpoints and WebSocket channels as production on a local port, so you can develop and test a client against the real API shape. It helps you exercise the behaviors that commonly trip up bots — asynchronous fills, lagging order history, async WebSocket delivery, the error envelope, and rate limits — but it is a mock: matching, pricing, fills, and timing differ from the real exchange, and only common paths are implemented. ## Download and run Download the single file, initialize its database once, then start the server: ```sh curl -O https://docs.korbit.co.kr/korbit-sandbox.mjs node korbit-sandbox.mjs init-db node korbit-sandbox.mjs run ``` The server listens on `http://127.0.0.1:9999` and prints the REST base URL and the two WebSocket URLs on startup. Read the full terms at any time with `node korbit-sandbox.mjs license` (a short notice also prints on a bare invocation and on `--help`). ## Point your client at the sandbox The signing helpers in this guide use a `BASE_URL` constant for the REST host. To test against the sandbox, point that `BASE_URL` (and the WebSocket URLs) at the local server instead of production: ```text REST: http://127.0.0.1:9999 WS public: ws://127.0.0.1:9999/v2/public WS private: ws://127.0.0.1:9999/v2/private ``` It uses the same request format and signing rules as production (headers, `timestamp`, `recvWindow`), so the same client code can target both. Test here first, and only switch the base URL to production once the user explicitly enables live trading. ## Test credentials The sandbox provisions a demo user and prints its API key id and secret (an HMAC key and an Ed25519 key) when you initialize the database. Re-view them at any time with the status command: ```sh node korbit-sandbox.mjs status ``` Read these into the same environment variables your client already uses (for example `KORBIT_API_KEY` and `KORBIT_API_SECRET`). They are sandbox-only — never reuse them anywhere real, and never hard-code them. ## Shaping test scenarios The sandbox database is read live on every request, so you can change its state while the server is running — no restart needed. Set a balance, move a market price, or add a trading pair to drive a specific path through your bot: ```sh node korbit-sandbox.mjs set-balance --user 1 --currency btc --available 5 node korbit-sandbox.mjs set-market --symbol btc_krw --price 95000000 ``` This lets you reproduce partial fills, insufficient-balance rejections, and rate-limit backoff while the bot runs. ### Orderbook mode By default each pair overlays synthetic maker depth, so a lone bot always has a counterparty and its orders fill without anyone placing the other side. Switch a pair to a pure central limit order book — where orders match only real counterparties, so your bot must place both sides to trade — with `set-market`: ```sh # Pure CLOB — match only real counterparties: node korbit-sandbox.mjs set-market --symbol btc_krw --synthetic off # Synthetic overlay (default). When it is on, the synthetic depth's fill levels are # shaped by --price (the anchor), --spread, and --volatility — set them together to # pin a flat, predictable fill price (each is optional; only what you pass changes): node korbit-sandbox.mjs set-market --symbol btc_krw --synthetic on --price 95000000 --spread 0 --volatility 0 ``` `set-market` owns the whole market_state row — `--price`, `--spread`, `--volatility`, `--seed`, and `--synthetic` — and `status` shows each pair's current mode. Note that `--price`, `--spread`, `--volatility`, and `--seed` drive the public market-data feeds (ticker, trades, candles) in *both* modes; `--synthetic` decides whether that simulated depth exists in the pair's orderbook at all — as displayed levels and as a fill counterparty. In pure CLOB the published orderbook contains only real resting orders (an empty book until someone places one). The published orderbook is the matching book, as on the real exchange: your resting limit orders appear in `/v2/orderbook` and the WS orderbook channel, aggregated into their price level, and the ticker's `bestBidPrice`/`bestAskPrice` reflect them (on simulated-walk pairs; a paper-trading pair's ticker mirrors production verbatim and cannot include your local orders). One behavior to expect: a resting order the market has moved onto is mid-fill and momentarily *not displayed* — the book is never shown crossed; the order fills (or its remainder reappears) almost immediately. Displayed non-user depth is also finite within one book update: whatever your fills consume of it is gone — from the displayed book and from matching alike — until the next update replenishes it, and each displayed quantity fills at most once, shared across all orders in price-time priority. ### Paper trading (live market data) Initialize with `--source live` and every pair mirrors **real production market data** — prices, order book, and trades — while your orders still fill locally with simulated settlement (no real money; needs network): ```sh node korbit-sandbox.mjs init-db --source live node korbit-sandbox.mjs run # For the full production pair set (every 'launched' pair), add --mode korbit-api; # --market-cache reuses a recent snapshot so repeated inits skip the per-pair fetch: node korbit-sandbox.mjs init-db --mode korbit-api --source live --market-cache ./market.json ``` Use it to test a strategy against real market dynamics instead of the simulated walk. Flip one pair at runtime with `set-market --symbol btc_krw --source live` (or back with `--source walk`); `status` shows each pair's mode, mirror freshness, and the live feed's connection state. Fills are approximations — they consume only the sandbox's local view of the mirrored book (each displayed quantity fills at most once until the next book update; the real market is untouched) and queue position is not modeled — so paper results overestimate fill quality. When a live pair's mirror has no data yet (right after startup, or with the feed down), affected requests fail with the sandbox-only error code `SANDBOX_MARKET_DATA_UNAVAILABLE` (HTTP 503) — retry shortly; it is not a production code. Run `node korbit-sandbox.mjs help` for the full detail. (korbit-cli users: `korbit sandbox start --paper --fresh` does all of this in one step — and wipes the database each time; to restart an existing paper sandbox keeping balances and orders, run `korbit sandbox start --paper` without `--fresh`. By default only the sandbox's built-in fixture pairs are seeded; add `--all-pairs` — `korbit sandbox start --paper --all-pairs --fresh` — to seed every launched production pair from a live snapshot so the sandbox carries production's tradable pair set. That first start costs a few extra seconds; the snapshot is then cached beside the database, so repeated starts (including `--fresh`) reseed in well under a second.) ## Terms and conduct The sandbox has its own terms — read them with `node korbit-sandbox.mjs license`. Review them before you rely on the sandbox, and if you do not agree, delete it and do not use it. Keep your use within them: - Use it only for local development and testing of a Korbit Open API integration. Get it only from the official source above. - Keep it on your own machine — never expose its endpoints to the public internet, do not redistribute or commit the `.mjs` file, and do not remove its notice. - If you are an agent working autonomously, stay within these terms on the user's behalf; if a person is in the loop, or is asking about the sandbox so they can use it themselves, make sure they have seen them. - Optional hygiene: delete the sandbox once you no longer need it. ## Sandbox disclaimer The sandbox is a mock for development and testing only. It is not the Korbit production server, is not officially supported, and is provided as-is. Only common paths are implemented, and matching, pricing, fills, and timing differ from the real exchange — passing against the sandbox proves only that your code fits the API surface, not that your strategy is safe or correct. Before trading for real, test against the production API, start small, and monitor closely. Any automatically generated bot should get a human review before it runs anywhere real. Read the full terms with `node korbit-sandbox.mjs license`. # Full API Reference Everything below is the complete specification for every REST endpoint and WebSocket channel — request parameters, response fields, error codes, and request/response examples — generated from the same API spec that renders docs.korbit.co.kr. This file is self-sufficient: implementing any endpoint requires no document other than this one. # REST · Quotation ## Get Tickers {#get-_v2_tickers} ``` GET /v2/tickers ``` Get latest price and trading volume for a symbol or symbols. ### Schema ```ts // URL query parameter type RequestQuery = { /** Enter the symbols of the trading pairs you want to query, separated by commas (,). If omitted, information for all available trading pairs on Korbit will be returned. Example: "btc_krw,eth_krw" */ symbol?: string; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = Array<{ /** Trading pair symbol. Example: "btc_krw" */ symbol: string; /** Open price (24H). Example: "361922.23" */ open: string; /** High price (24H). Example: "361922.23" */ high: string; /** Low price (24H). Example: "361922.23" */ low: string; /** Last price (24H). Example: "361922.23" */ close: string; /** Previous close price (24H). Example: "261922.23" */ prevClose: string; /** changed price. `close - prevClose`. Example: "100000" */ priceChange: string; /** changed price percent. `100 * (close - prevClose) / prevClose`. Example: "38.18" */ priceChangePercent: string; /** Trading volume (24H), in the base currency (`symbol`'s first segment). Example: "100" */ volume: string; /** Trading volume (24H), in the quote currency (`symbol`'s second segment). Example: "1000000000" */ quoteVolume: string; /** Best bid price. Example: "5000" */ bestBidPrice: string; /** Best ask price. Example: "6000" */ bestAskPrice: string; /** Last traded timestamp (ms). Example: 1700000000000 */ lastTradedAt: number; }>; ``` ### Example #### Request ```sh curl 'https://api.korbit.co.kr/v2/tickers?symbol=btc_krw,eth_krw' ``` #### Response ```json { "success": true, "data": [ { "symbol": "btc_krw", "open": "77060000", "high": "79650000", "low": "76550000", "close": "77136000", "prevClose": "77060000", "priceChange": "76000", "priceChangePercent": "0.1", "volume": "48.73739983", "quoteVolume": "3785149733.32633", "bestBidPrice": "77136000", "bestAskPrice": "77193000", "lastTradedAt": 1725525721041 }, { "symbol": "eth_krw", "open": "3259000", "high": "3370000", "low": "3222000", "close": "3250000", "prevClose": "3259000", "priceChange": "-9000", "priceChangePercent": "-0.28", "volume": "161.99278306", "quoteVolume": "532827941.01581", "bestBidPrice": "3251000", "bestAskPrice": "3254000", "lastTradedAt": 1725525545630 } ] } ``` ## Get Orderbook {#get-_v2_orderbook} ``` GET /v2/orderbook ``` Get orderbook data. ### Schema ```ts // URL query parameter type RequestQuery = { /** Trading pair. Example: "btc_krw" */ symbol: string; /** Orderbook grouping level. Available levels can be checked via the Get Tick Size Policy API. If not provided, grouping will not be applied. Example: "1000" */ level?: string; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = { /** timestamp (ms). Example: 1700000000000 */ timestamp: number; /** bids */ bids: Array<{ /** price. Example: "250000" */ price: string; /** quantity. Example: "10" */ qty: string; /** Total amount in the quote currency (only set when orderbook grouping is used. When not using grouping, it can be calculated as `price * qty`). Example: "2500000" */ amt?: string; }>; /** asks */ asks: Array<{ /** price. Example: "250000" */ price: string; /** quantity. Example: "10" */ qty: string; /** Total amount in the quote currency (only set when orderbook grouping is used. When not using grouping, it can be calculated as `price * qty`). Example: "2500000" */ amt?: string; }>; }; ``` ### Example #### Request ```sh curl 'https://api.korbit.co.kr/v2/orderbook?symbol=btc_krw' ``` #### Response ```json { "success": true, "data": { "timestamp": 1708057740895, "bids": [ { "price": "73303000", "qty": "0.00898326" }, { "price": "73302000", "qty": "0.00790837" }, { "price": "73301000", "qty": "0.00843099" }, { "price": "73300000", "qty": "0.00054024" }, { "price": "73299000", "qty": "0.00663446" } ], "asks": [ { "price": "73304000", "qty": "0.00985212" }, { "price": "73305000", "qty": "0.00367505" }, { "price": "73306000", "qty": "0.0096254" }, { "price": "73307000", "qty": "0.00502544" }, { "price": "73308000", "qty": "0.00640584" } ] } } ``` ## Get Recent Trades {#get-_v2_trades} ``` GET /v2/trades ``` Get recent trades. ### Schema ```ts // URL query parameter type RequestQuery = { /** Trading pair. Example: "btc_krw" */ symbol: string; /** limit (range: 1 ~ 500). Example: 100 */ limit?: number; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = Array<{ /** timestamp (ms). Example: 1700000000000 */ timestamp: number; /** price. Example: "250000" */ price: string; /** quantity. Example: "10" */ qty: string; /** whether the taker is the buyer. Example: true */ isBuyerTaker: boolean; /** trade ID (the ID of the trade execution assigned to each trading pair). Monotonically increasing per trading pair, but not guaranteed to be contiguous. Example: 1234 */ tradeId: number; }>; ``` ### Example #### Request ```sh curl 'https://api.korbit.co.kr/v2/trades?symbol=btc_krw&limit=4' ``` #### Response ```json { "success": true, "data": [ { "timestamp": 1708057271149, "price": "70507000", "qty": "0.00981535", "isBuyerTaker": false, "tradeId": 1004 }, { "timestamp": 1708057271035, "price": "70508000", "qty": "0.00682475", "isBuyerTaker": false, "tradeId": 1003 }, { "timestamp": 1708057270922, "price": "70509000", "qty": "0.00844147", "isBuyerTaker": false, "tradeId": 1002 }, { "timestamp": 1708057270809, "price": "70510000", "qty": "0.00553963", "isBuyerTaker": false, "tradeId": 1001 } ] } ``` ## Get Candlesticks {#get-_v2_candles} ``` GET /v2/candles ``` Get historical candlesticks (klines) data. ### Schema ```ts // URL query parameter type RequestQuery = { /** Trading pair. Example: "btc_krw" */ symbol: string; /** interval */ interval: | "1" // 1 min | "5" // 5 mins | "15" // 15 mins | "30" // 30 mins | "60" // 1 hour | "240" // 4 hours | "1D" // 1 day | "1W" // 1 week ; /** start timestamp. (default: listed time). Example: 1600000000000 */ start?: number; /** end timestamp. must be larger than `start`. (default: now). Example: 1700000000000 */ end?: number; /** limit (range: 1 ~ 200). Example: 100 */ limit: number; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = Array<{ /** candle start timestamp. Example: 1619244573612 */ timestamp: number; /** open price. Example: "361922.23" */ open: string; /** high price. Example: "361922.23" */ high: string; /** low price. Example: "361922.23" */ low: string; /** close price. Example: "361922.23" */ close: string; /** Trading volume, in the base currency. Example: "100" */ volume: string; }>; ``` ### Example #### Request ```sh curl 'https://api.korbit.co.kr/v2/candles?symbol=btc_krw&interval=60&limit=5&end=1700000000000' ``` #### Response ```json { "success": true, "data": [ { "timestamp": 1708041600000, "open": "71211000", "high": "9999990000", "low": "300000", "close": "71392000", "volume": "1.932320026577213946" }, { "timestamp": 1708045200000, "open": "73510000", "high": "74605000", "low": "300000", "close": "72315000", "volume": "2.418698679231323743" }, { "timestamp": 1708048800000, "open": "72315000", "high": "9999990000", "low": "300000", "close": "72380000", "volume": "1.947520219976227299" }, { "timestamp": 1708052400000, "open": "70267000", "high": "74777000", "low": "300000", "close": "74049000", "volume": "2.254855048982521506" }, { "timestamp": 1708056000000, "open": "68304000", "high": "74834000", "low": "68241000", "close": "74825000", "volume": "0.630193755379195341" } ] } ``` ## Get Trading Pairs {#get-_v2_currencyPairs} ``` GET /v2/currencyPairs ``` Get supported trading pairs with their currencies and order value bounds. ### Schema ```ts // No request parameters // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = Array<{ /** trading pair symbol. Example: "btc_krw" */ symbol: string; status: | "launched" // trading available | "stopped" // trading unavailable ; /** Base currency of the pair — the asset being traded (the first segment of `symbol`). Example: "btc" */ baseCurrency: string; /** Quote currency of the pair — the currency the pair is priced in (the second segment of `symbol`). `price`, `amt`, `minOrderValue`, and `maxOrderValue` are denominated in it. Example: "krw" */ quoteCurrency: string; /** Minimum order value for this pair, in `quoteCurrency`. An order whose `qty` * `price` (or `amt` for a market buy) is below it is rejected with `ORDER_VALUE_TOO_SMALL`. Omitted when this pair publishes no minimum: skip the client-side check and let the server decide — an omitted bound is not a guarantee that the order value is unconstrained. Example: "5000" */ minOrderValue?: string; /** Maximum order value for this pair, in `quoteCurrency`. An order above it is rejected with `ORDER_VALUE_TOO_LARGE`. Omitted when this pair publishes no maximum: skip the client-side check and let the server decide — an omitted bound is not a guarantee that the order value is unconstrained. Example: "1000000000" */ maxOrderValue?: string; }>; ``` ### Example #### Request ```sh curl 'https://api.korbit.co.kr/v2/currencyPairs' ``` #### Response ```json { "success": true, "data": [ { "symbol": "btc_krw", "status": "launched", "baseCurrency": "btc", "quoteCurrency": "krw", "minOrderValue": "5000", "maxOrderValue": "1000000000" }, { "symbol": "eth_krw", "status": "launched", "baseCurrency": "eth", "quoteCurrency": "krw", "minOrderValue": "5000", "maxOrderValue": "1000000000" }, { "symbol": "xrp_krw", "status": "stopped", "baseCurrency": "xrp", "quoteCurrency": "krw", "minOrderValue": "5000", "maxOrderValue": "1000000000" } ] } ``` ## Get Tick Size Policy {#get-_v2_tickSizePolicy} ``` GET /v2/tickSizePolicy ``` Get tick size policy and orderbook grouping levels for a trading pair. ### Schema ```ts // URL query parameter type RequestQuery = { /** Trading pair symbol. Example: "xrp_krw" */ symbol: string; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = Array<{ /** Trading pair symbol. Example: "xrp_krw" */ symbol: string; /** * Tick size policy list * From the array, find the item with the largest `priceGte` value where `order price >= priceGte`, and use its `tickSize`. * Example: For an order price of 150, use the `tickSize` from the item with `priceGte` of "100". */ tickSizePolicy: Array<{ /** Minimum price for this range (inclusive). Example: "0.1" */ priceGte: string; /** Tick size for this price range. Example: "0.0001" */ tickSize: string; }>; /** Available orderbook grouping levels */ orderbookLevels: string[]; }>; ``` ### Example #### Request ```sh curl 'https://api.korbit.co.kr/v2/tickSizePolicy?symbol=xrp_krw' ``` #### Response ```json { "success": true, "data": [ { "symbol": "xrp_krw", "tickSizePolicy": [ { "priceGte": "0", "tickSize": "0.0001" }, { "priceGte": "1", "tickSize": "0.001" }, { "priceGte": "10", "tickSize": "0.01" }, { "priceGte": "100", "tickSize": "0.1" }, { "priceGte": "1000", "tickSize": "1" }, { "priceGte": "5000", "tickSize": "5" }, { "priceGte": "10000", "tickSize": "10" }, { "priceGte": "50000", "tickSize": "50" }, { "priceGte": "100000", "tickSize": "100" }, { "priceGte": "500000", "tickSize": "500" }, { "priceGte": "1000000", "tickSize": "1000" } ], "orderbookLevels": [ "0.1", "1", "10", "100" ] } ] } ``` # REST · Trading ## Get Order {#get-_v2_orders} ``` GET /v2/orders ``` Use either `orderId` or `clientOrderId` to query the status of an individual order. However, orders with the statuses `expired` or `canceled` cannot be retrieved approximately 3 days after they have been closed. **Required Permissions:** `readOrders` ### Schema ```ts // URL query parameter type RequestQuery = { /** Trading pair. Example: "btc_krw" */ symbol: string; /** Account sequence number. Defaults to 1 (main account). Example: 1 */ accountSeq?: number; /** Enter `orderID` (responsed by POST /v2/orders). Enter one of `orderID` or `clientOrderId`. Example: 1234 */ orderId?: number; /** Enter `clientOrderId` (requested by POST /v2/orders). Enter one of `orderID` or `clientOrderId`. Example: "20141231-155959-abcdef" */ clientOrderId?: string; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = { /** Order ID generated by the server. Example: 1234 */ orderId: number; /** `clientOrderId` submitted from the user by POST /v2/orders. Example: "20141231-155959-abcdef" */ clientOrderId?: string; /** symbol. Example: "btc_krw" */ symbol: string; orderType: | "limit" // limit order | "market" // market order | "best" // best bid/offer ; side: "buy" | "sell"; /** * Time in Force strategies. * * Default: * - limit order: `gtc` * - market order: `ioc` * - best bid/offer: no default(Bad Request error if omitted) * * For market orders, only `ioc` can be entered. */ timeInForce?: | "gtc" // Good-Till-Canceled. The order will remain valid until terminated (fully executed or canceled) | "ioc" // Immediate-Or-Cancel. The order will be filled immediately, if can not then will be canceled. (Taker-Only) | "fok" // Fill-Or-Kill. The order will be filled fully, if can not then will be canceled. (Taker-Only) | "po" // Post-Only. If the order would be filled immediately, then will be canceled. (Maker-Only) ; /** Order price (limit/BBO order only. no price for market order. For BBO orders it's set after the price is determined). Example: "5000" */ price?: string; /** Order quantity in the base currency (limit/BBO order or sell-side market order only. For BBO orders it's set after the quantity is determined). Example: "10" */ qty?: string; /** Purchase amount in the quote currency. (buy-side market/BBO order only). Example: "50000" */ amt?: string; /** Filled quantity in the base currency. Example: "10" */ filledQty: string; /** Filled amount in the quote currency. Example: "50000" */ filledAmt: string; /** Average execution price. Example: "5000" */ avgPrice?: string; /** Order timestamp (ms). Example: 1700000000000 */ createdAt: number; /** Last execution timestamp (ms). Example: 1700000000000 */ lastFilledAt?: number; /** Stop-limit order trigged timestamp. Example: 1700000000000 */ triggeredAt?: number; /** Order status */ status: | "pending" // Order pending. When the balance is insufficient or timeInForce condition is triggered, the order may fail and change to the `expired` status. | "open" // Fully unfilled | "filled" // Execution closed. An order whose unfilled remainder is returned instead of resting on the book (e.g. an `ioc` order, or a price-protected (`pp`) order trimmed by the protection range) also closes as `filled` even when less than the requested quantity executed. Confirm the executed amount with `filledQty`/`filledAmt`. | "canceled" // Fully canceled | "partiallyFilled" // Partially filled | "partiallyFilledCanceled" // Partially filled and remaining amount canceled | "expired" // Order submission failed (due to insufficient balance or timeInForce conditions) ; }; ``` ### Example #### Request ```sh curl -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/orders?clientOrderId=20141231-155959-abcdef&symbol=btc_krw×tamp=TIMESTAMP&signature=SIGNATURE' ``` #### Response ```json { "success": true, "data": { "orderId": 1234, "clientOrderId": "20141231-155959-abcdef", "symbol": "btc_krw", "orderType": "limit", "side": "buy", "timeInForce": "gtc", "avgPrice": "5000", "price": "5000", "qty": "10", "filledQty": "1", "filledAmt": "5000", "createdAt": 1700000000000, "lastFilledAt": 1700000000000, "status": "partiallyFilled" } } ``` ## Get Open Orders {#get-_v2_openOrders} ``` GET /v2/openOrders ``` Query the list of open orders for a single trading pair. Only orders with the status `open` or `partiallyFilled` are queried. **Required Permissions:** `readOrders` ### Schema ```ts // URL query parameter type RequestQuery = { /** Trading pair. Example: "btc_krw" */ symbol: string; /** Account sequence number. Defaults to 1 (main account). Example: 1 */ accountSeq?: number; /** Number of queries (range: 1 to 1000). Default is 500. Example: 100 */ limit?: number; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = Array<{ /** Order ID generated by the server. Example: 1234 */ orderId: number; /** `clientOrderId` submitted from the user by POST /v2/orders. Example: "20141231-155959-abcdef" */ clientOrderId?: string; orderType: | "limit" // limit order | "market" // market order | "best" // best bid/offer ; side: "buy" | "sell"; /** Order price (limit/BBO order only. no price for market order. For BBO orders it's set after the price is determined). Example: "5000" */ price?: string; /** Order quantity in the base currency (limit/BBO order or sell-side market order only. For BBO orders it's set after the quantity is determined). Example: "10" */ qty?: string; /** Purchase amount in the quote currency. (buy-side market/BBO order only). Example: "50000" */ amt?: string; /** Filled quantity in the base currency. Example: "10" */ filledQty: string; /** Filled amount in the quote currency. Example: "50000" */ filledAmt: string; /** Average execution price. Example: "5000" */ avgPrice?: string; /** Order timestamp (ms). Example: 1700000000000 */ createdAt: number; /** Last execution timestamp (ms). Example: 1700000000000 */ lastFilledAt?: number; /** Order status */ status: | "pending" // Order pending. When the balance is insufficient or timeInForce condition is triggered, the order may fail and change to the `expired` status. | "open" // Fully unfilled | "filled" // Execution closed. An order whose unfilled remainder is returned instead of resting on the book (e.g. an `ioc` order, or a price-protected (`pp`) order trimmed by the protection range) also closes as `filled` even when less than the requested quantity executed. Confirm the executed amount with `filledQty`/`filledAmt`. | "canceled" // Fully canceled | "partiallyFilled" // Partially filled | "partiallyFilledCanceled" // Partially filled and remaining amount canceled | "expired" // Order submission failed (due to insufficient balance or timeInForce conditions) ; }>; ``` ### Example #### Request ```sh curl -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/openOrders?limit=100&symbol=btc_krw×tamp=TIMESTAMP&signature=SIGNATURE' ``` #### Response ```json { "success": true, "data": [ { "orderId": 1234, "orderType": "limit", "side": "buy", "avgPrice": "5000", "price": "5000", "qty": "10", "filledQty": "1", "filledAmt": "5000", "createdAt": 1700000000000, "lastFilledAt": 1700000000000, "status": "partiallyFilled" }, { "orderId": 1235, "orderType": "limit", "side": "sell", "price": "5000", "qty": "10", "filledQty": "0", "filledAmt": "0", "createdAt": 1700000000000, "status": "open" } ] } ``` ## Get All Orders {#get-_v2_allOrders} ``` GET /v2/allOrders ``` Query the recent order list for a single trading pair. Only orders created within 36 hours can be queried. This API is for checking order history, and the information provided may have a delay of a few seconds. If you need current information without delay, please use the `/v2/openOrders` or `/v2/orders` API. **Required Permissions:** `readOrders` ### Schema ```ts // URL query parameter type RequestQuery = { /** Trading pair. Example: "btc_krw" */ symbol: string; /** Account sequence number. Defaults to 1 (main account). Example: 1 */ accountSeq?: number; /** Query start time. Inclusive: a record at exactly this time is returned. Only data up to 36 hours prior to the current time can be queried. If not set, the query will retrieve data from 36 hours ago by default. */ startTime?: number; /** Query end time. Exclusive: a record at exactly this time is not returned. If not set, data is retrieved up to the current time. Results are newest-first and capped at limit; to retrieve more, narrow the range with startTime/endTime. */ endTime?: number; /** Maximum number of queries (range: 1 to 1000). Default is 500. Example: 100 */ limit?: number; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = Array<{ /** Order ID generated by the server. Example: 1234 */ orderId: number; /** `clientOrderId` submitted from the user by POST /v2/orders. Example: "20141231-155959-abcdef" */ clientOrderId?: string; /** symbol. Example: "btc_krw" */ symbol: string; orderType: | "limit" // limit order | "market" // market order | "best" // best bid/offer ; side: "buy" | "sell"; /** * Time in Force strategies. * * Default: * - limit order: `gtc` * - market order: `ioc` * - best bid/offer: no default(Bad Request error if omitted) * * For market orders, only `ioc` can be entered. */ timeInForce?: | "gtc" // Good-Till-Canceled. The order will remain valid until terminated (fully executed or canceled) | "ioc" // Immediate-Or-Cancel. The order will be filled immediately, if can not then will be canceled. (Taker-Only) | "fok" // Fill-Or-Kill. The order will be filled fully, if can not then will be canceled. (Taker-Only) | "po" // Post-Only. If the order would be filled immediately, then will be canceled. (Maker-Only) ; /** Order price (limit/BBO order only. no price for market order. For BBO orders it's set after the price is determined). Example: "5000" */ price?: string; /** Order quantity in the base currency (limit/BBO order or sell-side market order only. For BBO orders it's set after the quantity is determined). Example: "10" */ qty?: string; /** Purchase amount in the quote currency. (buy-side market/BBO order only). Example: "50000" */ amt?: string; /** Filled quantity in the base currency. Example: "10" */ filledQty: string; /** Filled amount in the quote currency. Example: "50000" */ filledAmt: string; /** Average execution price. Example: "5000" */ avgPrice?: string; /** Order timestamp (ms). Example: 1700000000000 */ createdAt: number; /** Last execution timestamp (ms). Example: 1700000000000 */ lastFilledAt?: number; /** Stop-limit order trigged timestamp. Example: 1700000000000 */ triggeredAt?: number; /** Order status */ status: | "pending" // Order pending. When the balance is insufficient or timeInForce condition is triggered, the order may fail and change to the `expired` status. | "open" // Fully unfilled | "filled" // Execution closed. An order whose unfilled remainder is returned instead of resting on the book (e.g. an `ioc` order, or a price-protected (`pp`) order trimmed by the protection range) also closes as `filled` even when less than the requested quantity executed. Confirm the executed amount with `filledQty`/`filledAmt`. | "canceled" // Fully canceled | "partiallyFilled" // Partially filled | "partiallyFilledCanceled" // Partially filled and remaining amount canceled | "expired" // Order submission failed (due to insufficient balance or timeInForce conditions) ; }>; ``` ### Example #### Request ```sh curl -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/allOrders?limit=100&symbol=btc_krw×tamp=TIMESTAMP&signature=SIGNATURE' ``` #### Response ```json { "success": true, "data": [ { "orderId": 1234, "clientOrderId": "20141231-155959-abcdef", "symbol": "btc_krw", "orderType": "limit", "side": "buy", "timeInForce": "gtc", "avgPrice": "5000", "price": "5000", "qty": "10", "filledQty": "1", "filledAmt": "5000", "createdAt": 1700000000000, "lastFilledAt": 1700000000000, "status": "partiallyFilled" }, { "orderId": 1235, "clientOrderId": "20141231-155959-abcdeg", "symbol": "btc_krw", "orderType": "limit", "side": "sell", "timeInForce": "gtc", "price": "5000", "qty": "10", "filledQty": "0", "filledAmt": "0", "createdAt": 1700000000000, "lastFilledAt": 1700000000000, "status": "open" } ] } ``` ## Get Recent Trades {#get-_v2_myTrades} ``` GET /v2/myTrades ``` Query the recent trades list for a single trading pair. Only trade history from the past 36 hours can be queried. This API is for checking trade history, and the information provided may have a delay of a few seconds. **Required Permissions:** `readOrders` ### Schema ```ts // URL query parameter type RequestQuery = { /** Trading pair. Example: "btc_krw" */ symbol: string; /** Account sequence number. Defaults to 1 (main account). Example: 1 */ accountSeq?: number; /** Query start time. Inclusive: a record at exactly this time is returned. Only data up to 36 hours prior to the current time can be queried. If not set, the query will retrieve data from 36 hours ago by default. */ startTime?: number; /** Query end time. Exclusive: a record at exactly this time is not returned. If not set, data is retrieved up to the current time. Results are newest-first and capped at limit; to retrieve more, narrow the range with startTime/endTime. */ endTime?: number; /** Maximum number of queries (range: 1 to 1000). Default is 500. Example: 100 */ limit?: number; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = Array<{ /** symbol. Example: "btc_krw" */ symbol: string; /** trade ID (the ID of the trade execution assigned to each trading pair). Monotonically increasing per trading pair, but not guaranteed to be contiguous. Example: 1234 */ tradeId: number; /** order ID. Example: 1234 */ orderId: number; side: "buy" | "sell"; /** trade price. Example: "5000" */ price: string; /** trade quantity (base currency). Example: "10" */ qty: string; /** trade amount (quote currency). Example: "50000" */ amt: string; /** trade timestamp (ms). Example: 1700000000000 */ tradedAt: number; /** taker trade `true`, maker trade `false`. Example: true */ isTaker: boolean; /** asset used for fee payment. Example: "krw" */ feeCurrency?: string; /** fee quantity. Example: "50" */ feeQty?: string; }>; ``` ### Example #### Request ```sh curl -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/myTrades?limit=100&symbol=btc_krw×tamp=TIMESTAMP&signature=SIGNATURE' ``` #### Response ```json { "success": true, "data": [ { "symbol": "btc_krw", "tradeId": 52, "orderId": 382312, "side": "buy", "price": "5000", "qty": "10", "amt": "50000", "tradedAt": 1700000000000, "isTaker": true, "feeCurrency": "krw", "feeQty": "50" } ] } ``` ## Place Order {#post-_v2_orders} ``` POST /v2/orders ``` Place a new order. **Required Permissions:** `writeOrders` ### Schema ```ts // POST body — `Content-Type: application/x-www-form-urlencoded` type RequestBody = { /** Trading pair. Example: "btc_krw" */ symbol: string; /** Account sequence number. Defaults to 1 (main account). Example: 1 */ accountSeq?: number; side: "buy" | "sell"; /** * order price for a limit order. * omit for a market/BBO order. * Example: "250000" */ price?: string; /** * order quantity, in the base currency, for a limit order and a market/BBO sell order. * omit for a market/BBO buy order. * Example: "10" */ qty?: string; /** * order amount (purchase amount), in the quote currency, for a market/BBO buy order. * omit for a limit order and a market/BBO sell order. * Example: "250000" */ amt?: string; orderType: | "limit" // limit order | "market" // market order | "best" // best bid/offer. The `timeInForce` and `bestNth` parameters must be set. ; /** * Selects the order's price when orderType=`best`. For other types, this parameter must be omitted. * - when timeInForce is one of `gtc`,`ioc`,`fok`: Opponent N price level where N is 1 ~ 5. * - when timeInForce is `po`: Queue N price level where N is 1 ~ 5. * Example: 1 */ bestNth?: number; /** * Time in Force strategies. * * Default: * - limit order: `gtc` * - market order: `ioc` * - best bid/offer: no default(Bad Request error if omitted) * * For market orders, only `ioc` can be entered. */ timeInForce?: | "gtc" // Good-Till-Canceled. The order will remain valid until terminated (fully executed or canceled) | "ioc" // Immediate-Or-Cancel. The order will be filled immediately, if can not then will be canceled. (Taker-Only) | "fok" // Fill-Or-Kill. The order will be filled fully, if can not then will be canceled. (Taker-Only) | "po" // Post-Only. If the order would be filled immediately, then will be canceled. (Maker-Only) ; /** * User-defined order ID. Even if multiple requests are made with the same `clientOrderId`, it will be processed only once. * You can search for the order using `clientOrderId` with the `GET /v2/orders`. Only strings matching the following regex pattern are allowed: `[0-9a-zA-Z.:_-]{1,36}` * However, orders with the statuses `expired` or `canceled` cannot be searched by `clientOrderId` or same `clientOrderId` can be reused approximately three days after they have been closed. * Example: "20141231-155959-abcdef" */ clientOrderId?: string; /** * Price protection. Set to `true` to enable the price protection feature. This order will only be executed within the price protection range when matched as a taker. * Use the `ppPercent` parameter to change the threshold. */ pp?: string; /** * Threshold(in percent) for the price protection feature. Set an integer between 1 and 100. * If the price protection threshold is set to 5, this order will only be executed within 5% of the midpoint price (between the best ask and best bid) when matched as a taker order. Any unfilled quantity will be canceled. * If you enable price protection but do not set ppPercent, the default (5) will be used. */ ppPercent?: string; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = { /** order ID. Example: 1234 */ orderId: number; }; ``` ### Error Code - `DUPLICATE_CLIENT_ORDER_ID` — Request rejected due to duplicate `clientOrderId`. - `INVALID_CURRENCY_PAIR` — Invalid symbol. - `INVALID_USER_STATUS` — Trading has been temporarily restricted according to Korbit's policy. - `BAD_REQUEST` — Bad request. - `NO_BALANCE` — Insufficient balance. - `ONLY_SELL_LIMIT_ORDERS_ALLOWED` — Only limit sell orders are allowed during the initial listing period. - `ORDER_VALUE_TOO_LARGE` — Order value exceeds the market's maximum. The bounds are per market, denominated in the pair's quote currency: see `maxOrderValue` and `quoteCurrency` in `GET /v2/currencyPairs`. Adjust `qty` * `price` (or `amt`) below it. - `ORDER_VALUE_TOO_SMALL` — Order value is below the market's minimum. The bounds are per market, denominated in the pair's quote currency: see `minOrderValue` and `quoteCurrency` in `GET /v2/currencyPairs`. Adjust `qty` * `price` (or `amt`) above it. - `PRICE_OVER_UPPER_BOUND` — Above the upper price limit during the initial listing period. - `PRICE_UNDER_LOWER_BOUND` — Below the lower price limit during the initial listing period. - `PRICE_TICK_SIZE_INVALID` — Invalid tick size. - `TOO_MANY_OPEN_ORDERS` — Order quantity limit exceeded. ### Example #### Request ```sh curl -X POST -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/orders' -H Content-Type: application/x-www-form-urlencoded --data-raw orderType=limit&price=250000&qty=10&side=buy&symbol=btc_krw&timeInForce=gtc×tamp=TIMESTAMP&signature=SIGNATURE ``` #### Response ```json { "success": true, "data": { "orderId": 1234 } } ``` ## Cancel Order {#delete-_v2_orders} ``` DELETE /v2/orders ``` Requests to cancel an open order. If the API call is successful, the cancel request is accepted and the order will be canceled soon. If the error code is one of `ORDER_ALREADY_CANCELED`, `ORDER_ALREADY_FILLED`, or `ORDER_ALREADY_EXPIRED`, it means the order has been already been closed. **Required Permissions:** `writeOrders` ### Schema ```ts // URL query parameter type RequestQuery = { /** Trading pair. Example: "btc_krw" */ symbol: string; /** Account sequence number. Defaults to 1 (main account). Example: 1 */ accountSeq?: number; /** `orderId` responsed in `POST /v2/orders`. You must choose to enter either `orderId` or `clientOrderId`. Example: 1234 */ orderId?: number; /** `clientOrderId` which is a user-defined order ID requested in `POST /v2/orders`. You must choose to enter either `orderId` or `clientOrderId`. Example: "20141231-155959-abcdef" */ clientOrderId?: string; }; ``` ### Error Code - `ORDER_NOT_FOUND` — Not found order - `ORDER_ALREADY_CANCELED` — Already canceled order - `ORDER_ALREADY_FILLED` — Already filled order - `ORDER_ALREADY_EXPIRED` — Already expired order - `TRY_AGAIN` — The order is currently being processed. Please try again in a few moments later. ### Example #### Request ```sh curl -X DELETE -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/orders?orderId=1234&symbol=btc_krw×tamp=TIMESTAMP&signature=SIGNATURE' ``` #### Response ```json { "success": true } ``` # REST · Asset ## Get Balance {#get-_v2_balance} ``` GET /v2/balance ``` Get balance. **Required Permissions:** `readBalances` ### Schema ```ts // URL query parameter type RequestQuery = { /** Account sequence number. Defaults to 1 (main account). Example: 1 */ accountSeq?: number; /** List of assets to query. Enter them separated by commas (,). If this field is not provided, all currently held assets will be queried. Example: "btc,eth" */ currencies?: string; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = Array<{ /** asset name. Example: "krw" */ currency: string; /** balance. `available + tradeInUse + withdrawalInUse`. Example: "100" */ balance: string; /** available quantity. Example: "70" */ available: string; /** quantity in trade. Example: "20" */ tradeInUse: string; /** quantity in withdrawal. Example: "10" */ withdrawalInUse: string; /** average purchase price. Example: "5000" */ avgPrice: string; }>; ``` ### Example #### Request ```sh curl -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/balance?currencies=btc%2Ceth×tamp=TIMESTAMP&signature=SIGNATURE' ``` #### Response ```json { "success": true, "data": [ { "currency": "btc", "balance": "100", "available": "70", "tradeInUse": "20", "withdrawalInUse": "10", "avgPrice": "5000" }, { "currency": "eth", "balance": "100", "available": "70", "tradeInUse": "20", "withdrawalInUse": "10", "avgPrice": "5000" } ] } ``` # REST · Deposit (Crypto) ## Get All Address {#get-_v2_coin_depositAddresses} ``` GET /v2/coin/depositAddresses ``` Retrieve the list of cryptocurrency deposit addresses. **Required Permissions:** `readDeposits` ### Schema ```ts // URL query parameter type RequestQuery = { /** Account sequence number. Deposit/withdrawal APIs only operate on the main account, so only `1` is accepted. Defaults to 1. Example: 1 */ accountSeq?: number; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = Array<{ /** symbol of the asset. Example: "btc" */ currency: string; /** symbol of the blockchain network. Example: "ETH" */ network: string; /** deposit address. Example: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa" */ address: string; /** secondary address (destination tag, memo, etc. if none, then null.) */ secondaryAddress?: string | null; }>; ``` ### Example #### Request ```sh curl -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/coin/depositAddresses?timestamp=TIMESTAMP&signature=SIGNATURE' ``` #### Response ```json { "success": true, "data": [ { "currency": "btc", "network": "BTC", "address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa" }, { "currency": "xrp", "network": "XRP", "address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", "secondaryAddress": "1234" } ] } ``` ## Get Single Address {#get-_v2_coin_depositAddress} ``` GET /v2/coin/depositAddress ``` Get the deposit address for a single cryptocurrency. **Required Permissions:** `readDeposits` ### Schema ```ts // URL query parameter type RequestQuery = { /** Account sequence number. Deposit/withdrawal APIs only operate on the main account, so only `1` is accepted. Defaults to 1. Example: 1 */ accountSeq?: number; /** symbol of the asset. Example: "btc" */ currency: string; /** * symbol of the blockchain network. List of networks can be queried using the `/v2/currencies` API. * Uses the default network if omitted. Please always specify the network to prevent possible errors, as the default network can be changed. * Example: "BTC" */ network?: string; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = { /** symbol of the asset. Example: "btc" */ currency: string; /** symbol of the blockchain network. Example: "ETH" */ network: string; /** deposit address. Example: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa" */ address: string; /** secondary address (destination tag, memo, etc. if none, then null.) */ secondaryAddress?: string | null; }; ``` ### Example #### Request ```sh curl -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/coin/depositAddress?currency=btc×tamp=TIMESTAMP&signature=SIGNATURE' ``` #### Response ```json { "success": true, "data": { "currency": "btc", "network": "BTC", "address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa" } } ``` ## Generate Address {#post-_v2_coin_depositAddress} ``` POST /v2/coin/depositAddress ``` Generate a cryptocurrency deposit address. If a deposit address already exists, the existing address will be returned. **Required Permissions:** `writeDeposits` ### Schema ```ts // POST body — `Content-Type: application/x-www-form-urlencoded` type RequestBody = { /** Account sequence number. Deposit/withdrawal APIs only operate on the main account, so only `1` is accepted. Defaults to 1. Example: 1 */ accountSeq?: number; /** symbol of the asset. Example: "btc" */ currency: string; /** * symbol of the blockchain network. List of networks can be queried using the `/v2/currencies` API. * Uses the default network if omitted. Please always specify the network to prevent possible errors, as the default network can be changed. * Example: "BTC" */ network?: string; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = { /** symbol of the asset. Example: "btc" */ currency: string; /** symbol of the blockchain network. Example: "ETH" */ network: string; /** deposit address. Example: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa" */ address: string; /** secondary address (destination tag, memo, etc. if none, then null.) */ secondaryAddress?: string | null; }; ``` ### Example #### Request ```sh curl -X POST -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/coin/depositAddress' -H Content-Type: application/x-www-form-urlencoded --data-raw currency=btc×tamp=TIMESTAMP&signature=SIGNATURE ``` #### Response ```json { "success": true, "data": { "currency": "btc", "network": "BTC", "address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa" } } ``` ## Get Recent Deposits {#get-_v2_coin_recentDeposits} ``` GET /v2/coin/recentDeposits ``` Get recent deposit history. **Required Permissions:** `readDeposits` ### Schema ```ts // URL query parameter type RequestQuery = { /** Account sequence number. Deposit/withdrawal APIs only operate on the main account, so only `1` is accepted. Defaults to 1. Example: 1 */ accountSeq?: number; /** symbol of the asset. Example: "btc" */ currency: string; /** Maximum number of queries (range: 1 to 100). Example: 100 */ limit?: number; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = Array<{ /** deposit ID. Example: 1234 */ id: number; /** symbol of the blockchain network. Example: "ETH" */ network: string; /** deposit address. Example: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa" */ address: string; /** secondary address (destination tag, memo, etc. if none, then null.) */ secondaryAddress?: string | null; /** deposit status */ status: | "pending" // Deposit transaction is detected on the network. | "actionRequired" // Pending deposit documentation submission. To process the deposit, please submit the required documents for approval on the Korbit website. | "reviewing" // Deposit documentation reviewing. | "done" // Deposit done. | "refunded" // Deposit amount returned after review rejection. | "failed" // Deposit failed (e.g., due to issues with the transaction). ; /** transaction hash. Example: "0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" */ transactionHash: string; /** symbol of the asset. Example: "btc" */ currency: string; /** deposit quantity. Example: "1.234" */ quantity: string; /** deposit timestamp (ms). Example: 1700000000000 */ createdAt: number; }>; ``` ### Example #### Request ```sh curl -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/coin/recentDeposits?currency=btc&limit=100×tamp=TIMESTAMP&signature=SIGNATURE' ``` #### Response ```json { "success": true, "data": [ { "id": 1234, "network": "BTC", "address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", "secondaryAddress": null, "status": "done", "transactionHash": "0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", "currency": "btc", "quantity": "1.234", "createdAt": 1700000000000 } ] } ``` ## Get Deposit Status {#get-_v2_coin_deposit} ``` GET /v2/coin/deposit ``` Check the status of cryptocurrency deposits. **Required Permissions:** `readDeposits` ### Schema ```ts // URL query parameter type RequestQuery = { /** Account sequence number. Deposit/withdrawal APIs only operate on the main account, so only `1` is accepted. Defaults to 1. Example: 1 */ accountSeq?: number; /** symbol of the asset. Example: "btc" */ currency: string; /** deposit ID. Example: 1234 */ coinDepositId: number; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = { /** deposit ID. Example: 1234 */ id: number; /** symbol of the blockchain network. Example: "ETH" */ network: string; /** deposit address. Example: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa" */ address: string; /** secondary address (destination tag, memo, etc. if none, then null.) */ secondaryAddress?: string | null; /** deposit status */ status: | "pending" // Deposit transaction is detected on the network. | "actionRequired" // Pending deposit documentation submission. To process the deposit, please submit the required documents for approval on the Korbit website. | "reviewing" // Deposit documentation reviewing. | "done" // Deposit done. | "refunded" // Deposit amount returned after review rejection. | "failed" // Deposit failed (e.g., due to issues with the transaction). ; /** transaction hash. Example: "0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" */ transactionHash: string; /** symbol of the asset. Example: "btc" */ currency: string; /** deposit quantity. Example: "1.234" */ quantity: string; /** deposit timestamp (ms). Example: 1700000000000 */ createdAt: number; }; ``` ### Example #### Request ```sh curl -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/coin/deposit?coinDepositId=1234¤cy=btc×tamp=TIMESTAMP&signature=SIGNATURE' ``` #### Response ```json { "success": true, "data": { "id": 1234, "network": "BTC", "address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", "secondaryAddress": null, "status": "done", "transactionHash": "0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", "currency": "btc", "quantity": "1.234", "createdAt": 1700000000000 } } ``` # REST · Withdrawal (Crypto) ## Get Address {#get-_v2_coin_withdrawableAddresses} ``` GET /v2/coin/withdrawableAddresses ``` Retrieve the list of addresses registered for API withdrawals. **Required Permissions:** `readWithdrawals` ### Schema ```ts // URL query parameter type RequestQuery = { /** Account sequence number. Deposit/withdrawal APIs only operate on the main account, so only `1` is accepted. Defaults to 1. Example: 1 */ accountSeq?: number; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = Array<{ /** symbol of the blockchain network. Example: "ETH" */ network: string; /** symbol of the asset. Omitted for withdraw addesses registered for any currency on the network. Example: "btc" */ currency?: string; /** address. Example: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa" */ address: string; /** secondary address (destination tag, memo, etc. if none, then null.) */ secondaryAddress?: string | null; }>; ``` ### Example #### Request ```sh curl -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/coin/withdrawableAddresses?timestamp=TIMESTAMP&signature=SIGNATURE' ``` #### Response ```json { "success": true, "data": [ { "network": "BTC", "currency": "btc", "address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa" }, { "network": "ETH", "address": "0x05a56e2d52c817161883f50c441c3228cfe54d9f" } ] } ``` ## Get Withdrawable Amount {#get-_v2_coin_withdrawableAmount} ``` GET /v2/coin/withdrawableAmount ``` Get the available cryptocurrency withdrawal amount. **Required Permissions:** `readWithdrawals` ### Schema ```ts // URL query parameter type RequestQuery = { /** Account sequence number. Deposit/withdrawal APIs only operate on the main account, so only `1` is accepted. Defaults to 1. Example: 1 */ accountSeq?: number; /** Symbol of the cryptocurrency to query. If not provided, all assets will be queried. Example: "btc" */ currency?: string; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = Array<{ /** symbol of the asset. Example: "btc" */ currency: string; /** withdrawable amount. Example: "1.52" */ withdrawableAmount: string; /** amount in withdrawal. Example: "0.005" */ withdrawalInUseAmount: string; }>; ``` ### Example #### Request ```sh curl -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/coin/withdrawableAmount?currency=btc×tamp=TIMESTAMP&signature=SIGNATURE' ``` #### Response ```json { "success": true, "data": [ { "currency": "btc", "withdrawableAmount": "1.52", "withdrawalInUseAmount": "0.005" }, { "currency": "eth", "withdrawableAmount": "10.52", "withdrawalInUseAmount": "2.5" } ] } ``` ## Request Withdrawal {#post-_v2_coin_withdrawal} ``` POST /v2/coin/withdrawal ``` Request for cryptocurrency withdrawal. You need to register your withdrawal addresses for use with API in order to use this feature. **Required Permissions:** `writeWithdrawals` ### Schema ```ts // POST body — `Content-Type: application/x-www-form-urlencoded` type RequestBody = { /** Account sequence number. Deposit/withdrawal APIs only operate on the main account, so only `1` is accepted. Defaults to 1. Example: 1 */ accountSeq?: number; /** symbol of the asset. Example: "btc" */ currency: string; /** * symbol of the blockchain network. List of networks can be queried using the `/v2/currencies` API. * Uses the default network if omitted. Please always specify the network to prevent possible errors, as the default network can be changed. * Example: "BTC" */ network?: string; /** amount of cryptocurrency to withdraw (not including fees). Example: "0.02521236" */ amount: string; /** Recipient address. Withdrawals can only be made to addresses registered for API withdrawals. Example: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa" */ address: string; /** secondary address (destination tag, memo, etc. if none, omit or set to an empty string.) */ secondaryAddress?: string; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = { /** Withdrawal status */ status: | "pending" // Withdrawal request received. | "actionRequired" // Pending email confirmation (withdrawal can be canceled). To proceed with the withdrawal, you must verify the confirmation email. | "reviewing" // Pending withdrawal reviewing (withdrawal can be canceled). Withdrawal may be delayed according to Korbit's policy. | "processing" // Withdrawal processing. | "done" // Withdrawal done. | "canceled" // Withdrawal canceled. | "failed" // Withdrawal failed. (Insufficient balance or other error) ; /** withdrawal ID. Example: 1234 */ coinWithdrawalId: number; }; ``` ### Error Code - `INVALID_CURRENCY` — Invalid currency - `WITHDRAWAL_SUSPENDED` — Withdrawal suspended - `UNREGISTERED_WITHDRAWAL_ADDRESS` — The address hasn't been registered as an OpenAPI withdrawal address. - `FORBIDDEN_WITHDRAWAL_ADDRESS` — Withdrawals to the address is forbidden due to policy. - `WITHDRAWAL_ALREADY_IN_PROGRESS` — A withdrawal is already in progress. Please try again after the current transaction is completed. - `INVALID_USER_STATUS` — Your account is restricted. Please check your status or contact customer service. - `NO_BALANCE` — Your balance is insufficient. Note: If your balance is insufficient, a `NO_BALANCE` error may occur, or the withdrawal request may succeed but appear as `failed` when checking the withdrawal status. - `DAILY_LIMIT_EXCEEDED` — You have exceeded the daily withdrawal limit. ### Example #### Request ```sh curl -X POST -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/coin/withdrawal' -H Content-Type: application/x-www-form-urlencoded --data-raw address=1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa&amount=0.02521236¤cy=btc×tamp=TIMESTAMP&signature=SIGNATURE ``` #### Response ```json { "success": true, "data": { "status": "pending", "coinWithdrawalId": 1234 } } ``` ## Cancel Withdrawal {#delete-_v2_coin_withdrawal} ``` DELETE /v2/coin/withdrawal ``` Cancel a cryptocurrency withdrawal. Withdrawals can only be canceled if the status is one of the following: - `actionRequired` - `reviewing` **Required Permissions:** `writeWithdrawals` ### Schema ```ts // URL query parameter type RequestQuery = { /** Account sequence number. Deposit/withdrawal APIs only operate on the main account, so only `1` is accepted. Defaults to 1. Example: 1 */ accountSeq?: number; /** withdrawal ID (responsed by `POST /v2/coin/withdrawal`). Example: 1234 */ coinWithdrawalId: number; }; ``` ### Error Code - `WITHDRAWAL_ALREADY_FINISHED` — The withdrawal has already been finished - `CANNOT_CANCEL_WITHDRAWAL` — The withdrawal cannot be canceled (likely because it's being processed) - `NOT_FOUND` — The withdrawal cannot be found ### Example #### Request ```sh curl -X DELETE -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/coin/withdrawal?coinWithdrawalId=1234×tamp=TIMESTAMP&signature=SIGNATURE' ``` #### Response ```json { "success": true } ``` ## Get Recent Withdrawals {#get-_v2_coin_recentWithdrawals} ``` GET /v2/coin/recentWithdrawals ``` Get recent cryptocurrency withdrawal history. **Required Permissions:** `readWithdrawals` ### Schema ```ts // URL query parameter type RequestQuery = { /** Account sequence number. Deposit/withdrawal APIs only operate on the main account, so only `1` is accepted. Defaults to 1. Example: 1 */ accountSeq?: number; /** symbol of the asset. Example: "btc" */ currency: string; /** Maximum number of queries (Range: 1 to 100). Example: 100 */ limit?: number; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = Array<{ /** withdrawal ID. Example: 1234 */ id: number; /** withdrawn coin quantity excluding fees. Example: "1.234" */ quantity: string; /** withdrawal fee. Example: "0.0001" */ fee: string; /** symbol of the asset. Example: "btc" */ currency: string; /** Withdrawal status */ status: | "pending" // Withdrawal request received. | "actionRequired" // Pending email confirmation (withdrawal can be canceled). To proceed with the withdrawal, you must verify the confirmation email. | "reviewing" // Pending withdrawal reviewing (withdrawal can be canceled). Withdrawal may be delayed according to Korbit's policy. | "processing" // Withdrawal processing. | "done" // Withdrawal done. | "canceled" // Withdrawal canceled. | "failed" // Withdrawal failed. (Insufficient balance or other error) ; /** symbol of the blockchain network. Example: "ETH" */ network: string; /** withdrawal address. Example: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa" */ address: string; /** secondary address (destination tag, memo, etc. if none, then null.) */ secondaryAddress?: string | null; /** transaction hash on the blockchain. If not yet sent to the blockchain, `null` is returned. Example: "0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" */ transactionHash?: string | null; /** withdrawal request timestamp (ms). Example: 1700000000000 */ createdAt: number; }>; ``` ### Example #### Request ```sh curl -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/coin/recentWithdrawals?currency=btc&limit=100×tamp=TIMESTAMP&signature=SIGNATURE' ``` #### Response ```json { "success": true, "data": [ { "id": 1234, "quantity": "1.234", "fee": "0.0001", "currency": "btc", "status": "done", "network": "BTC", "address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", "secondaryAddress": null, "transactionHash": "0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", "createdAt": 1700000000000 } ] } ``` ## Get Withdrawal Status {#get-_v2_coin_withdrawal} ``` GET /v2/coin/withdrawal ``` Get the status of the requested withdrawal. **Required Permissions:** `readWithdrawals` ### Schema ```ts // URL query parameter type RequestQuery = { /** Account sequence number. Deposit/withdrawal APIs only operate on the main account, so only `1` is accepted. Defaults to 1. Example: 1 */ accountSeq?: number; /** symbol of the asset. Example: "btc" */ currency: string; /** withdrawal ID. Example: 1234 */ coinWithdrawalId: number; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = { /** withdrawal ID. Example: 1234 */ id: number; /** withdrawn coin quantity excluding fees. Example: "1.234" */ quantity: string; /** withdrawal fee. Example: "0.0001" */ fee: string; /** symbol of the asset. Example: "btc" */ currency: string; /** Withdrawal status */ status: | "pending" // Withdrawal request received. | "actionRequired" // Pending email confirmation (withdrawal can be canceled). To proceed with the withdrawal, you must verify the confirmation email. | "reviewing" // Pending withdrawal reviewing (withdrawal can be canceled). Withdrawal may be delayed according to Korbit's policy. | "processing" // Withdrawal processing. | "done" // Withdrawal done. | "canceled" // Withdrawal canceled. | "failed" // Withdrawal failed. (Insufficient balance or other error) ; /** symbol of the blockchain network. Example: "ETH" */ network: string; /** withdrawal address. Example: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa" */ address: string; /** secondary address (destination tag, memo, etc. if none, then null.) */ secondaryAddress?: string | null; /** transaction hash on the blockchain. If not yet sent to the blockchain, `null` is returned. Example: "0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" */ transactionHash?: string | null; /** withdrawal request timestamp (ms). Example: 1700000000000 */ createdAt: number; }; ``` ### Example #### Request ```sh curl -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/coin/withdrawal?coinWithdrawalId=1234¤cy=btc×tamp=TIMESTAMP&signature=SIGNATURE' ``` #### Response ```json { "success": true, "data": { "id": 1234, "quantity": "1.234", "fee": "0.0001", "currency": "btc", "status": "done", "network": "BTC", "address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", "secondaryAddress": null, "transactionHash": "0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", "createdAt": 1700000000000 } } ``` # REST · Deposit/Withdrawal (KRW) ## Request Deposit {#post-_v2_krw_sendKrwDepositPush} ``` POST /v2/krw/sendKrwDepositPush ``` Send a notification for KRW deposit requests to your Korbit mobile app. After receiving the notification, you must complete the verification process for the deposit to proceed. To receive notifications, ensure that push notifications are enabled in the app settings. **Required Permissions:** `writeDeposits` ### Schema ```ts // POST body — `Content-Type: application/x-www-form-urlencoded` type RequestBody = { /** Account sequence number. Deposit/withdrawal APIs only operate on the main account, so only `1` is accepted. Defaults to 1. Example: 1 */ accountSeq?: number; /** Amount of KRW to deposit. Example: "50000" */ amount: string; }; ``` ### Example #### Request ```sh curl -X POST -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/krw/sendKrwDepositPush' -H Content-Type: application/x-www-form-urlencoded --data-raw 'amount=50000×tamp=TIMESTAMP&signature=SIGNATURE' ``` #### Response ```json {"success":true} ``` ## Request Withdrawal {#post-_v2_krw_sendKrwWithdrawalPush} ``` POST /v2/krw/sendKrwWithdrawalPush ``` Send a notification for KRW withdrawal requests to your Korbit mobile app. After receiving the notification, you must complete the verification process for the withdrawal to proceed. To receive notifications, ensure that push notifications are enabled in the app settings. **Required Permissions:** `writeWithdrawals` ### Schema ```ts // POST body — `Content-Type: application/x-www-form-urlencoded` type RequestBody = { /** Account sequence number. Deposit/withdrawal APIs only operate on the main account, so only `1` is accepted. Defaults to 1. Example: 1 */ accountSeq?: number; /** Amount of KRW to withdraw. Example: "50000" */ amount: string; }; ``` ### Example #### Request ```sh curl -X POST -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/krw/sendKrwWithdrawalPush' -H Content-Type: application/x-www-form-urlencoded --data-raw 'amount=50000×tamp=TIMESTAMP&signature=SIGNATURE' ``` #### Response ```json {"success":true} ``` ## Get Recent Deposits {#get-_v2_krw_recentDeposits} ``` GET /v2/krw/recentDeposits ``` Get recent KRW deposit history. **Required Permissions:** `readDeposits` ### Schema ```ts // URL query parameter type RequestQuery = { /** Account sequence number. Deposit/withdrawal APIs only operate on the main account, so only `1` is accepted. Defaults to 1. Example: 1 */ accountSeq?: number; /** Maximum number of queries (range: 1 to 100). Example: 100 */ limit?: number; /** Retrieve all transaction history. */ includeAll?: | "false" // Only regular KRW deposits (default) | "true" // Includes additional items such as deposit fees, event rewards, etc. ; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = Array<{ /** KRW deposit ID. Example: 1234 */ id: number; /** Deposit type (when includeAll=true) */ type?: | "general" // Regular KRW deposit | "depositInterest" // Deposit fee | "makerIncentive" // Maker incentive | "reward" // Event reward | "etc" // Other ; /** KRW deposit status */ status: | "pending" // Deposit request received. | "processing" // Processing deposit. | "reviewing" // Reviewing deposit. | "done" // Deposit done. | "canceling" // Deposit cancel requested. | "canceled" // Deposit canceled. | "failed" // Deposit failed. ; /** deposit quantity. Example: "1.234" */ quantity: string; /** deposit timestamp (ms). Example: 1700000000000 */ createdAt: number; }>; ``` ### Example #### Request ```sh curl -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/krw/recentDeposits?limit=100×tamp=TIMESTAMP&signature=SIGNATURE' ``` #### Response ```json { "success": true, "data": [ { "id": 1234, "status": "done", "quantity": "50000", "createdAt": 1700000000000 } ] } ``` ## Get Recent Withdrawals {#get-_v2_krw_recentWithdrawals} ``` GET /v2/krw/recentWithdrawals ``` Get recent KRW withdrawal history. **Required Permissions:** `readWithdrawals` ### Schema ```ts // URL query parameter type RequestQuery = { /** Account sequence number. Deposit/withdrawal APIs only operate on the main account, so only `1` is accepted. Defaults to 1. Example: 1 */ accountSeq?: number; /** Maximum number of queries (Range: 1 to 100). Example: 100 */ limit?: number; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = Array<{ /** KRW withdrawal ID. Example: 1234 */ id: number; /** Withdrawn KRW quantity excluding fees. Example: "50000" */ quantity: string; /** withdrawal fee. Example: "1000" */ fee: string; /** KRW withdrawal status */ status: | "pending" // Withdrawal request received. | "reviewing" // Pending withdrawal reviewing. Withdrawal may be delayed according to Korbit's policy. | "processing" // Processing withdrawal. | "done" // Withdrawal done. | "failed" // Withdrawal failed. | "canceled" // Withdrawal canceled. ; /** withdrawal request timestamp (ms). Example: 1700000000000 */ createdAt: number; }>; ``` ### Example #### Request ```sh curl -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/krw/recentWithdrawals?limit=100×tamp=TIMESTAMP&signature=SIGNATURE' ``` #### Response ```json { "success": true, "data": [ { "id": 1234, "quantity": "50000", "fee": "1000", "status": "done", "createdAt": 1700000000000 } ] } ``` # REST · Other Endpoints ## Get Crypto Info {#get-_v2_currencies} ``` GET /v2/currencies ``` Get cryptocurrencies information. ### Schema ```ts // No request parameters // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = Array<{ /** currency symbol. Example: "btc" */ name: string; /** currency name. Example: "Bitcoin" */ fullName: string; /** (deprecated) withdrawal status. Please refer to `withdrawalStatus` under the `networkList` field. */ withdrawalStatus?: string; /** (deprecated) deposit status. Please refer to `depositStatus` under the `networkList` field. */ depositStatus?: string; /** (deprecated) number of confirmations required for deposits. Please refer to `confirmationCount` under the `networkList` field. */ confirmationCount?: string; /** (deprecated) withdrawal fees. Please refer to `withdrawalTxFee` under the `networkList` field. */ withdrawalTxFee?: string; /** minimum withdrawal amount. Please refer to `withdrawalMinAmount` under the `networkList` field. */ withdrawalMinAmount?: string; /** max withdrawal amount per each request. Example: "10" */ withdrawalMaxAmountPerRequest: string; /** symbol for the default blockchain network. Example: "BTC" */ defaultNetwork?: string; /** list of supported blockchain networks (not present for fiat currencies) */ networkList?: Array<{ /** network symbol. Example: "ETH" */ name: string; /** network name. Example: "Ethereum" */ fullName: string; /** possible to withdraw: */ withdrawalStatus: | "launched" // yes | "stopped" // no ; /** possible to deposit: */ depositStatus: | "launched" // yes | "stopped" // no ; /** number of confirmations required for deposits. Example: 3 */ confirmationCount: number; /** withdrawal fees. Example: "0.0001" */ withdrawalTxFee: string; /** minimum withdrawal amount. Example: "0.00000001" */ withdrawalMinAmount: string; /** decimal places for withdrawal quantity. Example: 8 */ withdrawalPrecision: number; /** Whether the network has a secondary address */ hasSecondaryAddr: boolean; /** contract address. Example: "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2" */ contractAddress?: string; /** blockchain explorer address. Example: "https://etherscan.io/address/" */ addressExplorerUrl?: string; }>; }>; ``` ### Example #### Request ```sh curl 'https://api.korbit.co.kr/v2/currencies' ``` #### Response ```json { "success": true, "data": [ { "name": "krw", "fullName": "Won", "withdrawalMaxAmountPerRequest": "5000000000", "depositStatus": "launched", "withdrawalStatus": "launched", "withdrawalTxFee": "1000", "withdrawalMinAmount": "1000" }, { "name": "btc", "fullName": "Bitcoin", "withdrawalMaxAmountPerRequest": "120", "defaultNetwork": "BTC", "networkList": [ { "name": "BTC", "fullName": "Bitcoin", "depositStatus": "launched", "withdrawalStatus": "launched", "confirmationCount": 3, "withdrawalTxFee": "0.0008", "withdrawalMinAmount": "0.0001", "withdrawalPrecision": 8, "hasSecondaryAddr": false, "addressExplorerUrl": "https://www.blockchain.com/ko/btc/address/" } ], "depositStatus": "launched", "withdrawalStatus": "launched", "confirmationCount": "3", "withdrawalTxFee": "0.0008", "withdrawalMinAmount": "0.0001" }, { "name": "eth", "fullName": "Ethereum", "withdrawalMaxAmountPerRequest": "2000", "defaultNetwork": "ETH", "networkList": [ { "name": "ETH", "fullName": "Ethereum", "depositStatus": "launched", "withdrawalStatus": "launched", "confirmationCount": 45, "withdrawalTxFee": "0.005", "withdrawalMinAmount": "0.0001", "withdrawalPrecision": 8, "hasSecondaryAddr": false, "addressExplorerUrl": "https://etherscan.io/address/" }, { "name": "BASE", "fullName": "BASE", "depositStatus": "launched", "withdrawalStatus": "launched", "confirmationCount": 1, "withdrawalTxFee": "0.001", "withdrawalMinAmount": "0.0001", "withdrawalPrecision": 8, "hasSecondaryAddr": false, "addressExplorerUrl": "https://basescan.org/address/" } ], "depositStatus": "launched", "withdrawalStatus": "launched", "confirmationCount": "45", "withdrawalTxFee": "0.005", "withdrawalMinAmount": "0.0001" }, { "name": "usdt", "fullName": "Tether", "withdrawalMaxAmountPerRequest": "500000", "defaultNetwork": "TRX", "networkList": [ { "name": "TRX", "fullName": "Tron", "depositStatus": "launched", "withdrawalStatus": "launched", "confirmationCount": 1, "withdrawalTxFee": "1", "withdrawalMinAmount": "1", "withdrawalPrecision": 6, "hasSecondaryAddr": false, "addressExplorerUrl": "https://tronscan.org/#/address/" } ], "depositStatus": "launched", "withdrawalStatus": "launched", "confirmationCount": "1", "withdrawalTxFee": "1", "withdrawalMinAmount": "1" } ] } ``` ## Get Server Time {#get-_v2_time} ``` GET /v2/time ``` Get the current server time. ### Schema ```ts // No request parameters // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = { /** timestamp. Example: 1700000000000 */ time: number; }; ``` ### Example #### Request ```sh curl 'https://api.korbit.co.kr/v2/time' ``` #### Response ```json { "success": true, "data": { "time": 1700000000000 } } ``` ## Get Trading Fee Rates {#get-_v2_tradingFeePolicy} ``` GET /v2/tradingFeePolicy ``` Get the trading fee rates applied to your account. **Required Permissions:** `readOrders` ### Schema ```ts // URL query parameter type RequestQuery = { /** Account sequence number. Defaults to 1 (main account). Example: 1 */ accountSeq?: number; /** Enter the symbols of the trading pairs to query. To input multiple trading pairs, separate them with commas(,). If omitted, information for all available trading pairs on Korbit will be returned. Example: "btc_krw,eth_krw" */ symbol?: string; }; // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = Array<{ /** Trading pair symbol. Example: "btc_krw" */ symbol: string; /** Fee currency for buy orders. Example: "btc" */ buyFeeCurrency: string; /** Fee currency for sell orders. Example: "krw" */ sellFeeCurrency: string; /** Maximum fee rate. For buy orders on a pair whose `buyFeeCurrency` is the pair's `quoteCurrency`, an additional `quantity*price*maxFeeRate` of the quote currency will be required (converted to the amount in use). Once the order is executed, it will be settled according to the fee rate at the time of execution. Example: "0.0015" */ maxFeeRate: string; /** Taker fee rate. Example: "0.0015" */ takerFeeRate: string; /** Maker fee rate. Example: "0" */ makerFeeRate: string; }>; ``` ### Example #### Request ```sh curl -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/tradingFeePolicy?timestamp=TIMESTAMP&signature=SIGNATURE' ``` #### Response ```json { "success": true, "data": [ { "symbol": "btc_krw", "buyFeeCurrency": "btc", "sellFeeCurrency": "krw", "maxFeeRate": "0.002", "takerFeeRate": "0.0015", "makerFeeRate": "0" }, { "symbol": "eth_krw", "buyFeeCurrency": "eth", "sellFeeCurrency": "krw", "maxFeeRate": "0.002", "takerFeeRate": "0.0015", "makerFeeRate": "0" }, { "symbol": "etc_krw", "buyFeeCurrency": "krw", "sellFeeCurrency": "krw", "maxFeeRate": "0.002", "takerFeeRate": "0.0015", "makerFeeRate": "0" }, { "symbol": "xrp_krw", "buyFeeCurrency": "krw", "sellFeeCurrency": "krw", "maxFeeRate": "0.002", "takerFeeRate": "0.0015", "makerFeeRate": "0" } ] } ``` ## Get API Key Info {#get-_v2_currentKeyInfo} ``` GET /v2/currentKeyInfo ``` Get current API Key's information. **Required Permissions:** None ### Schema ```ts // No request parameters // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = { /** API Key ID. Example: "FFSoRME97Sr7WBCMZJ_NO5Bj8MZ03EyArRzqyr1NKIA" */ apiKey: string; /** UUID of the user who owns this API key. Example: "f81d4fae-7dec-11d0-a765-00a0c91e6bf6" */ userUuid?: string; /** Key Type */ type: "hmac-sha256" | "ed25519"; /** ED25519 public key. (only for `ED25519` type.). Example: -----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAk+Yp3C31eFwoky+zyRNB6rAv/lgULTeghxTQpqwQHzM= -----END PUBLIC KEY----- */ publicKey?: string; permissions: Array<"readBalances" | "readOrders" | "writeOrders" | "readDeposits" | "writeDeposits" | "readWithdrawals" | "writeWithdrawals">; /** List of IP addresses the API key can connect from. Multiple addresses are separated by commas (`,`). Example: "1.2.3.4,5.6.7.8" */ whitelist: string; /** API key expiration timestamp (scheduled). Example: 1700000000000 */ expiration: number; /** status of the key */ status: "activated" | "deactivated"; /** label (custom name). Example: "test key" */ label?: string; /** List of account sequence numbers this API key is allowed to access. */ allowedAccountSeqs: number[]; /** API key creation timestamp. Example: 1700000000000 */ createdAt: number; }; ``` ### Example #### Request ```sh curl -H X-KAPI-KEY=APIKEY 'https://api.korbit.co.kr/v2/currentKeyInfo?timestamp=TIMESTAMP&signature=SIGNATURE' ``` #### Response ```json { "success": true, "data": { "apiKey": "FFSoRME97Sr7WBCMZJ_NO5Bj8MZ03EyArRzqyr1NKIA", "userUuid": "f81d4fae-7dec-11d0-a765-00a0c91e6bf6", "type": "ed25519", "publicKey": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAk+Yp3C31eFwoky+zyRNB6rAv/lgULTeghxTQpqwQHzM=\n-----END PUBLIC KEY-----\n", "permissions": ["readBalances", "readOrders"], "whitelist": "1.2.3.4,5.6.7.8", "allowedAccountSeqs": [1, 2], "expiration": 1700000000000, "status": "activated", "label": "test key", "createdAt": 1700000000000 } } ``` ## Get Notices {#get-_v2_notices} ``` GET /v2/notices ``` Get the 20 most recent Korbit notices (announcements), most recent first. Includes both general Korbit notices and Open API (developer center) notices. ### Schema ```ts // No request parameters // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = Array<{ /** Notice title. Example: "공지사항 제목 샘플" */ title: string; /** Notice creation timestamp (ms). Example: 1700000000000 */ createdAt: number; /** Notice last-updated timestamp (ms). May be absent for some notices. Example: 1700000000000 */ updatedAt?: number; /** URL to the notice detail page. Example: "https://www.korbit.co.kr/notice/detail/?noticeId=4Oy9q6ALiM7jABzMt32ul5" */ url: string; }>; ``` ### Example #### Request ```sh curl 'https://api.korbit.co.kr/v2/notices' ``` #### Response ```json { "success": true, "data": [ { "title": "공지사항 제목 샘플", "createdAt": 1700000000000, "updatedAt": 1700000000000, "url": "https://www.korbit.co.kr/notice/detail/?noticeId=4Oy9q6ALiM7jABzMt32ul5" } ] } ``` ## Get Market Alerts {#get-_v2_marketAlerts} ``` GET /v2/marketAlerts ``` Get the current market alert (Market Warning System, 시장경보제) status for each trading pair. Returns only pairs that currently have active alerts. ### Schema ```ts // No request parameters // JSON response (the `data` field of the `{ success: true, data }` envelope) type Response = Array<{ /** Trading pair symbol. Example: "btc_krw" */ symbol: string; /** Active market alerts for this pair */ alerts: Array<{ /** Alert type. Example: "price" */ type: string; /** Alert type (Korean label). Example: "가격 급등락" */ typeKorean?: string; /** Alert level (present only for some alert types). Example: "danger" */ level?: string; /** Alert level (Korean label). Example: "투자위험" */ levelKorean?: string; /** Alert start timestamp (ms). Example: 1700000000000 */ startAt?: number; /** Alert end timestamp (ms). Example: 1700000000000 */ endAt?: number; /** Alert threshold or measured value (decimal string; meaning depends on `type`). Example: "250.5" */ value?: string; }>; }>; ``` ### Example #### Request ```sh curl 'https://api.korbit.co.kr/v2/marketAlerts' ``` #### Response ```json { "success": true, "data": [ { "symbol": "btc_krw", "alerts": [ { "type": "price", "typeKorean": "가격 급등락", "level": "danger", "levelKorean": "투자위험", "startAt": 1700000000000, "endAt": 1700000000000, "value": "250.5" }, { "type": "limit_buy_range", "typeKorean": "주문가격 제한", "startAt": 1700000000000, "endAt": 1700000000000, "value": "145000000" } ] } ] } ``` # WebSocket · Public Type ## Ticker {#method-subscribe_type-ticker} Streams latest pricing information for a symbol. ### Schema ```ts // JSON message sent over WebSocket type RequestMessage = { /** Set to `subscribe` or `unsubscribe` */ method: "subscribe"; /** Set to `ticker` */ type: "ticker"; /** Enter the symbols of the trading pairs you want to query. */ symbols: string[]; }; // JSON message received over WebSocket type Response = { /** Fixed value `ticker` */ type: "ticker"; /** Server time (unix timestamp, in milliseconds). Example: 1700000000000 */ timestamp: number; /** Trading pair symbol. Example: "btc_krw" */ symbol: string; /** * Whether the data is a snapshot or a real-time data. * `true` - The data is a latest snapshot and not a real-time data. Sent as the first message on subscription. * `false` or `null` - The data is from a real-time trade. */ snapshot?: boolean | null; data: { /** Open price (24H). Example: "361922.23" */ open: string; /** High price (24H). Example: "361922.23" */ high: string; /** Low price (24H). Example: "361922.23" */ low: string; /** Last price (24H). Example: "361922.23" */ close: string; /** Previous close price (24H). Example: "261922.23" */ prevClose: string; /** changed price. `close - prevClose`. Example: "100000" */ priceChange: string; /** changed price percent. `100 * (close - prevClose) / prevClose`. Example: "38.18" */ priceChangePercent: string; /** Trading volume (24H), in the base currency (`symbol`'s first segment). Example: "100" */ volume: string; /** Trading volume (24H), in the quote currency (`symbol`'s second segment). Example: "1000000000" */ quoteVolume: string; /** Best bid price. Example: "5000" */ bestBidPrice: string; /** Best ask price. Example: "6000" */ bestAskPrice: string; /** Last traded timestamp (unix timestamp, in milliseconds). Example: 1700000000000 */ lastTradedAt: number; }; }; ``` ### Example #### Request ```json [{"method":"subscribe","type":"ticker","symbols":["btc_krw","eth_krw"]}] ``` #### Response ```json { "type": "ticker", "timestamp": 1700000027754, "symbol": "btc_krw", "snapshot": true, "data": { "open": "94679000", "high": "111162000", "low": "93861000", "close": "99027000", "prevClose": "94679000", "priceChange": "4348000", "priceChangePercent": "4.59", "volume": "147.94385655", "quoteVolume": "14311735005.18033", "bestAskPrice": "99027000", "bestBidPrice": "99026000", "lastTradedAt": 1700000010022 } } ``` ## Orderbook {#method-subscribe_type-orderbook} Streams orderbook data for a symbol. Up to 30 prices are available for each side. ### Schema ```ts // JSON message sent over WebSocket type RequestMessage = { /** Set to `subscribe` or `unsubscribe` */ method: "subscribe"; /** Set to `orderbook` */ type: "orderbook"; /** Enter the symbols of the trading pairs you want to query. */ symbols: string[]; /** Orderbook grouping level. Available levels can be checked via the Get Tick Size Policy API. If not provided, grouping will not be applied. Example: "1000" */ level?: string; }; // JSON message received over WebSocket type Response = { /** Fixed value `orderbook` */ type: "orderbook"; /** Server time (unix timestamp, in milliseconds). Example: 1700000000000 */ timestamp: number; /** Trading pair symbol. Example: "btc_krw" */ symbol: string; /** * Whether the data is a snapshot or a real-time data. * `true` - The data is a latest snapshot and not a real-time data. Sent as the first message on subscription. * `false` or `null` - The data is from a real-time trade. */ snapshot?: boolean | null; data: { /** Timestamp of the orderbook data (unix timestamp, in milliseconds). Example: 1700000000000 */ timestamp: number; /** bids */ bids: Array<{ /** price. Example: "250000" */ price: string; /** quantity. Example: "10" */ qty: string; /** Total amount in the quote currency (only set when orderbook grouping is used. When not using grouping, it can be calculated as `price * qty`). Example: "2500000" */ amt?: string; }>; /** asks */ asks: Array<{ /** price. Example: "250000" */ price: string; /** quantity. Example: "10" */ qty: string; /** Total amount in the quote currency (only set when orderbook grouping is used. When not using grouping, it can be calculated as `price * qty`). Example: "2500000" */ amt?: string; }>; }; }; ``` ### Example #### Request ```json [{"method":"subscribe","type":"orderbook","symbols":["btc_krw"]}] ``` #### Response ```json { "type": "orderbook", "timestamp": 1700000006177, "symbol": "btc_krw", "snapshot": true, "data": { "timestamp": 1700000000234, "asks": [ { "price": "99131000", "qty": "0.00456677" }, { "price": "99132000", "qty": "0.00616665" }, { "price": "99133000", "qty": "0.00808569" } ], "bids": [ { "price": "99120000", "qty": "0.00363422" }, { "price": "99119000", "qty": "0.00475577" }, { "price": "99118000", "qty": "0.00389054" } ] } } ``` ## Trade {#method-subscribe_type-trade} Streams real-time trades. The subscription snapshot carries only the latest trade(s), not full trade history — use REST GET /v2/trades for recent history. ### Schema ```ts // JSON message sent over WebSocket type RequestMessage = { /** Set to `subscribe` or `unsubscribe` */ method: "subscribe"; /** Set to `trade` */ type: "trade"; /** Enter the symbols of the trading pairs you want to query. */ symbols: string[]; }; // JSON message received over WebSocket type Response = { /** Fixed value `trade` */ type: "trade"; /** Server time (unix timestamp, in milliseconds). Example: 1700000000000 */ timestamp: number; /** Trading pair symbol. Example: "btc_krw" */ symbol: string; /** * Whether the data is a snapshot or a real-time data. * `true` - The data is a latest snapshot and not a real-time data. Sent as the first message on subscription. * `false` or `null` - The data is from a real-time trade. */ snapshot?: boolean | null; data: Array<{ /** Time of the trade (unix timestamp, in milliseconds). Example: 1700000000000 */ timestamp: number; /** trade price. Example: "250000" */ price: string; /** trade quantity. Example: "10" */ qty: string; /** whether the taker is the buyer. Example: true */ isBuyerTaker: boolean; /** trade ID (unique per trading pair). Monotonically increasing per trading pair, but not guaranteed to be contiguous. After a reconnect the stream may redeliver already-seen trades in order — de-duplicate by tradeId. Example: 1234 */ tradeId: number; }>; }; ``` ### Example #### Request ```json [{"method":"subscribe","type":"trade","symbols":["btc_krw"]}] ``` #### Response ```json { "symbol": "btc_krw", "timestamp": 1700000005498, "type": "trade", "snapshot": true, "data": [ { "timestamp": 1700000001239, "price": "98909000", "qty": "0.00146702", "isBuyerTaker": true, "tradeId": 123456 } ] } ``` # WebSocket · Private Type ## My Order {#method-subscribe_type-myOrder} Streams the changes in my orders. **Required Permissions:** `readOrders` ### Schema ```ts // JSON message sent over WebSocket type RequestMessage = { /** Set to `subscribe` or `unsubscribe` */ method: "subscribe"; /** Set to `myOrder` */ type: "myOrder"; /** * List of account sequence numbers to subscribe to. Defaults to `[1]` (main account) when omitted. * Subscribing to an account sequence your API key is not allowed to access will cause the request to fail. */ accountSeqs?: number[]; /** Enter the symbols of the trading pairs you want to query. */ symbols: string[]; }; // JSON message received over WebSocket type Response = { /** Fixed value `myOrder` */ channelType: "myOrder"; /** Server time (unix timestamp, in milliseconds). Example: 1700000000000 */ timestamp: number; /** Trading pair symbol. Example: "btc_krw" */ symbol: string; order: { /** * The account sequence number this update belongs to. `1` is the main account. * Omitted when the subscription request did not explicitly include the `accountSeqs` parameter. * Example: 1 */ accountSeq?: number; orders: Array<{ /** order ID. Example: 1234 */ orderId: number; /** Order status */ status: | "pending" // Order pending. If the balance is insufficient, the order may fail and change to the `expired` status. | "unfilled" // Fully unfilled (Same as the `open` status from REST API) | "filled" // Execution closed. An order whose unfilled remainder is returned instead of resting on the book (e.g. an `ioc` order, or a price-protected (`pp`) order trimmed by the protection range) also closes as `filled` even when less than the requested quantity executed. Confirm the executed amount with `filledQty`/`filledAmt`. | "canceled" // Fully canceled | "partiallyFilled" // Partially filled | "partiallyFilledCanceled" // Partially filled and remaining amount canceled | "expired" // Order submission failed ; side: "buy" | "sell"; orderType: | "limit" // limit order | "market" // market order | "best" // best bid/offer ; /** Time in Force strategies. */ timeInForce?: | "gtc" // Good-Till-Canceled. The order will remain valid until terminated (fully executed or canceled) | "ioc" // Immediate-Or-Cancel. The order will be filled immediately, if can not then will be canceled. (Taker-Only) | "fok" // Fill-Or-Kill. The order will be filled fully, if can not then will be canceled. (Taker-Only) | "po" // Post-Only. If the order would be filled immediately, then will be canceled. (Maker-Only) ; /** Order price (limit order only. no price for market order.). Example: "5000" */ price?: string; /** Order quantity in the base currency (limit/BBO order or sell-side market order only. For BBO orders it's set after the quantity is determined). Example: "10" */ qty?: string; /** Filled quantity in the base currency. Example: "10" */ filledQty: string; /** Purchase amount in the quote currency (buy-side market/BBO order only). Example: "50000" */ amt?: string; /** Filled amount in the quote currency. Example: "50000" */ filledAmt: string; /** Average execution price. Example: "5000" */ avgPrice?: string; /** Order timestamp (ms). Example: 1700000000000 */ createdAt: number; /** Last execution timestamp (ms). Example: 1700000000000 */ lastFilledAt?: number; /** `clientOrderId` submitted from the user by POST /v2/orders. Example: "20141231-155959-abcdef" */ clientOrderId?: string; }>; }; }; ``` ### Example #### Request ```json [{"method":"subscribe","type":"myOrder","symbols":["btc_krw"],"accountSeqs":[1]}] ``` #### Response ```json { "symbol": "btc_krw", "timestamp": 1700000000000, "channelType": "myOrder", "order": { "accountSeq": 1, "orders": [ { "orderId": 123456, "status": "partiallyFilled", "side": "buy", "orderType": "limit", "timeInForce": "gtc", "price": "99017000", "qty": "0.9", "filledQty": "0.53793136", "amt": "89115300", "filledAmt": "53260583.9536", "avgPrice": "99010000", "createdAt": 1700000001000, "lastFilledAt": 1700000002000, "clientOrderId": "tjcfiDfSjq94giNAat31" } ] } } ``` ## My Trade {#method-subscribe_type-myTrade} Streams trades on my orders. **Required Permissions:** `readOrders` ### Schema ```ts // JSON message sent over WebSocket type RequestMessage = { /** Set to `subscribe` or `unsubscribe` */ method: "subscribe"; /** Set to `myTrade` */ type: "myTrade"; /** * List of account sequence numbers to subscribe to. Defaults to `[1]` (main account) when omitted. * Subscribing to an account sequence your API key is not allowed to access will cause the request to fail. */ accountSeqs?: number[]; /** Enter the symbols of the trading pairs you want to query. */ symbols: string[]; }; // JSON message received over WebSocket type Response = { /** Fixed value `myTrade` */ channelType: "myTrade"; /** Server time (unix timestamp, in milliseconds). Example: 1700000000000 */ timestamp: number; /** Trading pair symbol. Example: "btc_krw" */ symbol: string; trade: { /** * The account sequence number this update belongs to. `1` is the main account. * Omitted when the subscription request did not explicitly include the `accountSeqs` parameter. * Example: 1 */ accountSeq?: number; trades: Array<{ /** trade ID (unique per trading pair). Monotonically increasing per trading pair, but not guaranteed to be contiguous. After a reconnect the stream may redeliver already-seen trades in order — de-duplicate by tradeId. Example: 1234 */ tradeId: number; /** order ID. Example: 1234 */ orderId: number; side: "buy" | "sell"; /** price. Example: "5000" */ price: string; /** quantity. Example: "10" */ qty: string; /** fee quantity. Example: "10" */ fee: string; /** fee currency. Example: "krw" */ feeCurrency: string; /** time of the trade. Example: 1700000000000 */ filledAt: number; /** taker=true, maker=false. Example: true */ isTaker: boolean; }>; }; }; ``` ### Example #### Request ```json [{"method":"subscribe","type":"myTrade","symbols":["btc_krw"],"accountSeqs":[1]}] ``` #### Response ```json { "symbol": "btc_krw", "timestamp": 1700000001000, "channelType": "myTrade", "trade": { "accountSeq": 1, "trades": [ { "tradeId": 123456, "orderId": 456789, "side": "buy", "price": "99051000", "qty": "0.0013", "fee": "50", "feeCurrency": "krw", "filledAt": 1700000000000, "isTaker": true } ] } } ``` ## My Asset {#method-subscribe_type-myAsset} Streams changes to my balances in real time. **Required Permissions:** `readBalances` ### Schema ```ts // JSON message sent over WebSocket type RequestMessage = { /** Set to `subscribe` or `unsubscribe` */ method: "subscribe"; /** Set to `myAsset` */ type: "myAsset"; /** * List of account sequence numbers to subscribe to. Defaults to `[1]` (main account) when omitted. * Subscribing to an account sequence your API key is not allowed to access will cause the request to fail. */ accountSeqs?: number[]; }; // JSON message received over WebSocket type Response = { /** Fixed value `myAsset` */ channelType: "myAsset"; /** Server time (unix timestamp, in milliseconds). Example: 1700000000000 */ timestamp: number; asset: { /** * The account sequence number this update belongs to. `1` is the main account. * Omitted when the subscription request did not explicitly include the `accountSeqs` parameter. * Example: 1 */ accountSeq?: number; assets: Array<{ /** asset name. Example: "krw" */ currency: string; /** balance. `available + tradeInUse + withdrawalInUse`. Example: "100" */ balance: string; /** available quantity. Example: "70" */ available: string; /** quantity in trade. Example: "20" */ tradeInUse: string; /** quantity in withdrawal. Example: "10" */ withdrawalInUse: string; /** average purchase price. Example: "5000" */ avgPrice: string; /** time of the last change. Example: 1700000000000 */ updatedAt: number; }>; }; }; ``` ### Example #### Request ```json [{"method":"subscribe","type":"myAsset","accountSeqs":[1]}] ``` #### Response ```json { "timestamp": 1700000001000, "channelType": "myAsset", "asset": { "accountSeq": 1, "assets": [ { "currency": "btc", "balance": "10", "available": "7", "tradeInUse": "2", "withdrawalInUse": "1", "avgPrice": "50000", "updatedAt": 1700000000000 }, { "currency": "eth", "balance": "100", "available": "70", "tradeInUse": "20", "withdrawalInUse": "10", "avgPrice": "5000", "updatedAt": 1700000000000 } ] } } ``` # Korbit AI Guide — Terms of Use and Limitation of Liability This "Korbit AI Guide" (hereinafter, the "Document") is a technical reference in which Digital X Co., Ltd. (hereinafter, the "Company") consolidates the specifications and operational conventions of the Korbit Open API in a form that can be parsed and interpreted by developer tools and AI-based coding assistant software. This Document is provided as reference material only and does not, under any circumstances, constitute investment advice, solicitation of transactions, or financial, legal, tax, or accounting advice. The Company does not, through this Document, recommend or solicit any particular asset, strategy, or trading method. 1. This Document is provided "AS-IS." The Company makes no express or implied warranty of any kind — including as to fitness for a particular purpose, commercial usefulness, absence of errors, ongoing currency, or non-infringement — with respect to the descriptions, examples, values, or terminology contained herein. The contents of this Document reflect the Company's description of the API specification as of a given point in time and do not represent the Company's policy positions or future direction. 2. The operation of any code, script, automated bot, AI agent, or similar artifact (collectively, "User Implementations") developed or executed with reference to this Document, and all consequences derived therefrom — including but not limited to order placement and execution, movement of Korean won and virtual assets, gains and losses, fees, tax implications, actions taken against user accounts, and related disputes — shall rest solely with the user. The user makes independent judgments regarding the design, testing, and operation of any User Implementation and assumes all risks arising therefrom. 3. This Document is intended to be referenced by AI-based development tools, including large language models, in producing code; however, the logical completeness, safety, and legal compliance of such output are not guaranteed. Generated code may contain misinterpretations of the specification, hallucinations, omissions, defective delayed handling, or failure to reflect API changes. Before applying such output to a production environment, the user must sequentially perform pre-verification in an isolated test environment, direct human review of the code, and staged testing in a real account starting from reduced scale and gradually expanding. It is not recommended to rely on the responses or automated output of AI tools as the sole or a principal basis for investment judgment. 4. Trading in virtual assets involves substantial market risk, including the possibility of the total loss of principal. The example code, strategies, and figures provided in this Document are for illustrative purposes only and imply no profitability or safety. Unforeseeable losses may occur due to market volatility, network latency, exchange events, defects in User Implementations, and similar factors. 5. The quality, behavior, and results of third-party software combined with or used to operate this Document — including conversational AI tools, agent frameworks, developer extensions (plug-ins), and external data sources — are governed by the terms of use of the relevant third parties and the user, and the Company has no involvement therein. In particular, the Company assumes no responsibility whatsoever for incidents arising from combination with untrusted external input or unvetted tools, such as instruction injection, context poisoning, or exposure of credentials. 6. Credentials used to access the Korbit Open API — including API keys, secret or private keys, and signing materials — are issued, stored, and revoked by the user, and the user bears the responsibility of managing their storage, transmission, and use. Losses arising from disclosure, misentry, granting of permissions broader than necessary, misconfiguration of allowed access ranges, or exposure through source repositories or logs shall be borne by the user. 7. Care must be taken when configuring an environment in which a process not supervised by the user — e.g., an AI agent, automated bot, or script — is capable of transferring assets externally. Such configurations may include granting withdrawal-related permissions to an API key, pre-registering external withdrawal addresses, or constructing an automated approval flow that lacks after-the-fact confirmation. The Company does not recommend such configurations, and with respect to asset transfers carried out by a User Implementation within the scope of permissions set by the user, and the consequences thereof, the Company assumes no obligation to reverse the transfer or process any objection. 8. In utilizing this Document, the user shall comply with applicable laws and regulations, the Korbit Terms of Service, the Open API Terms of Service, and the access policies, rate limits, technical guidelines, and other terms of use established by the Korbit Developer Center. Uses beyond such terms — for example, attempts to circumvent rate limits, access through unauthorized accounts, or manipulation of asset types that are not permitted — are undertaken at the user's responsibility, and any resulting account actions and losses shall be borne by the user. 9. To the maximum extent permitted by applicable law, the Company assumes no legal liability whatsoever for direct, indirect, incidental, special, or consequential damages — including loss of profits, loss of data, business interruption, or damage to reputation — that the user or any third party may incur arising from the use of this Document, the operation of any User Implementation, or the behavior of third-party software combined with this Document. 10. The Company may, at its sole discretion and without prior notice, modify or discontinue the content, structure, expression, API endpoints, parameters, response formats, policies, and distribution and maintenance of accompanying software described in this Document. Even where such changes cause a User Implementation to malfunction or behave differently from the user's expectations, the Company assumes no responsibility for such outcomes. 11. Copyright and other intellectual property rights in this Document and its accompanying resources (including example code, interface descriptions, and terminology definitions) belong to the Company. Users may reference and utilize this Document for the purpose of their own integration with the Korbit Open API. Without separate written consent, this Document may not be commercially distributed, resold, incorporated into paid services, or extensively adapted into derivative works. By accessing or using this Document, or by developing or executing any User Implementation based on it, the user is deemed to have understood and agreed to each of the foregoing provisions.