Agent-Logs-Url: https://github.com/genichin/portfolio-hub/sessions/64fc2cec-78f5-41d1-99a4-45f868781edd Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
16 KiB
PortfolioHub API Examples
1. Purpose
This document contains example requests and responses for PortfolioHub HTTP APIs.
- Sync rule: when files under
app/api/orapp/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 <TOKEN> - JSON examples are representative and may omit some optional fields.
3. Health
3.1 GET /health
Request:
curl -X GET "http://localhost:8000/health"
Response 200:
{
"status": "ok"
}
4. Portfolios
4.1 GET /portfolios
Request:
curl -X GET "http://localhost:8000/portfolios?broker=upbit&status_filter=enabled" \
-H "Authorization: Bearer <READ_TOKEN>"
Response 200:
[
{
"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
}
]
4.2 GET /portfolios/{portfolio_id}
Request:
curl -X GET "http://localhost:8000/portfolios/krw-btc?recent_trades=5" \
-H "Authorization: Bearer <READ_TOKEN>"
Response 200:
{
"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
}
4.3 POST /portfolios
Request:
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
}'
Response 201:
- Response body shape is
PortfolioDetailResponse(same as 4.2).
4.4 PATCH /portfolios/{portfolio_id}
Request:
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"
}'
Response 200:
- Response body shape is
PortfolioDetailResponse.
4.5 POST /portfolios/{portfolio_id}/buy
Request:
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": ""}'
Response 200:
- Response body shape is
PortfolioDetailResponse.
4.6 POST /portfolios/{portfolio_id}/sell
Request:
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": ""}'
Response 200:
- Response body shape is
PortfolioDetailResponse.
4.7 POST /portfolios/{portfolio_id}/invest
Request:
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": ""}'
Response 200:
- Response body shape is
PortfolioDetailResponse.
4.8 POST /portfolios/{portfolio_id}/transfer
Request:
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": ""}'
Response 200:
- Response body shape is
PortfolioDetailResponse.
4.9 POST /portfolios/{portfolio_id}/reset
Request:
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"}'
Response 200:
- Response body shape is
PortfolioDetailResponse.
4.10 DELETE /portfolios/{portfolio_id}
Request:
curl -X DELETE "http://localhost:8000/portfolios/krw-btc?keep_ledger=false" \
-H "Authorization: Bearer <ADMIN_TOKEN>"
Response 204:
- No content.
4.11 POST /portfolios/{portfolio_id}/comments
Create a new comment for a portfolio.
Required permission: trade-write
Request:
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."}'
Response 201:
{
"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"
}
4.12 GET /portfolios/{portfolio_id}/comments
List all comments for a portfolio, sorted by most recent first.
Required permission: read
Request:
curl -X GET "http://localhost:8000/portfolios/krw-btc/comments" \
-H "Authorization: Bearer <READ_TOKEN>"
Response 200:
[
{
"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"
}
]
4.13 PATCH /portfolios/{portfolio_id}/comments/{comment_id}
Update an existing comment's content.
Required permission: trade-write
Request:
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."}'
Response 200:
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"portfolio_id": "krw-btc",
"content": "Updated note: BTC rally continues.",
"created_at": "2026-04-10T00:00:00Z",
"updated_at": "2026-04-10T01:00:00Z"
}
4.14 DELETE /portfolios/{portfolio_id}/comments/{comment_id}
Delete a comment.
Required permission: trade-write
Request:
curl -X DELETE "http://localhost:8000/portfolios/krw-btc/comments/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>"
Response 204:
- No content.
5. Strategy Sets
5.1 GET /strategy-sets
6. Auth
6.1 POST /auth/login
Request:
curl -X POST "http://localhost:8000/auth/login" \
-H "Content-Type: application/json" \
-d '{
"username": "admin",
"password": "bootstrap123"
}'
Response 200:
{
"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:
curl -X POST "http://localhost:8000/auth/tokens" \
-H "Authorization: Bearer <SESSION_OR_DB_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"name": "mobile-app",
"permission": "read",
"expires_in_days": 30
}'
Response 201:
{
"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",
"bot_principal_id": null,
"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:
curl -X GET "http://localhost:8000/auth/tokens" \
-H "Authorization: Bearer <SESSION_OR_DB_TOKEN>"
6.4 DELETE /auth/tokens/{token_id}
Request:
curl -X DELETE "http://localhost:8000/auth/tokens/token-1" \
-H "Authorization: Bearer <SESSION_OR_DB_TOKEN>"
Response 204:
- No content.
6.5 POST /auth/bots
Request:
curl -X POST "http://localhost:8000/auth/bots" \
-H "Authorization: Bearer <ADMIN_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"name": "market-bot",
"permission": "trade-write",
"description": "External trading bot"
}'
Response 201:
{
"id": "bot-1",
"name": "market-bot",
"permission": "trade-write",
"description": "External trading bot",
"is_active": true,
"created_at": "2026-04-03T00:00:00Z",
"updated_at": "2026-04-03T00:00:00Z",
"deactivated_at": null
}
6.6 GET /auth/bots
Request:
curl -X GET "http://localhost:8000/auth/bots?is_active=true" \
-H "Authorization: Bearer <ADMIN_TOKEN>"
6.7 POST /auth/bots/{bot_id}/tokens
Request:
curl -X POST "http://localhost:8000/auth/bots/bot-1/tokens" \
-H "Authorization: Bearer <ADMIN_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"name": "prod-key",
"permission": "read",
"expires_in_days": 30
}'
Response 201:
{
"token": "plain-bot-token",
"token_type": "bearer",
"auth_token": {
"id": "token-2",
"user_id": "user-1",
"name": "prod-key",
"token_prefix": "efgh5678",
"permission": "read",
"token_type": "bot",
"bot_principal_id": "bot-1",
"expires_at": "2026-05-03T00:00:00Z",
"last_used_at": null,
"revoked_at": null,
"created_at": "2026-04-03T00:00:00Z"
}
}
6.8 GET /auth/bots/{bot_id}/tokens
Request:
curl -X GET "http://localhost:8000/auth/bots/bot-1/tokens" \
-H "Authorization: Bearer <ADMIN_TOKEN>"
6.9 DELETE /auth/bots/{bot_id}/tokens/{token_id}
Request:
curl -X DELETE "http://localhost:8000/auth/bots/bot-1/tokens/token-2" \
-H "Authorization: Bearer <ADMIN_TOKEN>"
Response 204:
- No content.
6.10 DELETE /auth/bots/{bot_id}
Request:
curl -X DELETE "http://localhost:8000/auth/bots/bot-1" \
-H "Authorization: Bearer <ADMIN_TOKEN>"
Response 200:
{
"id": "bot-1",
"name": "market-bot",
"permission": "trade-write",
"description": "External trading bot",
"is_active": false,
"created_at": "2026-04-03T00:00:00Z",
"updated_at": "2026-04-03T01:00:00Z",
"deactivated_at": "2026-04-03T01:00:00Z"
}
7. Users
All user management endpoints require an admin token.
7.1 GET /users
Request:
curl -X GET "http://localhost:8000/users" \
-H "Authorization: Bearer <ADMIN_TOKEN>"
Response 200:
[
{
"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:
curl -X GET "http://localhost:8000/users/user-1" \
-H "Authorization: Bearer <ADMIN_TOKEN>"
Response 200:
{
"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:
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
}'
Response 201:
{
"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:
curl -X PATCH "http://localhost:8000/users/user-1" \
-H "Authorization: Bearer <ADMIN_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"username": "alice-updated"
}'
Response 200:
{
"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:
curl -X DELETE "http://localhost:8000/users/user-1" \
-H "Authorization: Bearer <ADMIN_TOKEN>"
Response 204:
- No content.
Request:
curl -X GET "http://localhost:8000/strategy-sets" \
-H "Authorization: Bearer <READ_TOKEN>"
Response 200:
[
{
"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:
curl -X POST "http://localhost:8000/strategy-sets" \
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"name": "target_value",
"config": "{\"target\": 1000}"
}'
Response 201:
{
"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:
curl -X PATCH "http://localhost:8000/strategy-sets/set-1" \
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"config": "{\"target\": 2000}"
}'
Response 200:
{
"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:
curl -X DELETE "http://localhost:8000/strategy-sets/set-1" \
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>"
Response 204:
- No content on success.
Response 409 Conflict (if strategy set is in use):
{
"detail": "Strategy set is in use by 1 portfolio(s) and cannot be deleted"
}
6. Common Error Examples
6.1 Missing token
Response 401:
{
"detail": "Missing bearer token"
}
6.2 Invalid token
Response 401:
{
"detail": "Invalid token"
}
6.3 Insufficient permission
Response 403:
{
"detail": "Insufficient permission"
}