Emulates all 41 operations of the Toss Invest Open API. Accounts, holdings, orders, and conditional orders are stateful; market and reference data are generated deterministically from the seeded prices.
Included now:
POST /oauth2/token(client credentials; form body or HTTP Basic). Issuing a new token revokes the previous one (token-revoked).- Market data:
/api/v1/prices,/orderbook,/trades,/price-limits,/candles - Stock info:
/api/v1/stocks,/stocks/all, warnings, and KR investor trends - Market info: exchange rate, KR/US market calendars, rankings, market indicators
- Sectors: list, rankings, detail, member stocks and ETFs
- Accounts:
/api/v1/accounts,/api/v1/holdings - Orders: create, modify, cancel, list (
OPEN/CLOSEDwith cursor), detail - Conditional orders (
SINGLE/OCO/OTO): create, list, detail, modify, delete - Buying power, sellable quantity, commissions
- Emulator control:
POST /_emulator/pricesmoves a price, filling resting limit orders and firing conditional orders
Account-scoped endpoints need the X-Tossinvest-Account: <accountSeq> header.
Responses are wrapped in { "result": ... }; errors are
{ "error": { "requestId", "code", "message", "data"? } }. The exception is
POST /oauth2/token, which answers in the plain OAuth2 shape
({ "access_token", "token_type", "expires_in" }, or
{ "error", "error_description" }).
Start
# From this repo (after `bun install && bun run build`)
bun packages/emulate/dist/index.js --service tossinvest
# Or from the published package
npx @pleaseai/emulate --service tossinvestA single service starts on the base port (default 4000). Use -p <port> to
change it. When started alongside other services, ports are assigned
sequentially from the base port.
Auth
curl -X POST http://localhost:4000/oauth2/token \
-d grant_type=client_credentials \
-d client_id=tsck_example_client_id \
-d client_secret=tsck_example_client_secretTrading Example
# Current price
curl "http://localhost:4000/api/v1/prices?symbols=005930" \
-H "Authorization: Bearer <access_token>"
# Market buy; fills immediately at the current price
curl -X POST http://localhost:4000/api/v1/orders \
-H "Authorization: Bearer <access_token>" \
-H "X-Tossinvest-Account: 1" \
-H "content-type: application/json" \
-d '{"symbol":"005930","side":"BUY","orderType":"MARKET","quantity":"10"}'
# Move the price to fill a resting limit order
curl -X POST http://localhost:4000/_emulator/prices \
-H "content-type: application/json" \
-d '{"symbol":"005930","price":70000}'Seed Config
Add a tossinvest: section to emulate.config.yaml (or pass --seed <file>):
tossinvest:
holidays: # optional; a listed country (KR or US) replaces its built-in 2026 holiday set; null or [] clears it
KR: [2026-05-05, 2026-10-09]
US: [2026-07-03]
clients:
- client_id: tsck_example_client_id
client_secret: tsck_example_client_secret
name: Demo App
stocks:
- symbol: '005930'
name: 삼성전자
market: KOSPI
price: 72000
accounts:
- account_seq: 1
cash:
krw: 10000000
usd: 10000
commission:
kr: '0.00015'
us: '0.001'
kr_start_date: 2026-01-01 # KR startDate in GET /commissions
holdings:
- symbol: '005930'
quantity: 100
average_price: 65000