Rust SDK core for Korea Investment & Securities Open API.
kis-sdk is an early Rust client for KIS Open API integrations. The narrow
typed SDK surface focuses on OAuth token issuance and a small domestic stock
slice, while inventory-backed SDK APIs account for all 338 endpoints in the
bundled official inventory by stable operation id. Follow-on work can add more
ergonomic typed wrappers without changing the current coverage boundary.
- Package name:
kis-sdk. - Current package version:
0.2.1. - Crates.io publishing: package metadata is publishable, but the crate has not
been published yet. Actual upload still requires an authorized
v*.*.*tag, thecrates-ioenvironment gate, andCARGO_REGISTRY_TOKEN. - License metadata:
MIT OR Apache-2.0. - Supported runtime: async Rust with
tokio,reqwest, and rustls TLS. - Official contract snapshot:
contracts/kis_official_endpoint_inventory.compact.json, captured on 2026-05-29 Asia/Seoul. - Inventory reconciliation: 338/338 official endpoints are accounted for by
typed methods, scoped inventory APIs, or lower-level
execute_inventory.
KisClientbuilder with explicit environment selection and sharedreqwestclient reuse.- Redacted
AppCredentials,Account,AccountProductCode, andSecretStringhelpers. - OAuth token issuance, token revoke, and WebSocket approval-key issuance, with in-memory token reuse and static bearer token injection for tests.
- Typed domestic stock methods for quotation price, balance inquiry, and cash order calls.
- Inventory-backed overseas stock API surface for 51 endpoints across trading/account, quotation, market-analysis, and realtime-quotation collections.
- Domain-scoped domestic futures/options inventory methods for 44 order/account, quotation, and realtime quotation endpoints, with typed operation-id newtypes for safer call sites.
- Domain-scoped inventory helpers for 29 domestic stock realtime tryitout endpoints and 18 listed bond endpoints, with typed operation-id newtypes available alongside the legacy string constants.
- Collection-specific overseas futures/options inventory wrapper covering all 35 order/account, quotation, and realtime endpoints from the bundled official inventory.
- Inventory-backed
execute_inventorysupport for the bundled official endpoint inventory, including required input/header validation and TR ID selection rules from the captured metadata. - Domestic stock REST
execute_domestic_stock_restsupport for the 158 listed endpoints across the domestic stock trading/account, quotation, ELW, sector/misc, product info, market analysis, and ranking analysis collections. - Explicit
RetryPolicyandFallbackPolicyoptions. Retry is disabled by default.RetryPolicy::conservative_reads()retries retryable GET/read failures only and does not retry trading POST mutations.
If the crate is not available on crates.io yet, use the repository directly:
[dependencies]
kis-sdk = { git = "https://github.com/bogyie/kis-sdk", branch = "main" }After the first authorized crates.io publish completes, consumers should be able to switch to a versioned dependency:
[dependencies]
kis-sdk = "0.2"use kis_sdk::{
apis::domestic_stock::{DomesticStockMarketDivision, InquirePriceRequest},
config::Environment,
credentials::AppCredentials,
KisClient,
};
#[tokio::main]
async fn main() -> Result<(), kis_sdk::KisError> {
let client = KisClient::builder(Environment::Real)
.app_credentials(AppCredentials::new(
std::env::var("KIS_APP_KEY").expect("KIS_APP_KEY is required"),
std::env::var("KIS_APP_SECRET").expect("KIS_APP_SECRET is required"),
))
.build()?;
let quote = client
.inquire_domestic_stock_price(&InquirePriceRequest::with_market(
DomesticStockMarketDivision::Stock,
"005930",
))
.await?;
assert!(quote.is_success());
Ok(())
}The current SDK surface has two layers:
- Typed methods for OAuth and selected domestic stock workflows.
- Inventory-backed methods for the full bundled official endpoint inventory. These methods validate required inventory fields and safety rules before network I/O, but they do not yet provide narrow Rust request/response structs for every endpoint.
The typed SDK currently exposes:
| Method | KIS path | Notes |
|---|---|---|
issue_access_token |
/oauth2/tokenP |
OAuth token issuance and in-memory token reuse. |
revoke_access_token |
/oauth2/revokeP |
Explicit OAuth access-token revoke; never called implicitly on drop. |
issue_realtime_approval_key |
/oauth2/Approval |
Issues a WebSocket access approval key only; live WebSocket subscription management is outside the current typed API. |
inquire_domestic_stock_price |
/uapi/domestic-stock/v1/quotations/inquire-price |
Domestic stock quote read. |
inquire_domestic_stock_balance |
/uapi/domestic-stock/v1/trading/inquire-balance |
Domestic stock balance read. |
place_domestic_stock_cash_order |
/uapi/domestic-stock/v1/trading/order-cash |
Real cash orders are locally blocked by KisError::LiveTradingDisabled. |
execute_domestic_stock_realtime_tryitout |
/tryitout/* |
Domain-scoped inventory execution for 29 domestic stock realtime tryitout endpoints. This is not a live WebSocket subscription API. |
execute_bond_trading_account |
/uapi/domestic-bond/v1/trading/* |
Domain-scoped inventory execution for 7 listed bond trading/account endpoints. Real trading mutations remain locally blocked. |
execute_bond_quotation |
/uapi/domestic-bond/v1/quotations/* |
Domain-scoped inventory execution for 8 listed bond quotation endpoints. |
execute_bond_realtime_tryitout |
/tryitout/* |
Domain-scoped inventory execution for 3 listed bond realtime tryitout endpoints. This is not a live WebSocket subscription API. |
execute_overseas_futures_options |
35 overseas futures/options inventory endpoints | Collection-specific wrapper keyed by OverseasFuturesOptionsEndpoint; all bundled endpoints are real-only, required fields are validated from inventory, and real trading mutations are locally blocked. |
For new call sites, prefer the typed variants where they exist:
Account::domestic_stock, InquirePriceRequest::with_market,
CashOrderRequest::with_order_division,
execute_domestic_stock_realtime_tryitout_operation,
execute_bond_*_operation, and
execute_domestic_futures_options_operation. The older String fields,
string constants, and &str operation-id methods remain available for
compatibility.
Typed helpers still serialize to the exact KIS wire values. For example,
AccountProductCode::DomesticStock serializes to 01,
DomesticStockMarketDivision::Stock serializes to J, and
CashOrderDivision::Limit serializes to 00. Operation newtypes expose their
stable inventory operation id through operation_id() and reject out-of-scope
strings through FromStr.
The domestic futures/options SDK surface exposes inventory-backed domain methods for all 44 endpoints in these bundled official collections:
| Collection | Endpoint count | SDK entry point |
|---|---|---|
| Domestic futures/options trading/account | 15 | execute_domestic_futures_options_trading_account |
| Domestic futures/options quotations | 9 | execute_domestic_futures_options_quotation |
| Domestic futures/options realtime quotations | 20 | execute_domestic_futures_options_realtime_quotation |
The same endpoints are also available through the combined
execute_domestic_futures_options method. Operation ids are exposed through
kis_sdk::apis::domestic_futures_options::{TRADING_ACCOUNT_OPERATION_IDS, QUOTATION_OPERATION_IDS, REALTIME_QUOTATION_OPERATION_IDS}.
The bundled inventory covers 338 official endpoints across 22 collections. Endpoints outside the typed domestic stock methods and domain-scoped inventory surfaces do not yet have ergonomic typed Rust request methods, but they can be addressed and called through the lower-level inventory execution API with stable operation ids:
use kis_sdk::endpoint::InventoryRequest;
use serde_json::json;
let response = client
.execute_inventory::<serde_json::Value>(
"domestic_stock_quotation.get_domestic_stock_quotations_inquire_price",
InventoryRequest::new().query(json!({
"FID_COND_MRKT_DIV_CODE": "J",
"FID_INPUT_ISCD": "005930"
})),
)
.await?;For domestic stock REST coverage, prefer the scoped helper when the operation must stay inside the listed domestic stock REST collections:
use kis_sdk::endpoint::InventoryRequest;
use serde_json::json;
let response = client
.execute_domestic_stock_rest::<serde_json::Value>(
"domestic_stock_quotation.get_domestic_stock_quotations_inquire_price",
InventoryRequest::new().query(json!({
"FID_COND_MRKT_DIV_CODE": "J",
"FID_INPUT_ISCD": "005930"
})),
)
.await?;The inventory execution API follows the same safety boundary as the typed methods: required query/body/non-standard header fields are validated before network I/O, standard KIS headers are filled by the client, ambiguous TR IDs require an explicit override, real-only endpoints are rejected in mock mode, and real trading mutations are locally blocked.
The machine-checkable reconciliation test
full_inventory_reconciliation_accounts_for_every_official_endpoint_once
proves that every endpoint in the bundled official inventory is assigned to
exactly one SDK-callable coverage surface:
| Coverage surface | Endpoint count |
|---|---|
| OAuth typed methods | 3 |
| Domestic stock REST inventory API | 158 |
| Domestic stock realtime tryitout inventory API | 29 |
| Domestic futures/options inventory API | 44 |
| Overseas stock inventory API | 51 |
| Overseas futures/options inventory API | 35 |
| Listed bond inventory API | 18 |
| Total accounted official inventory | 338/338 |
See docs/contract-quality-report.md for
the collection split, mock-contract evidence, and known limitations. The
coverage count is based on the captured BOG-221 inventory snapshot, not a live
portal re-scrape.
The overseas stock SDK surface pins inventory-backed endpoint handles for these collections:
| Collection | Endpoint count | Access |
|---|---|---|
[해외주식] 주문/계좌 |
18 | OverseasStockCollection::TradingAccount / TRADING_ACCOUNT_ENDPOINTS |
[해외주식] 기본시세 |
14 | OverseasStockCollection::Quotation / QUOTATION_ENDPOINTS |
[해외주식] 시세분석 |
15 | OverseasStockCollection::MarketAnalysis / MARKET_ANALYSIS_ENDPOINTS |
[해외주식] 실시간시세 |
4 | OverseasStockCollection::RealtimeQuotation / REALTIME_QUOTATION_ENDPOINTS |
use kis_sdk::{
apis::overseas_stock::OverseasStockEndpoint,
endpoint::InventoryRequest,
};
use serde_json::json;
let response = client
.execute_overseas_stock::<serde_json::Value>(
OverseasStockEndpoint::GetOverseasPriceQuotationsPrice,
InventoryRequest::new().query(json!({
"AUTH": "",
"EXCD": "NAS",
"SYMB": "AAPL"
})),
)
.await?;The realtime helpers intentionally execute the REST-style inventory tryitout shape captured in the bundled official inventory. Future live WebSocket subscription support should use a separate API so callers do not confuse tryitout coverage with streaming behavior.
- Do not hard-code real app keys, app secrets, access tokens, approval keys, account numbers, or customer data.
- Prefer loading real credentials from a secret manager or process environment outside source control.
- Use
AppCredentials::new("<app-key>", "<app-secret>")andAccount::new("<8-digit-cano>", "<2-digit-product-code>")placeholders in examples and tests. SecretStringredacts debug output, but callers must still avoid logging raw values before constructing SDK types.- Real trading mutations are blocked locally in the current implementation.
Repository checks used for the current SDK and documentation baseline:
cargo fmt --check
cargo test --locked
cargo test --locked --test mock_server_contract
cargo doc --locked --no-deps
git diff --checkContract evidence is recorded in
docs/contract-quality-report.md. The
mock-server test suite requests every bundled endpoint and verifies expected
mock support or explicit KIS_MOCK_UNSUPPORTED_ENVIRONMENT rejection.
The developer-only mock server guide is available at
docs/mock-server/README.md for contract and
test-harness validation.
This repository is prepared for an authorized crates.io publish, but publishing has not been performed from this branch.
Cargo.tomlincludes package name, version, edition, license, description, repository, README, and keywords.- README and usage documentation avoid secrets and use placeholder-only examples.
- The developer mock-server harness and contract-quality report provide package validation evidence without live KIS credentials.
- The publish workflow runs
scripts/verify-crates-publishable.pybefore the third-party publish action sopublish = falseor registry restrictions cannot produce a false-positive empty publish. - Before publishing, confirm license-file expectations, run
cargo package --locked, review the generated package contents, and use only the authorized tag/environment workflow for any upload step.