refactor docs into technical specs
This commit is contained in:
@@ -0,0 +1,663 @@
|
||||
# API Contracts
|
||||
|
||||
- Related REQ: `REQ-001`, `REQ-002`, `REQ-003`
|
||||
|
||||
## 공통 원칙
|
||||
- 응답은 JSON 기반으로 반환한다.
|
||||
- 인증이 필요한 엔드포인트는 `Authorization: Bearer <token>`을 사용한다.
|
||||
- 권한 계층은 `read` < `trade-write` < `admin`이다.
|
||||
- 에러 응답은 HTTP 상태 코드와 함께 원인 메시지를 제공한다.
|
||||
- Base URL 예시는 `http://localhost:8000`을 기준으로 한다.
|
||||
- JSON 예시는 대표 형태이며 일부 선택 필드를 생략할 수 있다.
|
||||
|
||||
## Contract Surface
|
||||
|
||||
### 공개 문서 엔드포인트
|
||||
- `GET /docs`
|
||||
- `GET /redoc`
|
||||
- `GET /openapi.json`
|
||||
|
||||
### 헬스
|
||||
- `GET /health` (인증 없음)
|
||||
- 정상 응답 예시:
|
||||
```json
|
||||
{"status": "ok"}
|
||||
```
|
||||
|
||||
### 인증
|
||||
- `POST /auth/login`
|
||||
- `GET /auth/tokens`
|
||||
- `POST /auth/tokens`
|
||||
- `DELETE /auth/tokens/{token_id}`
|
||||
- `POST /auth/bots`
|
||||
- `GET /auth/bots`
|
||||
- `POST /auth/bots/{bot_id}/tokens`
|
||||
- `GET /auth/bots/{bot_id}/tokens`
|
||||
- `DELETE /auth/bots/{bot_id}/tokens/{token_id}`
|
||||
- `DELETE /auth/bots/{bot_id}`
|
||||
|
||||
### 포트폴리오
|
||||
- `GET /portfolios`
|
||||
- `GET /portfolios/{portfolio_id}`
|
||||
- `POST /portfolios`
|
||||
- `PATCH /portfolios/{portfolio_id}`
|
||||
- `POST /portfolios/{portfolio_id}/buy`
|
||||
- `POST /portfolios/{portfolio_id}/sell`
|
||||
- `POST /portfolios/{portfolio_id}/invest`
|
||||
- `POST /portfolios/{portfolio_id}/transfer`
|
||||
- `POST /portfolios/{portfolio_id}/reset`
|
||||
- `DELETE /portfolios/{portfolio_id}`
|
||||
|
||||
### 포트폴리오 댓글 (`REQ-001`)
|
||||
- `POST /portfolios/{portfolio_id}/comments`
|
||||
- `GET /portfolios/{portfolio_id}/comments`
|
||||
- `PATCH /portfolios/{portfolio_id}/comments/{comment_id}`
|
||||
- `DELETE /portfolios/{portfolio_id}/comments/{comment_id}`
|
||||
|
||||
### 전략셋
|
||||
- `GET /strategy-sets`
|
||||
- `POST /strategy-sets`
|
||||
- `GET /strategy-sets/{strategy_set_id}`
|
||||
- `PATCH /strategy-sets/{strategy_set_id}`
|
||||
- `DELETE /strategy-sets/{strategy_set_id}`
|
||||
|
||||
### 사용자
|
||||
- `GET /users`
|
||||
- `GET /users/{user_id}`
|
||||
- `POST /users`
|
||||
- `PATCH /users/{user_id}`
|
||||
- `DELETE /users/{user_id}`
|
||||
|
||||
## 오류 처리 기준
|
||||
|
||||
| 시나리오 | HTTP |
|
||||
| --- | --- |
|
||||
| 인증 헤더 누락/형식 오류 | `401` |
|
||||
| 유효하지 않은 토큰 | `401` |
|
||||
| 권한 부족 | `403` |
|
||||
| 유효성 검증 실패 | `422` |
|
||||
| 리소스 없음 | `404` |
|
||||
| 내부 예외 | `500` |
|
||||
|
||||
## 상세 엔드포인트 명세
|
||||
|
||||
### 헬스
|
||||
|
||||
#### `GET /health`
|
||||
- 인증 없음
|
||||
- 응답 `200`:
|
||||
|
||||
```json
|
||||
{"status": "ok"}
|
||||
```
|
||||
|
||||
### 포트폴리오
|
||||
|
||||
#### `GET /portfolios`
|
||||
- Auth: `read`
|
||||
- Query:
|
||||
- `broker: string | null`
|
||||
- `status_filter: string | null`
|
||||
- 응답 `200`: `PortfolioListItem[]`
|
||||
|
||||
예시:
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8000/portfolios?broker=upbit&status_filter=enabled" \
|
||||
-H "Authorization: Bearer <READ_TOKEN>"
|
||||
```
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"portfolio_id": "krw-btc",
|
||||
"broker": "upbit",
|
||||
"status": "enabled",
|
||||
"name": "Bitcoin",
|
||||
"symbol": "KRW-BTC",
|
||||
"cash_balance": 1000000.0,
|
||||
"asset_quantity": 0.1,
|
||||
"price": 100000000.0,
|
||||
"profit_buffer": 0.0,
|
||||
"active_strategy_id": "stv-001",
|
||||
"avabile_strategies": ["stv-001"],
|
||||
"created_at": "2026-04-03T00:00:00Z",
|
||||
"updated_at": "2026-04-03T00:00:00Z",
|
||||
"last_trade_at": "2026-04-03T00:00:00Z",
|
||||
"total_value": 11000000.0
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### `GET /portfolios/{portfolio_id}`
|
||||
- Auth: `read`
|
||||
- Query:
|
||||
- `recent_trades: int = 5`
|
||||
- 응답 `200`: `PortfolioDetailResponse`
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8000/portfolios/krw-btc?recent_trades=5" \
|
||||
-H "Authorization: Bearer <READ_TOKEN>"
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"portfolio": {
|
||||
"portfolio_id": "krw-btc",
|
||||
"broker": "upbit",
|
||||
"status": "enabled",
|
||||
"name": "Bitcoin",
|
||||
"symbol": "KRW-BTC",
|
||||
"cash_balance": 1000000.0,
|
||||
"asset_quantity": 0.1,
|
||||
"price": 100000000.0,
|
||||
"profit_buffer": 0.0,
|
||||
"active_strategy_id": "stv-001",
|
||||
"avabile_strategies": ["stv-001"],
|
||||
"created_at": "2026-04-03T00:00:00Z",
|
||||
"updated_at": "2026-04-03T00:00:00Z",
|
||||
"last_trade_at": "2026-04-03T00:00:00Z"
|
||||
},
|
||||
"recent_entries": [
|
||||
{
|
||||
"portfolio_id": "krw-btc",
|
||||
"recorded_at": "2026-04-03T00:00:00Z",
|
||||
"action": "buy",
|
||||
"price": 100000000.0,
|
||||
"quantity": 0.1,
|
||||
"fee_amount": 0.0,
|
||||
"tax_amount": 0.0,
|
||||
"cash_balance_after": 1000000.0,
|
||||
"asset_quantity_after": 0.1,
|
||||
"total_value_after": 11000000.0,
|
||||
"profit_buffer_after": 0.0,
|
||||
"strategy_set_id": "stv-001",
|
||||
"note": ""
|
||||
}
|
||||
],
|
||||
"strategy_set": {
|
||||
"id": "stv-001",
|
||||
"name": "target_value",
|
||||
"config": "{}",
|
||||
"nick": "target_value:99914b932bd3",
|
||||
"created_at": "2026-04-03T00:00:00Z"
|
||||
},
|
||||
"live_quantity": null
|
||||
}
|
||||
```
|
||||
|
||||
#### `POST /portfolios`
|
||||
- Auth: `trade-write`
|
||||
- Body: `CreatePortfolioRequest`
|
||||
- 응답 `201`: `PortfolioDetailResponse`
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/portfolios" \
|
||||
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"portfolio_id": "krw-btc",
|
||||
"broker": "upbit",
|
||||
"status": "enabled",
|
||||
"name": "Bitcoin",
|
||||
"symbol": "KRW-BTC",
|
||||
"strategy_name": "target_value",
|
||||
"strategy_params": {},
|
||||
"active_strategy_id": null,
|
||||
"avabile_strategies": null,
|
||||
"cash_balance": 1000000,
|
||||
"asset_quantity": 0,
|
||||
"asset_price": 0,
|
||||
"profit_buffer": 0
|
||||
}'
|
||||
```
|
||||
|
||||
#### `PATCH /portfolios/{portfolio_id}`
|
||||
- Auth: `trade-write`
|
||||
- Body: `UpdatePortfolioRequest`
|
||||
- 응답 `200`: `PortfolioDetailResponse`
|
||||
|
||||
```bash
|
||||
curl -X PATCH "http://localhost:8000/portfolios/krw-btc" \
|
||||
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"broker": "upbit",
|
||||
"name": "Bitcoin",
|
||||
"symbol": "KRW-BTC"
|
||||
}'
|
||||
```
|
||||
|
||||
#### 거래/자금 이동 엔드포인트
|
||||
- `POST /portfolios/{portfolio_id}/buy`: `BuyRequest` -> `PortfolioDetailResponse`
|
||||
- `POST /portfolios/{portfolio_id}/sell`: `SellRequest` -> `PortfolioDetailResponse`
|
||||
- `POST /portfolios/{portfolio_id}/invest`: `InvestRequest` -> `PortfolioDetailResponse`
|
||||
- `POST /portfolios/{portfolio_id}/transfer`: `TransferRequest` -> `PortfolioDetailResponse`
|
||||
- `POST /portfolios/{portfolio_id}/reset`: `ResetPortfolioRequest` -> `PortfolioDetailResponse`
|
||||
- `DELETE /portfolios/{portfolio_id}`: admin only, query `keep_ledger: bool = false`, 응답 `204`
|
||||
|
||||
대표 요청:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/portfolios/krw-btc/buy" \
|
||||
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"quantity": 0.01, "price": 100000000, "fee": 0, "tax": 0, "note": ""}'
|
||||
|
||||
curl -X POST "http://localhost:8000/portfolios/krw-btc/sell" \
|
||||
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"quantity": 0.01, "price": 100000000, "fee": 0, "tax": 0, "note": ""}'
|
||||
|
||||
curl -X POST "http://localhost:8000/portfolios/krw-btc/invest" \
|
||||
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"movement_type": "deposit", "amount": 50000, "note": ""}'
|
||||
|
||||
curl -X POST "http://localhost:8000/portfolios/krw-btc/transfer" \
|
||||
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"direction": "to-buffer", "amount": 10000, "note": ""}'
|
||||
|
||||
curl -X POST "http://localhost:8000/portfolios/krw-btc/reset" \
|
||||
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"infer_symbol": "KRW-BTC"}'
|
||||
|
||||
curl -X DELETE "http://localhost:8000/portfolios/krw-btc?keep_ledger=false" \
|
||||
-H "Authorization: Bearer <ADMIN_TOKEN>"
|
||||
```
|
||||
|
||||
#### 포트폴리오 댓글 (`REQ-001`)
|
||||
- `POST /portfolios/{portfolio_id}/comments`
|
||||
- Auth: `trade-write`
|
||||
- Body: `CommentCreateRequest`
|
||||
- Response `201`: `CommentResponse`
|
||||
- `GET /portfolios/{portfolio_id}/comments`
|
||||
- Auth: `read`
|
||||
- Response `200`: `CommentResponse[]`
|
||||
- `PATCH /portfolios/{portfolio_id}/comments/{comment_id}`
|
||||
- Auth: `trade-write`
|
||||
- Body: `CommentUpdateRequest`
|
||||
- Response `200`: `CommentResponse`
|
||||
- `DELETE /portfolios/{portfolio_id}/comments/{comment_id}`
|
||||
- Auth: `trade-write`
|
||||
- Response `204`
|
||||
|
||||
예시:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/portfolios/krw-btc/comments" \
|
||||
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"content": "Bought more BTC today.\nLooking good."}'
|
||||
|
||||
curl -X GET "http://localhost:8000/portfolios/krw-btc/comments" \
|
||||
-H "Authorization: Bearer <READ_TOKEN>"
|
||||
|
||||
curl -X PATCH "http://localhost:8000/portfolios/krw-btc/comments/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
|
||||
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"content": "Updated note: BTC rally continues."}'
|
||||
|
||||
curl -X DELETE "http://localhost:8000/portfolios/krw-btc/comments/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
|
||||
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>"
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"portfolio_id": "krw-btc",
|
||||
"content": "Bought more BTC today.\nLooking good.",
|
||||
"created_at": "2026-04-10T00:00:00Z",
|
||||
"updated_at": "2026-04-10T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 전략셋
|
||||
|
||||
#### `GET /strategy-sets`
|
||||
- Auth: `read`
|
||||
- Response `200`: `StrategySetResponse[]`
|
||||
|
||||
#### `POST /strategy-sets`
|
||||
- Auth: `trade-write`
|
||||
- Body: `CreateStrategySetRequest`
|
||||
- Response `201`: `StrategySetResponse`
|
||||
- 기존 `(name, config)` 쌍이 있으면 기존 strategy set 반환
|
||||
|
||||
#### `PATCH /strategy-sets/{strategy_set_id}`
|
||||
- Auth: `trade-write`
|
||||
- Body: `UpdateStrategySetRequest`
|
||||
- Response `200`: `StrategySetResponse`
|
||||
|
||||
#### `DELETE /strategy-sets/{strategy_set_id}`
|
||||
- Auth: `trade-write`
|
||||
- Response `204`
|
||||
- in use 상태면 `409`
|
||||
|
||||
대표 요청:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/strategy-sets" \
|
||||
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "target_value", "config": "{}"}'
|
||||
|
||||
curl -X PATCH "http://localhost:8000/strategy-sets/stv-001" \
|
||||
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"config": "{\"rebalance\": true}"}'
|
||||
|
||||
curl -X DELETE "http://localhost:8000/strategy-sets/stv-001" \
|
||||
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>"
|
||||
```
|
||||
|
||||
### 인증
|
||||
|
||||
#### `POST /auth/login`
|
||||
- Auth 없음
|
||||
- Body: `LoginRequest`
|
||||
- Response `200`: `TokenResponse`
|
||||
- 잘못된 username/password면 `401`
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/auth/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"username": "admin",
|
||||
"password": "bootstrap123"
|
||||
}'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "plain-token-value",
|
||||
"token_type": "bearer",
|
||||
"expires_at": "2026-04-03T12:00:00Z",
|
||||
"permission": "admin",
|
||||
"user": {
|
||||
"id": "user-1",
|
||||
"username": "admin",
|
||||
"email": "admin@example.com",
|
||||
"permission": "admin",
|
||||
"is_active": true,
|
||||
"created_at": "2026-04-03T00:00:00Z",
|
||||
"updated_at": "2026-04-03T00:00:00Z",
|
||||
"last_login_at": "2026-04-03T00:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `GET /auth/tokens`
|
||||
- Auth: DB-issued `read` 이상 토큰
|
||||
- Response `200`: `AuthTokenResponse[]`
|
||||
|
||||
#### `POST /auth/tokens`
|
||||
- Auth: DB-issued `read` 이상 토큰
|
||||
- Body: `CreateClientTokenRequest`
|
||||
- Response `201`: `IssuedClientTokenResponse`
|
||||
- 요청 permission이 현재 permission을 넘으면 `403`
|
||||
|
||||
#### `DELETE /auth/tokens/{token_id}`
|
||||
- Auth: DB-issued `read` 이상 토큰
|
||||
- Response `204`
|
||||
- 미존재 시 `404`
|
||||
|
||||
대표 요청:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/auth/tokens" \
|
||||
-H "Authorization: Bearer <SESSION_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "cli", "permission": "trade-write", "expires_in_days": 30}'
|
||||
```
|
||||
|
||||
#### 봇 주체와 봇 토큰
|
||||
- `POST /auth/bots`: admin only, `CreateBotPrincipalRequest` -> `BotPrincipalResponse`
|
||||
- `GET /auth/bots`: admin only, `is_active` 필터 지원
|
||||
- `GET /auth/bots/{bot_id}`: admin only
|
||||
- `DELETE /auth/bots/{bot_id}`: admin only, bot 비활성화 및 연결 토큰 revoke, 응답 `200`
|
||||
- `POST /auth/bots/{bot_id}/tokens`: admin only, `CreateBotTokenRequest` -> `IssuedBotTokenResponse`
|
||||
- `GET /auth/bots/{bot_id}/tokens`: admin only, `AuthTokenResponse[]`
|
||||
- `DELETE /auth/bots/{bot_id}/tokens/{token_id}`: admin only, `204`
|
||||
|
||||
대표 요청:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/auth/bots" \
|
||||
-H "Authorization: Bearer <ADMIN_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "rebalance-bot", "permission": "trade-write", "description": "nightly jobs"}'
|
||||
|
||||
curl -X POST "http://localhost:8000/auth/bots/bot-1/tokens" \
|
||||
-H "Authorization: Bearer <ADMIN_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "nightly", "permission": "trade-write", "expires_in_days": 90}'
|
||||
```
|
||||
|
||||
### 사용자
|
||||
|
||||
모든 `/users` 엔드포인트는 admin only이다.
|
||||
|
||||
- `GET /users`: `UserResponse[]`
|
||||
- `GET /users/{user_id}`: `UserResponse`, 미존재 시 `404`
|
||||
- `POST /users`: `CreateUserRequest` -> `UserResponse`, duplicate username/email이면 `409`
|
||||
- `PATCH /users/{user_id}`: `UpdateUserRequest` -> `UserResponse`, duplicate면 `409`
|
||||
- `DELETE /users/{user_id}`: `204`
|
||||
|
||||
bootstrap 규칙:
|
||||
|
||||
- DB persistence가 활성이고 `PORTFOLIOHUB_BOOTSTRAP_USERNAME`, `PORTFOLIOHUB_BOOTSTRAP_PASSWORD`가 설정되면, `users` 테이블이 비어 있을 때만 startup에서 초기 admin user를 생성한다.
|
||||
|
||||
대표 요청:
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8000/users" \
|
||||
-H "Authorization: Bearer <ADMIN_TOKEN>"
|
||||
|
||||
curl -X POST "http://localhost:8000/users" \
|
||||
-H "Authorization: Bearer <ADMIN_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"username": "alice",
|
||||
"email": "alice@example.com",
|
||||
"password": "password123",
|
||||
"permission": "read",
|
||||
"is_active": true
|
||||
}'
|
||||
```
|
||||
|
||||
## 요청/응답 모델 요약
|
||||
|
||||
### Request Models
|
||||
|
||||
`CreatePortfolioRequest`
|
||||
|
||||
- `portfolio_id: str`
|
||||
- `broker: str = "kis"`
|
||||
- `status: str = "enabled"`
|
||||
- `name: str | null`
|
||||
- `symbol: str`
|
||||
- `strategy_name: str = "target_value"`
|
||||
- `strategy_params: object = {}`
|
||||
- `active_strategy_id: str | null`
|
||||
- `avabile_strategies: string[] | null`
|
||||
- `cash_balance: float >= 0`
|
||||
- `asset_quantity: float >= 0`
|
||||
- `asset_price: float >= 0`
|
||||
- `profit_buffer: float >= 0`
|
||||
|
||||
`UpdatePortfolioRequest`
|
||||
|
||||
- `broker: str | null`
|
||||
- `status: str | null`
|
||||
- `name: str | null`
|
||||
- `symbol: str | null`
|
||||
- `strategy_name: str | null`
|
||||
- `strategy_params: object | null`
|
||||
- `active_strategy_id: str | null`
|
||||
- `avabile_strategies: string[] | null`
|
||||
- `asset_price: float >= 0 | null`
|
||||
|
||||
`BuyRequest` / `SellRequest`
|
||||
|
||||
- `quantity: float > 0`
|
||||
|
||||
`InvestRequest`
|
||||
|
||||
- `movement_type: "deposit" | "profit" | "withdraw"`
|
||||
- `amount: float > 0`
|
||||
- `note: str = ""`
|
||||
|
||||
`TransferRequest`
|
||||
|
||||
- `direction: "to-buffer" | "from-buffer"`
|
||||
- `amount: float > 0`
|
||||
- `note: str = ""`
|
||||
|
||||
`ResetPortfolioRequest`
|
||||
|
||||
- `infer_symbol: str | null`
|
||||
|
||||
`CommentCreateRequest` / `CommentUpdateRequest`
|
||||
|
||||
- `content: str`
|
||||
|
||||
`LoginRequest`
|
||||
|
||||
- `username: str`
|
||||
- `password: str`
|
||||
|
||||
`CreateClientTokenRequest`
|
||||
|
||||
- `name: str`
|
||||
- `permission: "read" | "trade-write" | "admin" | null`
|
||||
- `expires_in_days: int | null`
|
||||
|
||||
`CreateBotPrincipalRequest`
|
||||
|
||||
- `name: str`
|
||||
- `permission: "read" | "trade-write" | "admin"`
|
||||
- `description: str | null`
|
||||
|
||||
`CreateBotTokenRequest`
|
||||
|
||||
- `name: str`
|
||||
- `permission: "read" | "trade-write" | "admin" | null`
|
||||
- `expires_in_days: int | null`
|
||||
|
||||
`CreateUserRequest`
|
||||
|
||||
- `username: str`
|
||||
- `email: str | null`
|
||||
- `password: str`
|
||||
- `permission: "read" | "trade-write" | "admin"`
|
||||
- `is_active: bool`
|
||||
|
||||
`UpdateUserRequest`
|
||||
|
||||
- `username: str | null`
|
||||
- `email: str | null`
|
||||
- `password: str | null`
|
||||
- `permission: "read" | "trade-write" | "admin" | null`
|
||||
- `is_active: bool | null`
|
||||
|
||||
`CreateStrategySetRequest`
|
||||
|
||||
- `name: str`
|
||||
- `config: str`
|
||||
|
||||
`UpdateStrategySetRequest`
|
||||
|
||||
- `name: str | null`
|
||||
- `config: str | null`
|
||||
|
||||
### Response Models
|
||||
|
||||
`PortfolioListItem`
|
||||
|
||||
- portfolio summary fields + computed `total_value`
|
||||
|
||||
`PortfolioSummary`
|
||||
|
||||
- `portfolio_id: str`
|
||||
- `broker: str`
|
||||
- `status: str`
|
||||
- `name: str`
|
||||
- `symbol: str`
|
||||
- `cash_balance: float`
|
||||
- `asset_quantity: float`
|
||||
- `price: float`
|
||||
- `profit_buffer: float`
|
||||
- `active_strategy_id: str | null`
|
||||
- `avabile_strategies: string[] | null`
|
||||
- `created_at: datetime | null`
|
||||
- `updated_at: datetime | null`
|
||||
- `last_trade_at: datetime | null`
|
||||
|
||||
`PortfolioDetailResponse`
|
||||
|
||||
- `portfolio: PortfolioSummary`
|
||||
- `recent_entries: LedgerEntryResponse[]`
|
||||
- `strategy_set: StrategySetResponse | null`
|
||||
- `live_quantity: float | null`
|
||||
|
||||
`StrategySetResponse`
|
||||
|
||||
- `id: str`
|
||||
- `name: str`
|
||||
- `config: str`
|
||||
- `nick: str`
|
||||
- `created_at: datetime | null`
|
||||
|
||||
`CommentResponse`
|
||||
|
||||
- `id: str`
|
||||
- `portfolio_id: str`
|
||||
- `content: str`
|
||||
- `created_at: datetime | null`
|
||||
- `updated_at: datetime | null`
|
||||
|
||||
`BotPrincipalResponse`
|
||||
|
||||
- `id: str`
|
||||
- `name: str`
|
||||
- `permission: "read" | "trade-write" | "admin"`
|
||||
- `description: str | null`
|
||||
- `is_active: bool`
|
||||
- `created_at: datetime | null`
|
||||
- `updated_at: datetime | null`
|
||||
- `deactivated_at: datetime | null`
|
||||
|
||||
`ErrorResponse`
|
||||
|
||||
- `detail: str`
|
||||
|
||||
## 공통 오류 예시
|
||||
|
||||
### Missing token
|
||||
|
||||
```json
|
||||
{"detail": "Not authenticated"}
|
||||
```
|
||||
|
||||
### Invalid token
|
||||
|
||||
```json
|
||||
{"detail": "Invalid token"}
|
||||
```
|
||||
|
||||
### Insufficient permission
|
||||
|
||||
```json
|
||||
{"detail": "Insufficient permission"}
|
||||
```
|
||||
|
||||
## 문서 동기화 규칙
|
||||
- `app/api/` 또는 `app/schemas/` 변경 시 본 문서를 같은 변경 단위에서 갱신한다.
|
||||
- `REQ-002` 태그 검색 API가 구현되면 요청/응답 파라미터와 필터 의미를 본 문서에 추가한다.
|
||||
|
||||
## Open Implementation Points
|
||||
- 에러 응답 body를 공통 포맷(`error_code`, `message`, `details`)으로 강제할지 결정한다.
|
||||
- idempotency가 필요한 쓰기 API(예: 재시도 가능 매매 요청)의 키 정책을 확정한다.
|
||||
Reference in New Issue
Block a user