Broker OpenAPI Token Auto-Renewal: A Design That Fails Loudly
If you're trading automatically through a broker's OpenAPI, your authentication has to stay alive while you're asleep. These are field notes on token lifetime management collected while running against the Korea Investment Securities (KIS) OpenAPI. The core idea is to make your code permanently aware that "this token will expire eventually."
⚠️ This article is for educational and informational purposes only. It is not investment advice, and any losses are your own responsibility.
Two Different Credentials
The KIS OpenAPI uses two keys that serve genuinely different purposes.
- access token — the Bearer token for REST orders and lookups. Issued via
client_credentials, and it has a lifetime (roughly 24 hours). - approval key — a separate key used to connect to the real-time quote WebSocket.
Confuse the two and you end up in the classic "REST works but real-time won't connect" situation. They are not interchangeable.
Issuing the Access Token
Issuance is a plain POST to the token endpoint carrying your app key and app secret.
func (kis *KISClient) GetAccessToken() error {
kis.mu.Lock()
defer kis.mu.Unlock()
payload := map[string]string{
"grant_type": "client_credentials",
"appkey": kis.AppKey, // never hardcode — load from config/env
"appsecret": kis.AppSecret,
}
// POST /oauth2/tokenP ...
kis.Authorization = "Bearer " + tokenResp.AccessToken
kis.AuthDay = time.Now().Format("20060102") // record the issue *date*
return nil
}
The app key and app secret never get baked into the source. Read them from a config file or environment variables, and encrypt them at rest as a matter of course.
The Core Pattern: Check Freshness Before Every Request
This is the most important piece. Immediately before every API call, verify that the token is from today, and reissue if it isn't.
// Reissue if there is no token, or the issue date is not today
if kis.Authorization == "" || kis.AuthDay != time.Now().Format("20060102") {
if err := kis.GetAccessToken(); err != nil {
return nil, err
}
}
A date-based check is simple but effective. When the token expires as midnight passes, the next request naturally detects it and pulls a fresh one. On top of that, scheduling a preemptive refresh before the market opens (say, 08:05) keeps reissue latency from creeping into your very first order at the bell.
Failed Renewal Means Full Stop
If token reissue fails and the bot keeps running, every subsequent order and lookup fails in sequence with an authentication error. So the rule is unambiguous — on renewal failure, stop trading and raise an alert immediately.
if err := kis.GetAccessToken(); err != nil {
stopTrading() // block new entries
notify("🚨 token renewal failed — bot halted: " + err.Error())
return err
}
"Quietly retry forever" is dangerous. The worst outcome is the session running on while a human has no idea authentication is broken. Stop, alert, and get a person involved.
Summary
- The access token (REST) and approval key (real-time) are separate keys with separate jobs
- Keep app key and app secret out of source — config/env plus encryption
- Before every request, check token freshness by date and reissue when needed
- Refresh preemptively before the open to eliminate opening-bell latency
- Renewal failure = halt trading + alert — no silent retries
Authentication isn't glamorous, but when it collapses the whole bot collapses with it. Making your code permanently conscious of token lifetime is the first step toward running unattended.
댓글
댓글 쓰기