# Portfolio Hub Interface Contract - Baseline Mode: observed - Baseline Seed: .stagepilot/bootstrap/baseline.yaml - Status: confirmed - Owner: repository-maintainers - Last Updated (KST): 2026-05-11 00:00 - Source Discovery / Batch: bootstrap-baseline, bat-001, bat-006, bat-008 - Primary Runtime: mixed ## Project Summary - PortfolioHub exposes portfolio management and trading ledger workflows through a FastAPI service and a companion CLI. ## Primary Domain - Portfolio management and trading ledger services. ## Purpose - This document captures the approved external and cross-boundary interface contracts for the project. - It is a cross-cutting reference for REQ drafting, batch design, implementation, verification, and handoff. ## Scope - Covers externally visible APIs, CLI surfaces, events, files, or batch I/O contracts. - Does not duplicate full business requirements already owned by REQ documents. - Does not replace low-level code references or generated API artifacts. ## Interface Summary - Primary HTTP API :: manage authentication, portfolios, strategy sets, and users over JSON HTTP endpoints - PortfolioHub CLI :: provide command-line access to health checks, database setup, users, portfolios, strategies, and service tokens ## Interface: `Primary HTTP API` - Type: http-api - Consumers / Producers: authenticated clients, operators, and downstream services - Purpose: manage authentication, portfolios, strategy sets, and users over JSON HTTP endpoints - Stability: internal ### Inputs - Authorization: Bearer :: required for protected routes and mapped to read, trade-write, or admin permissions - JSON request bodies :: carry login, token issuance, user CRUD, strategy-set CRUD, portfolio mutations, comments, and tags - Route and query parameters :: select users, tokens, portfolios, filters, recent history windows, comments, and tags ### Outputs - JSON response models :: responses are shaped by app/schemas/auth.py, app/schemas/user.py, and app/schemas/portfolio.py - HTTP 201, 200, and 204 status codes :: create, read or update, and delete semantics respectively - GET /health payload :: returns `{"status": "ok", "service": "portfolio-hub", "version": "", "checked_at": ""}` for readiness checks ### Error Contract - 401 Unauthorized :: invalid credentials, invalid token, or missing token on protected routes - 403 Forbidden :: insufficient permission or token issuance policy violation - 404 Not Found :: missing user, token, portfolio, comment, tag, or strategy set - 422 Unprocessable Entity :: FastAPI or Pydantic request validation failure ### Compatibility Rules - Route prefixes /health, /auth, /users, /strategy-sets, and /portfolios define the current internal API surface. - Permission semantics read, trade-write, and admin must stay aligned across auth guards and token metadata. - Breaking route or schema changes require REQ and Batch documentation updates before release. ## Interface: `PortfolioHub CLI` - Type: cli - Consumers / Producers: operators and automation - Purpose: provide command-line access to health checks, database setup, users, portfolios, strategies, and service tokens - Stability: internal ### Inputs - pfhub subcommands :: include user, portfolio, strategy, service-token, health-check, init-db, and migrate-db command families - Flags and options :: include --format table|json, --force confirmations, and operation-specific mutation arguments - Environment configuration :: app/core/config.py resolves PORTFOLIOHUB_* and TIMESCALEDB_* values before command execution - Portfolio identification (bat-006) :: all portfolio mutation commands accept `` (positional) + `--user ` instead of raw portfolio_id; `comment` and `tag` sub-commands still accept portfolio_id (UUID) directly ### Outputs - Table or JSON console rendering :: read flows emit structured operator output suitable for manual review or automation - One-time token reveal :: issued client or service tokens are shown once and then only masked metadata remains - Process exit status :: command success or failure is communicated through standard CLI exit behavior ### Error Contract - Typer validation errors :: invalid arguments or missing required options stop the command before execution - Confirmation refusal :: write commands abort unless the operator confirms or passes --force - Persistence and auth failures :: disabled DB mode, connectivity issues, or permission problems surface as command errors ### Compatibility Rules - Command names and primary flags should remain stable for internal automation. - Write commands require explicit confirmation unless --force is present. - JSON output should remain machine-readable across additive command evolution. ## Interface: `v1 Consumer API (trex-basic)` - Type: http-api - Consumers / Producers: trex-basic service (Consumer-Driven Contract, dcy-002) - Purpose: provide asset listing, user info, and trade ledger recording for the trex-basic trading daemon - Stability: internal (versioned under /v1) ### Authentication - All /v1 endpoints require `Authorization: Bearer ` header. - Token must be a DB-issued service token (`token_source="db"`, `token_type="service"`); env tokens are rejected with HTTP 403. - POST /v1/assets/{asset_id}/ledger/trades additionally requires `trade-write` permission. ### Endpoint: `GET /v1/assets` - Auth: service-token bearer (read) - Query Params: `?status=` (optional, filters by portfolio status) - Response: HTTP 200 ``` { "total": , "assets": [ { "asset_id": "", // portfolio.portfolio_id (UUID after BAT-006) "symbol": "", "market": "", "status": "", "cash": , "quantity": , "broker_type": "", "strategy_id": "", "strategy_type": "", "strategy_params": {}, "updated_at": "", "name": "", "price": , "profit_buffer": , "tags": []|null, "owner_user_id": "", "created_at": "", "last_trade_at": "" } ] } ``` - Error: HTTP 401 if no token; HTTP 403 if not a service token ### Endpoint: `GET /v1/user_info` - Auth: service-token bearer (read); token must have `runtime_id` assigned - Response: HTTP 200 ``` { "runtime_id": "", "trading_enabled": , // mapped from users.is_active "disabled_reason": "", // "account_inactive" when trading_enabled=false "brokers": [ { "broker": "", "account_ref": "", "credential_payload": {}|null, // Fernet-decrypted {app_key, app_secret} "credential_version": , "last_rotated_at": "" } ], "user_id": "", "username": "", "email": "", "permission": "" } ``` - Error: HTTP 401 if no token; HTTP 403 if not a service token; HTTP 404 if token has no runtime_id ### Endpoint: `POST /v1/assets/{asset_id}/ledger/trades` - Auth: service-token bearer (trade-write) - Path Param: `asset_id` — must be a valid UUID portfolio_id - Request Body: ``` { "order_id": "", // required, min_length=1 "execution_id": "", // required, min_length=1 "side": "buy"|"sell", "quantity": , // > 0 "price": , // > 0 "fee_amount": , // >= 0, default 0 "tax_amount": , // >= 0, default 0 "executed_at": "" } ``` - Response HTTP 201 (new record): ``` { "ledger_entry_id": , "asset_id": "", "order_id": "", "execution_id": "", "side": "", "quantity": , "price": , "fee_amount": , "tax_amount": , "executed_at": "", "recorded_at": "", "status": "new" } ``` - Response HTTP 200 (idempotent duplicate with identical payload): same body with `"status": "duplicate"` - Error: HTTP 401 if no token; HTTP 403 if not service token or insufficient permission; HTTP 404 if asset_id not found; HTTP 409 if duplicate key with different payload ### Idempotency Contract - Deduplication key: `(asset_id, order_id, execution_id)` — enforced by `uq_ledger_trades_idempotency` DB unique constraint - Identical payload re-send → HTTP 200 + `status="duplicate"`, no new row created - Same key, different payload → HTTP 409 Conflict ### Deployment Constraint (bat-006) - trex-basic and portfolio-hub must be deployed simultaneously when BAT-006 is released. - After BAT-006, all `asset_id` values in GET /v1/assets responses change from TEXT to UUID format. - trex-basic consumers must handle UUID-format asset_id before the coordinated deployment is completed. ## Shared Constraints - Token permission levels gate both HTTP and CLI mutations consistently. - DB-backed endpoints and commands require PORTFOLIOHUB_DB_ENABLED=true plus valid TimescaleDB settings. - Secret and token values are only revealed once at issuance time and are masked afterwards. ## Current Gaps / Planned Changes - External consumer versioning policy is still implicit and not published as a separate contract artifact. - Service-token lifecycle and ownership rules may continue to evolve as authorization requirements mature. - OpenAPI publication and CLI reference generation are not yet automated. ## Update Triggers - Update this document when an externally visible API, event, CLI, or file contract changes. - Update this document when a batch changes compatibility, validation, or error behavior. - Update this document when a new external consumer or producer is introduced. ## Change Log - 2026-05-11 KST: bat-008 — REQ-011 AC-1: expanded v1 Consumer API section with authentication contract, detailed request/response schemas for GET /v1/assets, GET /v1/user_info, POST /v1/assets/{asset_id}/ledger/trades; updated GET /health response schema (REQ-004); added idempotency contract. - 2026-05-11 KST: bat-006 — Added v1 consumer API interface section (trex-basic); noted asset_id UUID change and simultaneous deployment constraint; updated CLI portfolio inputs to reflect name+user identification. - 2026-04-30 KST: Bootstrap baseline created from repository observation for initial StagePilot adoption.