Files
portfolio-hub/docs/api-examples.md
T

11 KiB

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 <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",
    "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",
    "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",
    "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",
    "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.

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",
    "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.

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"
}