Korea Investment Securities API: A Beginner's Guide to Your First Integration
Search for how to build a stock trading bot in Korea and the first thing you run into is the Korea Investment Securities API (KIS OpenAPI). Then you try to actually start, and you stall on the very first question: "What's an app key? And what's a token?" This article covers exactly that one step — from applying for access to your first successful quote lookup. Nothing more.
⚠️ This article is for educational and informational purposes only. It is not investment advice, and any losses are your own responsibility.
0. Three Things You Need
- A Korea Investment Securities account — can be opened remotely. This is the account your bot will trade through.
- Python 3.9 or later — the
requestslibrary is all you need. - A paper trading (mock investment) application — don't start on a live account. Paper trading is a practice environment where no real money moves.
1. Applying for API Access and Getting Your App Key
Apply for "OpenAPI" access on the KIS developer portal and you get two strings per account.
- APP KEY — effectively your program's username
- APP SECRET — the password for that username
Here's the mistake beginners make most: pasting these two values straight into the source code. The moment you push to GitHub, anyone can use your account's API. Always pull them from a separate config file or environment variables.
import os
# Read from environment variables (never hardcode in source)
APP_KEY = os.environ["KIS_APP_KEY"]
APP_SECRET = os.environ["KIS_APP_SECRET"]
ACCOUNT_NO = os.environ["KIS_ACCOUNT_NO"] # format: 8 digits-2 digits
# Paper trading server (a different host from live)
BASE_URL = "https://openapivts.koreainvestment.com:29443"
Paper trading and live trading use different domains entirely. If you're practicing but pointing at the live host, authentication won't pass — and the reverse is equally true if you send paper keys to the live host.
2. Getting an Access Token
The app key alone won't fetch you a quote. You present the app key and receive a pass in return — that pass is the access token. It's valid for roughly 24 hours.
import requests
def get_access_token():
url = f"{BASE_URL}/oauth2/tokenP"
body = {
"grant_type": "client_credentials",
"appkey": APP_KEY,
"appsecret": APP_SECRET,
}
res = requests.post(url, json=body, timeout=10)
res.raise_for_status() # raises on any HTTP error
return res.json()["access_token"]
token = get_access_token()
print("token prefix:", token[:10]) # never log the whole token
Printing the full token to your console or log file is a bad habit. If you need a sanity check, print the first few characters only.
One more thing — token issuance is rate-limited per day. Request a fresh one every time you run your code and you'll hit the ceiling fast. The standard approach is to cache the token to a file and only re-request it when the date rolls over.
import json, datetime, pathlib
CACHE = pathlib.Path("token_cache.json") # add this to .gitignore
def load_token():
today = datetime.date.today().isoformat()
if CACHE.exists():
data = json.loads(CACHE.read_text())
if data.get("date") == today:
return data["token"] # reuse today's token
token = get_access_token()
CACHE.write_text(json.dumps({"date": today, "token": token}))
return token
3. Your First Quote — Samsung Electronics' Current Price
Now you hold the pass, so you can request real data. The KIS API uses a value called tr_id to identify "which function am I calling." It differs per function, and sometimes differs between paper and live, so checking the docs is mandatory.
def get_price(token, code="005930"): # 005930 = Samsung Electronics
url = f"{BASE_URL}/uapi/domestic-stock/v1/quotations/inquire-price"
headers = {
"authorization": f"Bearer {token}",
"appkey": APP_KEY,
"appsecret": APP_SECRET,
"tr_id": "FHKST01010100", # current stock quote
}
params = {
"FID_COND_MRKT_DIV_CODE": "J", # J = equities
"FID_INPUT_ISCD": code,
}
res = requests.get(url, headers=headers, params=params, timeout=10)
res.raise_for_status()
out = res.json()["output"]
return {
"name": out["hts_kor_isnm"],
"price": int(out["stck_prpr"]),
"change_pct": float(out["prdy_ctrt"]),
}
print(get_price(load_token()))
# {'name': 'Samsung Electronics', 'price': 71000, 'change_pct': 0.85}
If that prints, your integration works. Everything else is a repeat of the same pattern — change the URL, change the tr_id, change the parameters.
4. Three Errors You Will Definitely Hit
- EGW00123 / invalid token — either the token expired, or you sent a paper token to the live host. Check that your host and your keys are a matched pair first.
- tr_id error — usually the right function but the wrong tr_id: some functions need the paper-trading variant, and a few of those differ in the leading characters.
- Rate limit exceeded — loop through 100 tickers with no pause and you'll get cut off. Put something like
time.sleep(0.2)between calls to space them out.
5. What Comes Next
Once that first lookup works, the natural progression is:
- Loop over several tickers and save the results to CSV
- Compute an indicator like a moving average on the saved data
- Build a dry run that logs instead of ordering when your conditions trigger
- Only after that's thoroughly validated, wire up the paper trading order API
Skipping step 3 and jumping straight to order code is the most expensive beginner mistake there is. Wire up ordering last.
Summary
- App key and app secret go in environment variables, never in source
- Paper and live differ in host and tr_id — keep them matched
- The token is a 24-hour pass — cache it and reuse it
- Your first goal is one thing: printing a current price successfully
- The order API comes dead last
API integration isn't hard so much as unfamiliar. The moment a single price prints to your screen, it's just ordinary HTTP programming from there.
댓글
댓글 쓰기