본문으로 건너뛰기

Exchange Keys — /api/exchange-keys

거래소/브로커 API 키를 사용자별로 등록/조회/삭제하고, 등록 전 dry-run으로 검증하는 라우트입니다.

공통

  • 베이스 경로: /api/exchange-keys
  • 인증: 모든 라우트 JWT 필요
  • 암호화: API key/secret은 AUTH_SECRET_KEY + ENCRYPTION_SALT 기반 Fernet으로 암호화 후 저장
  • 멀티유저 격리: WHERE user_id = current_user.id
Phase 3 제약

현재 paper_mode=true 키만 등록 가능합니다. paper_mode=false 시도는 422 에러. P4-01 이후 라이브 키 허용 (admin role + 확인 모달 필요).


POST /api/exchange-keys

키 등록.

요청 (ExchangeKeyCreate)

필드타입필수기본설명
asset_classcrypto|us_equity|kr_equityNcrypto
exchangestring (≤32)Ybinance/alpaca/kis
api_keystringY평문 (서버에서 암호화)
api_secretstringY평문 (서버에서 암호화)
base_urlstring (≤255)NAlpaca paper/live 분기
account_nostring (≤32)KIS만 YKIS 계좌번호
account_product_codestring (≤8)KIS만 YKIS 상품코드 (예: 01)
paper_modeboolNtrue현재 false 시 422
labelstringNdefault키 별칭
permissionsstringNtrade_only권한 메모

응답 201 (ExchangeKeyResponse)

{
"id": 17,
"asset_class": "us_equity",
"exchange": "alpaca",
"label": "default",
"permissions": "trade_only",
"is_active": true,
"paper_mode": true,
"api_key_masked": "PKAB...x3f2",
"account_no_masked": null,
"account_product_code": null,
"created_at": "2026-04-26T10:00:00Z"
}

api_key_masked는 앞 4 + ... + 뒤 4 형식. 전체 키는 응답에 절대 포함되지 않음.

에러

  • 422 — paper_mode=false, KIS 필수 필드 누락

cURL

curl -X POST http://localhost:8000/api/exchange-keys \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"asset_class": "us_equity",
"exchange": "alpaca",
"api_key": "PKABCD...",
"api_secret": "secret123...",
"paper_mode": true,
"label": "alpaca-paper"
}'

KIS:

curl -X POST http://localhost:8000/api/exchange-keys \
-H "Authorization: Bearer $JWT" \
-d '{
"asset_class": "kr_equity",
"exchange": "kis",
"api_key": "...",
"api_secret": "...",
"account_no": "12345678",
"account_product_code": "01",
"paper_mode": true
}'

GET /api/exchange-keys

호출자의 키 목록 (마스킹된 채로).

Query 파라미터

파라미터기본설명
asset_class필터

응답 200

[
{...ExchangeKeyResponse...}
]

cURL

curl -H "Authorization: Bearer $JWT" \
http://localhost:8000/api/exchange-keys

DELETE /api/exchange-keys/{id}

본인 키 삭제 (cascade — 연결된 BotManager broker 인스턴스도 release).

응답 204

본문 없음.

에러

  • 404 — 다른 user의 키 또는 미존재

cURL

curl -X DELETE -H "Authorization: Bearer $JWT" \
http://localhost:8000/api/exchange-keys/17

POST /api/exchange-keys/test

키를 등록하지 않고 dry-run으로 검증합니다. 임시 broker 인스턴스를 만들어 get_account() 호출 후 즉시 폐기.

요청 (ExchangeKeyTestRequest)

ExchangeKeyCreate와 동일하되 label/permissions 없음.

응답 200 (ExchangeKeyTestResponse)

성공 시:

{
"ok": true,
"asset_class": "us_equity",
"paper_mode": true,
"equity": 100000.0,
"buying_power": 200000.0,
"currency": "USD",
"error": null
}

실패 시:

{
"ok": false,
"asset_class": "us_equity",
"paper_mode": true,
"equity": null,
"buying_power": null,
"currency": null,
"error": "401 Unauthorized: invalid api key"
}

에러

  • 422 — 페이로드 invalid
  • 응답 자체는 200이지만 ok=false + error 필드 사용

cURL

curl -X POST http://localhost:8000/api/exchange-keys/test \
-H "Authorization: Bearer $JWT" \
-d '{
"asset_class": "us_equity",
"exchange": "alpaca",
"api_key": "PKABCD...",
"api_secret": "secret123...",
"paper_mode": true
}'

멀티유저 격리

  • 모든 라우트가 WHERE user_id = current_user.id 강제
  • 다른 user 키 조회/삭제 시 일관되게 404
  • BotRegistry는 user별로 broker 인스턴스를 별도 관리 → 키가 누출돼도 다른 user의 broker로 매핑 불가

비고

  • AUTH_SECRET_KEY 또는 ENCRYPTION_SALT 변경 시 기존 키는 복호화 불가 (mask는 **** 표시). 안전을 위해 시크릿 로테이션 후 재등록 필요
  • 키 권한은 거래소 측에서 trade-only로 제한 권장 (출금 권한은 절대 부여하지 않음)
  • 정상적인 사용에서 응답에 plaintext 키가 포함되는 경우는 없음