--- name: stockcrypto-logs version: 2.0.0 description: "투자 거래 자동 기록 — AI 에이전트를 위한 StockCrypto-Logs API 가이드" api_base: https://www.stockcrypto-log.com/api/v1 skill_url: https://www.stockcrypto-log.com/skill.md --- # StockCrypto-Logs Agent Skill AI 에이전트(Claude Code, OpenAI Assistants, MCP 클라이언트 등)가 사용자의 투자 거래를 자동으로 기록할 수 있도록 하는 개인 API 키 시스템입니다. --- ## 빠른 시작 (Quick Start) ``` 1. API 키 발급: 설정 > 개발자 API 탭 2. GET /api/v1/accounts → 내 계좌 목록 확인 3. POST /api/v1/instruments/resolve → 티커 정규화 4. POST /api/v1/orders/validate → 계좌/티커/주문 사전검증 5. POST /api/v1/orders → 주문 실행 (Idempotency-Key 필수) 6. GET /api/v1/orders → 주문 상태 확인 ``` --- ## Authentication 모든 요청에 아래 헤더를 포함하세요. ``` Authorization: Bearer slg_ ``` - 키 형식: `slg_` 로 시작하는 64자 소문자 16진수 문자열 - 키 발급: **설정 > 개발자 API** 탭 - 키는 발급 시 **단 한 번만** 표시됩니다. 반드시 안전한 곳에 즉시 저장하세요. - 키 분실 시 재발급 불가 — 비활성화 후 새 키를 발급해야 합니다. - **하나의 에이전트/스크립트마다 별도의 키를 발급**하여 관리를 권장합니다. - 요청 시마다 `last_used_at`이 자동 갱신됩니다. ## Scope Matrix `/api/v1` 호출은 API Key의 `scopes`에 따라 허용됩니다. | Endpoint | Required Scope | |------|------| | `GET /api/v1/accounts` | `orders:read` | | `POST /api/v1/instruments/resolve` | `orders:write` | | `POST /api/v1/orders/validate` | `orders:write` | | `POST /api/v1/orders` | `orders:write` | | `DELETE /api/v1/orders/:broker_order_id` | `orders:write` | | `GET /api/v1/orders` / `GET /api/v1/fills` / `GET /api/v1/trades` | `orders:read` | | `GET /api/v1/positions` | `positions:read` | | `GET /api/v1/balances` | `balances:read` | 기본 스코프는 read-only(`orders:read`, `positions:read`, `balances:read`)이며, 주문 실행이 필요한 키만 `orders:write`를 부여하세요. ## Rate Limit Policy - 기본값: `60 req/min` - burst: `120` - 응답 헤더: - `x-ratelimit-limit` - `x-ratelimit-remaining` - `retry-after` (429일 때) - `RATE_LIMITED` 응답을 받으면 `retry-after` 초만큼 대기 후 재시도하세요. --- ## 인증 에러 상세 | 상황 | HTTP | error 메시지 | |------|------|-------------| | Authorization 헤더 없음 | 401 | `Missing or invalid Authorization header. Use: Bearer slg_` | | `slg_` 로 시작하지 않는 키 | 401 | `Invalid API key format. Keys must start with slg_` | | 존재하지 않는 키 | 401 | `Invalid API key` | | 비활성화된 키 | 401 | `API key has been deactivated` | ## Scope/Rate 에러 코드 | 상황 | HTTP | `error.code` | |------|------|-------------| | 요청 endpoint에 필요한 scope가 없음 | 403 | `INSUFFICIENT_SCOPE` | | 키별 요청 제한 초과 | 429 | `RATE_LIMITED` | --- ## Endpoints ### GET /api/v1/accounts 계좌 목록을 반환합니다. > **중요**: 거래 기록 전 반드시 이 API를 먼저 호출하여 유효한 `account_id`를 확인하세요. > `account_id`를 추측하거나 하드코딩하지 마세요 — 유효성은 반드시 API로 검증해야 합니다. **Request** ```http GET /api/v1/accounts Authorization: Bearer slg_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ``` **Response 200** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "broker_name": "키움증권", "account_name": "위탁계좌", "is_primary": true, "created_at": "2025-01-01T00:00:00.000Z" }, { "id": "660e8400-e29b-41d4-a716-446655440001", "broker_name": "업비트", "account_name": "코인 지갑", "is_primary": false, "created_at": "2025-02-01T00:00:00.000Z" } ] ``` > `is_primary: true` 인 계좌가 사용자의 기본 계좌입니다. 계좌 선택 기준이 없다면 이 계좌를 사용하세요. > 계좌가 없으면 빈 배열 `[]`을 반환합니다. 이 경우 거래 기록 전 앱에서 계좌를 먼저 생성해야 합니다. --- ### POST /api/v1/trades 거래 1건을 기록합니다. **Request** ```http POST /api/v1/trades Authorization: Bearer slg_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Content-Type: application/json ``` **Request Body** 필수 필드만 포함한 최소 예시: ```json { "account_id": "550e8400-e29b-41d4-a716-446655440000", "date": "2026-02-24", "ticker": "AAPL", "market": "US", "type": "BUY", "currency": "USD", "quantity": 10, "price": 215.50 } ``` 전체 필드 예시: ```json { "account_id": "550e8400-e29b-41d4-a716-446655440000", "date": "2026-02-24", "ticker": "NVDA", "name": "NVIDIA Corporation", "market": "US", "type": "BUY", "currency": "USD", "quantity": 5, "price": 850.00, "exchange_rate": 1380, "fees": 2500, "notes": "AI 반도체 수요 지속. 실적 발표 전 진입.", "asset_category": "SPOT", "position_side": "LONG", "emotion": "conviction", "confidence": 4, "rule_compliance": 90 } ``` **Response 201** ```json { "id": "770e8400-e29b-41d4-a716-446655440002", "user_id": "880e8400-e29b-41d4-a716-446655440003", "account_id": "550e8400-e29b-41d4-a716-446655440000", "date": "2026-02-24", "ticker": "NVDA", "name": "NVIDIA Corporation", "market": "US", "type": "BUY", "currency": "USD", "quantity": 5, "price": 850.00, "exchange_rate": 1380, "fees": 2500, "notes": "AI 반도체 수요 지속. 실적 발표 전 진입.", "asset_category": "SPOT", "position_side": "LONG", "leverage": 1, "emotion": "conviction", "confidence": 4, "rule_compliance": 90, "created_at": "2026-02-24T09:30:00.000Z", "updated_at": "2026-02-24T09:30:00.000Z" } ``` **유효성 검사 실패 응답 (400)** ```json { "error": "Validation Failed", "details": [ { "code": "invalid_enum_value", "path": ["market"], "message": "Invalid enum value. Expected 'KR' | 'US' | 'CRYPTO', received 'NYSE'" }, { "code": "invalid_string", "path": ["date"], "message": "date must be YYYY-MM-DD" } ] } ``` --- ### GET /api/v1/trades 최근 거래 목록을 반환합니다. 거래 기록 후 확인용으로 사용합니다. **Query Parameters** | 파라미터 | 타입 | 기본값 | 최대값 | 설명 | |---------|------|--------|--------|------| | `limit` | integer | 20 | 100 | 반환할 거래 수 | **Request** ```http GET /api/v1/trades?limit=5 Authorization: Bearer slg_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ``` **Response 200** — 최신 날짜 순(내림차순) 정렬 ```json [ { "id": "770e8400-e29b-41d4-a716-446655440002", "user_id": "880e8400-e29b-41d4-a716-446655440003", "account_id": "550e8400-e29b-41d4-a716-446655440000", "date": "2026-02-24", "ticker": "NVDA", "name": "NVIDIA Corporation", "market": "US", "type": "BUY", "currency": "USD", "quantity": 5, "price": 850.00, "exchange_rate": 1380, "fees": 2500, "notes": "AI 반도체 수요 지속.", "asset_category": "SPOT", "position_side": "LONG", "leverage": 1, "emotion": "conviction", "confidence": 4, "rule_compliance": 90, "created_at": "2026-02-24T09:30:00.000Z", "updated_at": "2026-02-24T09:30:00.000Z" } ] ``` --- ## Trade Field Reference ### 필수 필드 | 필드 | 타입 | 유효성 규칙 | 설명 | |------|------|------------|------| | `account_id` | string (UUID) | 사용자 소유의 계좌 UUID | 계좌 ID — GET /api/v1/accounts 로 확인 | | `date` | string | `YYYY-MM-DD` 형식 정규식 | 거래일 (시간 제외) | | `ticker` | string | 1자 이상 | 종목 코드 (예: AAPL, 005930, BTC) | | `market` | enum | `KR` \| `US` \| `CRYPTO` | 시장 구분 | | `type` | enum | `BUY` \| `SELL` \| `TRANSFER_IN` \| `TRANSFER_OUT` | 거래 유형 | | `currency` | enum | `KRW` \| `USD` | 거래 통화 | | `quantity` | number | 양수 (0 초과) | 거래 수량 | | `price` | number | 0 이상 (음수 불가) | 거래 단가 | ### 선택 필드 (모두 기본값 있음) | 필드 | 타입 | 기본값 | 유효성 규칙 | 설명 | |------|------|--------|------------|------| | `name` | string | — | — | 종목명 (예: Apple Inc., 삼성전자) | | `exchange_rate` | number | `1` | 양수 | 환율 (KRW/USD 기준, USD 거래 시 필수 권장) | | `fees` | number | `0` | 0 이상 | 수수료 (KRW 기준) | | `notes` | string \| null | — | — | 거래 메모 (투자 이유, 전략 등) | | `asset_category` | enum | `SPOT` | `SPOT` \| `FUTURE` \| `OPTION` | 자산 유형 | | `position_side` | enum | `LONG` | `LONG` \| `SHORT` | 포지션 방향 | | `leverage` | number | `1` | — | 레버리지 배수 (선물/옵션) | | `option_type` | enum \| null | — | `CALL` \| `PUT` | 옵션 유형 | | `strike_price` | number \| null | — | — | 행사가 (옵션) | | `expiry_date` | string \| null | — | `YYYY-MM-DD` | 만기일 (선물/옵션) | | `emotion` | enum \| null | — | `fomo` \| `fear` \| `calm` \| `conviction` \| `tilt` | 거래 시 감정 | | `confidence` | number \| null | — | 1~5 정수 | 자신감 지수 | | `rule_compliance` | number \| null | — | 0~100 정수 | 매매 원칙 준수율 (%) | --- ## Market & Ticker 규칙 ### KR (한국 주식) - `ticker`: 6자리 숫자 코드 (예: `005930` = 삼성전자, `000660` = SK하이닉스) - `currency`: `KRW` - `exchange_rate`: `1` (KRW 거래이므로 환산 불필요) ```json { "ticker": "005930", "name": "삼성전자", "market": "KR", "currency": "KRW", "exchange_rate": 1 } ``` ### US (미국 주식) - `ticker`: 알파벳 코드 (예: `AAPL`, `TSLA`, `NVDA`, `SPY`) - `currency`: `USD` - `exchange_rate`: 거래 시점 USD/KRW 환율 (예: `1380`) ```json { "ticker": "AAPL", "name": "Apple Inc.", "market": "US", "currency": "USD", "exchange_rate": 1380 } ``` ### CRYPTO (암호화폐) - `ticker`: 코인 심볼 대문자 (예: `BTC`, `ETH`, `XRP`, `SOL`) - `currency`: KRW 거래소(업비트/빗썸)는 `KRW`, USD 거래소(바이낸스)는 `USD` - KRW 거래 시 `exchange_rate: 1`, USD 거래 시 환율 기입 ```json { "ticker": "BTC", "name": "Bitcoin", "market": "CRYPTO", "currency": "KRW", "exchange_rate": 1 } ``` --- ## 거래 유형 설명 | type | 설명 | 일반적인 사용 사례 | |------|------|------------------| | `BUY` | 매수 | 주식/코인 구매 | | `SELL` | 매도 | 주식/코인 판매 | | `TRANSFER_IN` | 입금/입고 | 계좌 간 자산 이동 (수취) | | `TRANSFER_OUT` | 출금/출고 | 계좌 간 자산 이동 (송출) | --- ## 감정 필드 설명 | emotion | 한국어 의미 | 설명 | |---------|------------|------| | `fomo` | 상승 추격 | 놓칠까봐 불안해서 추격 매수 | | `fear` | 공포 매도 | 하락 공포에 의한 손절 | | `calm` | 냉정 | 계획에 따른 기계적 매매 | | `conviction` | 확신 | 충분한 분석 후 강한 확신으로 진입 | | `tilt` | 틸트 | 손실 후 감정적 복구 매매 | --- ## Examples ### 예시 1: 미국 주식 매수 (Apple) ```typescript const API_KEY = 'slg_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; const BASE = 'https://www.stockcrypto-log.com'; // 1. 계좌 목록 조회 const accounts = await fetch(`${BASE}/api/v1/accounts`, { headers: { Authorization: `Bearer ${API_KEY}` } }).then(r => r.json()); // 2. 기본(primary) 계좌 선택 const account = accounts.find(a => a.is_primary) ?? accounts[0]; if (!account) throw new Error('계좌가 없습니다. 앱에서 먼저 계좌를 생성하세요.'); // 3. 거래 기록 const trade = await fetch(`${BASE}/api/v1/trades`, { method: 'POST', headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ account_id: account.id, date: '2026-02-24', ticker: 'AAPL', name: 'Apple Inc.', market: 'US', type: 'BUY', currency: 'USD', quantity: 10, price: 215.50, exchange_rate: 1380, fees: 1500, notes: '실적 발표 전 기술적 지지선 매수', emotion: 'conviction', confidence: 4, rule_compliance: 95 }) }).then(r => r.json()); console.log('거래 기록 완료:', trade.id); ``` --- ### 예시 2: 한국 주식 매도 (삼성전자) ```typescript const trade = await fetch(`${BASE}/api/v1/trades`, { method: 'POST', headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ account_id: account.id, date: '2026-02-25', ticker: '005930', name: '삼성전자', market: 'KR', type: 'SELL', currency: 'KRW', quantity: 50, price: 72000, exchange_rate: 1, fees: 3600, notes: '목표가 도달. 1차 분할 매도.', emotion: 'calm', confidence: 5, rule_compliance: 100 }) }).then(r => r.json()); ``` --- ### 예시 3: 비트코인 매수 (업비트, KRW) ```typescript const trade = await fetch(`${BASE}/api/v1/trades`, { method: 'POST', headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ account_id: account.id, date: '2026-02-26', ticker: 'BTC', name: 'Bitcoin', market: 'CRYPTO', type: 'BUY', currency: 'KRW', quantity: 0.005, price: 120000000, exchange_rate: 1, fees: 3000, notes: '반감기 후 재축적 구간', emotion: 'conviction', confidence: 4 }) }).then(r => r.json()); ``` --- ### 예시 4: 최근 거래 확인 (기록 후 검증) ```typescript const recent = await fetch(`${BASE}/api/v1/trades?limit=5`, { headers: { Authorization: `Bearer ${API_KEY}` } }).then(r => r.json()); console.log('최근 5건:'); recent.forEach(t => { console.log(` ${t.date} [${t.market}] ${t.ticker} ${t.type} ${t.quantity}주 @ ${t.price}`); }); ``` --- ### 예시 5: 에러 처리 포함 전체 플로우 ```typescript async function recordTrade(apiKey: string, tradeData: object) { const BASE = 'https://www.stockcrypto-log.com'; const headers = { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' }; // 1. 계좌 확인 const accountsRes = await fetch(`${BASE}/api/v1/accounts`, { headers }); if (accountsRes.status === 401) { throw new Error('API 키가 유효하지 않습니다. 설정 > 개발자 API에서 확인하세요.'); } const accounts = await accountsRes.json(); if (accounts.length === 0) { throw new Error('계좌가 없습니다. 앱에서 먼저 계좌를 생성해 주세요.'); } const account = accounts.find(a => a.is_primary) ?? accounts[0]; // 2. 거래 기록 const tradeRes = await fetch(`${BASE}/api/v1/trades`, { method: 'POST', headers, body: JSON.stringify({ account_id: account.id, ...tradeData }) }); if (tradeRes.status === 400) { const err = await tradeRes.json(); const msgs = err.details?.map((d: any) => `${d.path.join('.')}: ${d.message}`).join('\n'); throw new Error(`입력 오류:\n${msgs}`); } if (tradeRes.status === 403) { throw new Error('계좌 접근 권한이 없습니다.'); } if (!tradeRes.ok) { throw new Error(`서버 오류 (${tradeRes.status})`); } return await tradeRes.json(); } ``` --- ### 예시 6: 여러 거래 순차 기록 (배치) ```typescript async function recordBatch(apiKey: string, trades: object[]) { const BASE = 'https://www.stockcrypto-log.com'; const headers = { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' }; // 계좌 1회만 조회 const accounts = await fetch(`${BASE}/api/v1/accounts`, { headers }).then(r => r.json()); const account = accounts.find(a => a.is_primary) ?? accounts[0]; const results = []; for (const tradeData of trades) { try { const res = await fetch(`${BASE}/api/v1/trades`, { method: 'POST', headers, body: JSON.stringify({ account_id: account.id, ...tradeData }) }); const data = await res.json(); results.push({ success: res.ok, data }); // API 부하 방지를 위해 요청 간 딜레이 권장 await new Promise(resolve => setTimeout(resolve, 200)); } catch (err) { results.push({ success: false, error: err }); } } return results; } ``` --- ## 주식 자동화 주문 플로우 (신규) `/api/v1/trades`는 거래 기록용이며, 자동 주문 워크플로우는 아래 순서를 사용합니다. 1. `GET /api/v1/accounts`로 유효한 `account_id` 확인 2. `POST /api/v1/instruments/resolve`로 티커 정규화 3. `POST /api/v1/orders/validate`로 계좌/브로커/주문 사전검증 4. `POST /api/v1/orders`로 주문 실행 (`Idempotency-Key` 헤더 필수) 5. `DELETE /api/v1/orders/:broker_order_id`로 미체결(`PENDING`) 주문 취소 6. `GET /api/v1/orders`, `GET /api/v1/fills`, `GET /api/v1/positions`, `GET /api/v1/balances`로 상태 조회 ### POST /api/v1/instruments/resolve ```json { "market": "US", "ticker": "AAPL" } ``` 성공 응답에는 `instrumentKey`, `symbol`, `venue`, `tradable`이 포함됩니다. ### POST /api/v1/orders/validate ```json { "account_id": "550e8400-e29b-41d4-a716-446655440000", "broker": "키움증권", "market": "US", "ticker": "AAPL", "type": "BUY", "currency": "USD", "quantity": 1, "price": 210.5 } ``` 성공 시 `validation_token`을 반환하며 유효시간은 짧습니다(기본 60초). ### POST /api/v1/orders 필수 헤더: - `Authorization: Bearer slg_` - `Idempotency-Key: ` 요청 바디는 `orders/validate` 필드 + `validation_token`을 포함해야 합니다. 성공 응답 예시: ```json { "order_id": "b8df4b46-0d9f-42f5-ae5d-73e2e8fce23a", "broker_order_id": "db3280d9-f6e0-4323-9aa5-b8f6ab09f4a5", "status": "PENDING", "request_id": "db3280d9-f6e0-4323-9aa5-b8f6ab09f4a5" } ``` ### DELETE /api/v1/orders/:broker_order_id `PENDING` 상태 주문을 취소합니다. - 이미 `CANCELED` 상태인 주문은 **동일 성공 응답**을 반환합니다 (idempotent cancel). - `FILLED` 상태 주문은 취소할 수 없습니다. 요청 예시: ```http DELETE /api/v1/orders/db3280d9-f6e0-4323-9aa5-b8f6ab09f4a5 Authorization: Bearer slg_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ``` 성공 응답 예시: ```json { "order_id": "b8df4b46-0d9f-42f5-ae5d-73e2e8fce23a", "broker_order_id": "db3280d9-f6e0-4323-9aa5-b8f6ab09f4a5", "status": "CANCELED", "canceled_at": "2026-03-05T01:23:45.678Z", "request_id": "bd5c8440-13fd-4de5-8ec4-8ab6dcf60af9" } ``` --- ## 에이전트 동작 원칙 AI 에이전트가 이 API를 사용할 때 반드시 따라야 할 원칙: 1. **계좌 확인 필수**: 거래 기록 전 항상 `GET /api/v1/accounts`를 호출하여 유효한 `account_id`를 확인합니다. 2. **account_id 하드코딩 금지**: UUID는 세션마다 변하지 않지만, 사용자가 계좌를 삭제/재생성할 수 있으므로 항상 동적으로 조회하세요. 3. **날짜 형식 엄수**: `date` 필드는 반드시 `YYYY-MM-DD` 형식이어야 합니다. ISO 8601 타임스탬프(`2026-02-24T09:30:00Z`)는 유효하지 않습니다. 4. **market 추론 규칙**: - 6자리 숫자 → `KR` + `KRW` - 알파벳 코드 → `US` + `USD` - 주요 코인 심볼(BTC, ETH, XRP 등) → `CRYPTO` 5. **환율 처리**: USD 거래 시 `exchange_rate`에 실제 환율을 기입하면 KRW 기준 포트폴리오 가치 계산이 정확해집니다. 환율이 불확실할 경우 생략(기본값 1)하고 notes에 기록하세요. 6. **수수료 단위**: `fees`는 항상 **KRW 기준**입니다. USD 수수료는 환율을 곱해 KRW로 변환하세요. 7. **음수 quantity 금지**: 매도는 `type: "SELL"` + 양수 `quantity`로 표현합니다. --- ## Error Responses | HTTP 코드 | 의미 | 대처 | |-----------|------|------| | 400 | 입력 유효성 검사 실패 | `details` 배열에서 오류 필드 확인 후 수정 | | 401 | API 키 없음 / 잘못됨 / 비활성화 | 설정 > 개발자 API에서 키 확인 또는 재발급 | | 403 | account_id가 본인 소유가 아님 | GET /api/v1/accounts로 유효한 계좌 ID 재확인 | | 404 | 주문 미존재 (`ORDER_NOT_FOUND`) | `broker_order_id` 확인 후 재시도 | | 409 | 멱등키 충돌 / 계좌-브로커 불일치 / 주문 취소 불가 (`ORDER_NOT_CANCELLABLE`) | 새 Idempotency-Key 사용 또는 account/broker/order status 재확인 | | 422 | 티커 해석 실패 | `/api/v1/instruments/resolve` 결과 기준으로 재시도 | | 500 | 서버 내부 오류 | 잠시 후 재시도; 지속 시 앱 지원팀에 문의 | 주요 에러 코드: - `ACCOUNT_BROKER_MISMATCH` - `TICKER_NOT_RESOLVED` - `IDEMPOTENCY_CONFLICT` - `ORDER_NOT_FOUND` - `ORDER_NOT_CANCELLABLE` 취소 API 에러 예시: ```json { "error": { "code": "ORDER_NOT_CANCELLABLE", "message": "order status FILLED cannot be canceled", "request_id": "a11708af-cfe4-43b9-8446-3c39d825341c" } } ``` --- ## Security Policy - API 키는 **`https://www.stockcrypto-log.com` 도메인에만** 전송하세요. - 다른 도메인(프록시, 제3자 서버 등)으로 키를 전송하는 것은 절대 금지입니다. - 키가 노출된 경우 **즉시** 설정 > 개발자 API에서 비활성화하고 새 키를 발급하세요. - 소스 코드, 공개 저장소, 채팅, 로그에 키를 직접 포함하지 마세요 — 환경 변수나 시크릿 매니저를 사용하세요. - 하나의 에이전트/스크립트마다 별도의 키를 사용하면 노출 시 피해 범위를 최소화할 수 있습니다.