# PortfolioHub API Examples ## 1. Purpose This document contains example requests and responses for PortfolioHub HTTP APIs. - Sync rule: when files under `app/api/` or `app/schemas/` change, this document must be updated in the same commit/PR. - Companion spec: `docs/portfoliohub-spec.md` ## 2. Conventions - Base URL example: `http://localhost:8000` - Auth header format: `Authorization: Bearer ` - JSON examples are representative and may omit some optional fields. ## 3. Health ### 3.1 GET /health Request: ```bash curl -X GET "http://localhost:8000/health" ``` Response 200: ```json { "status": "ok" } ``` ## 4. Portfolios ### 4.1 GET /portfolios Request: ```bash curl -X GET "http://localhost:8000/portfolios?broker=upbit&status_filter=enabled" \ -H "Authorization: Bearer " ``` Response 200: ```json [ { "portfolio_id": "krw-btc", "broker": "upbit", "status": "enabled", "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 } ] ``` ### 4.2 GET /portfolios/{portfolio_id} Request: ```bash curl -X GET "http://localhost:8000/portfolios/krw-btc?recent_trades=5" \ -H "Authorization: Bearer " ``` Response 200: ```json { "portfolio": { "portfolio_id": "krw-btc", "broker": "upbit", "status": "enabled", "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 } ``` ### 4.3 POST /portfolios Request: ```bash curl -X POST "http://localhost:8000/portfolios" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "portfolio_id": "krw-btc", "broker": "upbit", "status": "enabled", "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 }' ``` Response 201: - Response body shape is `PortfolioDetailResponse` (same as 4.2). ### 4.4 PATCH /portfolios/{portfolio_id} Request: ```bash curl -X PATCH "http://localhost:8000/portfolios/krw-btc" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "broker": "upbit", "symbol": "KRW-BTC" }' ``` Response 200: - Response body shape is `PortfolioDetailResponse`. ### 4.5 POST /portfolios/{portfolio_id}/buy Request: ```bash curl -X POST "http://localhost:8000/portfolios/krw-btc/buy" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"quantity": 0.01, "price": 100000000, "fee": 0, "tax": 0, "note": ""}' ``` Response 200: - Response body shape is `PortfolioDetailResponse`. ### 4.6 POST /portfolios/{portfolio_id}/sell Request: ```bash curl -X POST "http://localhost:8000/portfolios/krw-btc/sell" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"quantity": 0.01, "price": 100000000, "fee": 0, "tax": 0, "note": ""}' ``` Response 200: - Response body shape is `PortfolioDetailResponse`. ### 4.7 POST /portfolios/{portfolio_id}/invest Request: ```bash curl -X POST "http://localhost:8000/portfolios/krw-btc/invest" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"movement_type": "deposit", "amount": 50000, "note": ""}' ``` Response 200: - Response body shape is `PortfolioDetailResponse`. ### 4.8 POST /portfolios/{portfolio_id}/transfer Request: ```bash curl -X POST "http://localhost:8000/portfolios/krw-btc/transfer" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"direction": "to-buffer", "amount": 10000, "note": ""}' ``` Response 200: - Response body shape is `PortfolioDetailResponse`. ### 4.9 POST /portfolios/{portfolio_id}/reset Request: ```bash curl -X POST "http://localhost:8000/portfolios/krw-btc/reset" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"infer_symbol": "KRW-BTC"}' ``` Response 200: - Response body shape is `PortfolioDetailResponse`. ### 4.10 DELETE /portfolios/{portfolio_id} Request: ```bash curl -X DELETE "http://localhost:8000/portfolios/krw-btc?keep_ledger=false" \ -H "Authorization: Bearer " ``` Response 204: - No content. ## 5. Strategy Sets ### 5.1 GET /strategy-sets ## 6. Auth ### 6.1 POST /auth/login Request: ```bash curl -X POST "http://localhost:8000/auth/login" \ -H "Content-Type: application/json" \ -d '{ "username": "admin", "password": "bootstrap123" }' ``` Response 200: ```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" } } ``` ### 6.2 POST /auth/tokens Request: ```bash curl -X POST "http://localhost:8000/auth/tokens" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "name": "mobile-app", "permission": "read", "expires_in_days": 30 }' ``` Response 201: ```json { "token": "plain-client-token", "token_type": "bearer", "auth_token": { "id": "token-1", "user_id": "user-1", "name": "mobile-app", "token_prefix": "abcd1234", "permission": "read", "token_type": "client", "expires_at": "2026-05-03T00:00:00Z", "last_used_at": null, "revoked_at": null, "created_at": "2026-04-03T00:00:00Z" } } ``` ### 6.3 GET /auth/tokens Request: ```bash curl -X GET "http://localhost:8000/auth/tokens" \ -H "Authorization: Bearer " ``` ### 6.4 DELETE /auth/tokens/{token_id} Request: ```bash curl -X DELETE "http://localhost:8000/auth/tokens/token-1" \ -H "Authorization: Bearer " ``` Response 204: - No content. ## 7. Users All user management endpoints require an admin token. ### 7.1 GET /users Request: ```bash curl -X GET "http://localhost:8000/users" \ -H "Authorization: Bearer " ``` Response 200: ```json [ { "id": "user-1", "username": "alice", "email": "alice@example.com", "created_at": "2026-04-03T00:00:00Z", "updated_at": "2026-04-03T00:00:00Z" } ] ``` ### 7.2 GET /users/{user_id} Request: ```bash curl -X GET "http://localhost:8000/users/user-1" \ -H "Authorization: Bearer " ``` Response 200: ```json { "id": "user-1", "username": "alice", "email": "alice@example.com", "created_at": "2026-04-03T00:00:00Z", "updated_at": "2026-04-03T00:00:00Z" } ``` ### 7.3 POST /users Request: ```bash curl -X POST "http://localhost:8000/users" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "username": "alice", "email": "alice@example.com", "password": "password123", "permission": "read", "is_active": true }' ``` Response 201: ```json { "id": "user-1", "username": "alice", "email": "alice@example.com", "permission": "read", "is_active": true, "created_at": "2026-04-03T00:00:00Z", "updated_at": "2026-04-03T00:00:00Z", "last_login_at": null } ``` ### 7.4 PATCH /users/{user_id} Request: ```bash curl -X PATCH "http://localhost:8000/users/user-1" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "username": "alice-updated" }' ``` Response 200: ```json { "id": "user-1", "username": "alice-updated", "email": "alice@example.com", "permission": "read", "is_active": true, "created_at": "2026-04-03T00:00:00Z", "updated_at": "2026-04-03T00:10:00Z", "last_login_at": null } ``` ### 7.5 DELETE /users/{user_id} Request: ```bash curl -X DELETE "http://localhost:8000/users/user-1" \ -H "Authorization: Bearer " ``` Response 204: - No content. Request: ```bash curl -X GET "http://localhost:8000/strategy-sets" \ -H "Authorization: Bearer " ``` Response 200: ```json [ { "id": "set-1", "name": "target_value", "config": "{\"target\": 1000}", "nick": "target_value:abc123def456", "created_at": "2026-04-03T08:13:08.318Z" }, { "id": "set-2", "name": "momentum", "config": "{\"window\": 20}", "nick": "momentum:xyz789uvw012", "created_at": "2026-04-03T08:14:15.450Z" } ] ``` ### 5.2 POST /strategy-sets Request: ```bash curl -X POST "http://localhost:8000/strategy-sets" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "name": "target_value", "config": "{\"target\": 1000}" }' ``` Response 201: ```json { "id": "set-1", "name": "target_value", "config": "{\"target\": 1000}", "nick": "target_value:abc123def456", "created_at": "2026-04-03T08:13:08.318Z" } ``` ### 5.3 PATCH /strategy-sets/{strategy_set_id} Request: ```bash curl -X PATCH "http://localhost:8000/strategy-sets/set-1" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "config": "{\"target\": 2000}" }' ``` Response 200: ```json { "id": "set-1-updated", "name": "target_value", "config": "{\"target\": 2000}", "nick": "target_value:def789ghi012", "created_at": "2026-04-03T08:13:08.318Z" } ``` Note: Since strategy sets are identified by (name, config) uniqueness, updating may result in a new strategy set ID. ### 5.4 DELETE /strategy-sets/{strategy_set_id} Request: ```bash curl -X DELETE "http://localhost:8000/strategy-sets/set-1" \ -H "Authorization: Bearer " ``` Response 204: - No content on success. Response 409 Conflict (if strategy set is in use): ```json { "detail": "Strategy set is in use by 1 portfolio(s) and cannot be deleted" } ``` ## 6. Common Error Examples ### 6.1 Missing token Response 401: ```json { "detail": "Missing bearer token" } ``` ### 6.2 Invalid token Response 401: ```json { "detail": "Invalid token" } ``` ### 6.3 Insufficient permission Response 403: ```json { "detail": "Insufficient permission" } ```