# Stonks Oracle — API Reference This document covers every HTTP endpoint exposed by the four FastAPI services in the Stonks Oracle platform. For each endpoint: HTTP method, path, query parameters (with type, default, and constraints), request body schema, response schema, and error codes. **Live endpoints:** | Service | Base URL | Source | |---------|----------|--------| | Query API | `https://stonks-api.celestium.life` | `services/api/app.py` | | Symbol Registry | `https://stonks-registry.celestium.life` | `services/symbol_registry/app.py` | | Trading Engine | `https://stonks-trading.celestium.life` | `services/trading/app.py` | | Risk Engine | (cluster-internal) | `services/risk/app.py` | **Common error format:** All services return errors as `{"detail": "error message"}` with the appropriate HTTP status code. --- ## Table of Contents - [1. Query API](#1-query-api) - [2. Symbol Registry API](#2-symbol-registry-api) - [3. Trading Engine API](#3-trading-engine-api) - [4. Risk Engine API](#4-risk-engine-api) --- ## 1. Query API Source: `services/api/app.py` Base path: `/` (most endpoints prefixed with `/api/`) ### 1.1 Health and Metrics #### `GET /health` Liveness probe. Verifies database connectivity. - **Response:** `{"status": "ok"}` - **Errors:** `503` — Database unavailable #### `GET /metrics` Prometheus metrics endpoint for scraping. - **Response:** Prometheus text format (`text/plain`) ### 1.2 Companies #### `GET /api/companies` List tracked companies with optional filters. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `active` | bool | `true` | Filter by active status | | `sector` | string | — | Filter by sector | | `ticker` | string | — | Filter by ticker (auto-uppercased) | - **Response:** Array of company objects with `id`, `ticker`, `legal_name`, `exchange`, `sector`, `industry`, `market_cap_bucket`, `active`, `created_at`, `updated_at` #### `GET /api/companies/{company_id}` Get a single company with aliases and active source count. - **Path params:** `company_id` (UUID string) - **Response:** Company object + `aliases[]` + `active_source_count` - **Errors:** `404` — Company not found #### `GET /api/companies/{company_id}/sources` List sources configured for a company. - **Path params:** `company_id` (UUID string) - **Response:** Array of source objects with `id`, `source_type`, `source_name`, `config`, `credibility_score`, `retention_days`, `access_policy`, `active` ### 1.3 Documents #### `GET /api/documents` List documents with optional filters, ordered by `published_at` descending. | Parameter | Type | Default | Constraints | Description | |-----------|------|---------|-------------|-------------| | `ticker` | string | — | — | Filter by ticker | | `company_id` | string | — | — | Filter by company UUID | | `document_type` | string | — | — | Filter by type | | `status` | string | — | — | Filter by processing status | | `since` | string | — | ISO 8601 timestamp | Documents published after this time | | `limit` | int | `50` | max `200` | Page size | | `offset` | int | `0` | — | Pagination offset | - **Response:** Array of document objects #### `GET /api/documents/{document_id}` Get a single document with intelligence extraction and company mentions. - **Path params:** `document_id` (UUID string) - **Response:** Document object + `company_mentions[]` + `intelligence` (with `company_impacts[]`) - **Errors:** `404` — Document not found ### 1.4 Trends #### `GET /api/trends` List trend summaries with optional filters. | Parameter | Type | Default | Constraints | Description | |-----------|------|---------|-------------|-------------| | `ticker` | string | — | — | Filter by entity_id (ticker) | | `entity_type` | string | `"company"` | — | Entity type filter | | `window` | string | — | — | Time window filter | | `limit` | int | `50` | max `200` | Page size | | `offset` | int | `0` | — | Pagination offset | - **Response:** Array of trend objects with JSONB fields parsed, plus `projection` sub-object from `trend_projections` #### `GET /api/trends/history` Historical trend snapshots for charting (time series from `trend_history` table). | Parameter | Type | Default | Constraints | Description | |-----------|------|---------|-------------|-------------| | `ticker` | string | — | — | Filter by entity_id | | `window` | string | — | — | Time window filter | | `limit` | int | `200` | max `1000` | Max rows | - **Response:** Array of trend history objects ordered by `generated_at` ascending #### `GET /api/trends/{trend_id}` Get a single trend summary by ID. - **Path params:** `trend_id` (UUID string) - **Response:** Trend object with parsed JSONB fields - **Errors:** `404` — Trend not found #### `GET /api/trends/{trend_id}/evidence` Drill down from a trend window to contributing documents and raw artifacts. Full provenance chain. - **Path params:** `trend_id` (UUID string) - **Response:** `{ trend, evidence[] }` — each evidence item includes `intelligence` and `company_impacts[]` - **Errors:** `404` — Trend not found #### `GET /api/trends/{trend_id}/projection` Trend projection for a specific trend window. - **Path params:** `trend_id` (UUID string) - **Response:** Projection object with `projected_direction`, `projected_strength`, `projected_confidence`, `projection_horizon`, `driving_factors`, `macro_contribution_pct`, `diverges_from_current` - **Errors:** `404` — Trend not found ### 1.5 Market Prices #### `GET /api/market/prices/{ticker}` Historical close prices from `market_snapshots`. | Parameter | Type | Default | Constraints | Description | |-----------|------|---------|-------------|-------------| | `limit` | int | `30` | max `200` | Max bars returned | - **Path params:** `ticker` (auto-uppercased) - **Response:** Array of OHLCV objects ordered oldest-first ### 1.6 Recommendations #### `GET /api/recommendations` List recommendations with optional filters. | Parameter | Type | Default | Constraints | Description | |-----------|------|---------|-------------|-------------| | `ticker` | string | — | — | Filter by ticker | | `action` | string | — | — | Filter by action (buy/sell/hold) | | `mode` | string | — | — | Filter by mode | | `since` | string | — | ISO 8601 | Generated after this time | | `min_confidence` | float | — | 0.0–1.0 | Minimum confidence threshold | | `limit` | int | `50` | max `200` | Page size | | `offset` | int | `0` | — | Pagination offset | | `latest` | bool | `true` | — | Return only latest per ticker | - **Response:** Array of recommendation objects #### `GET /api/recommendations/{recommendation_id}` Get a single recommendation with evidence and risk evaluation. - **Path params:** `recommendation_id` (UUID string) - **Response:** Recommendation + `evidence[]` + `risk_evaluation` - **Errors:** `404` — Recommendation not found #### `GET /api/recommendations/{recommendation_id}/evidence` Full evidence drill-down: provenance chain from recommendation to source documents and raw artifacts. - **Path params:** `recommendation_id` (UUID string) - **Response:** `{ recommendation, evidence[], trend_window }` — each evidence item includes `intelligence` and `company_impacts[]` - **Errors:** `404` — Recommendation not found ### 1.7 Orders #### `GET /api/orders` List orders with optional filters. | Parameter | Type | Default | Constraints | Description | |-----------|------|---------|-------------|-------------| | `ticker` | string | — | — | Filter by ticker | | `status` | string | — | — | Filter by order status | | `side` | string | — | — | Filter by side (buy/sell) | | `since` | string | — | ISO 8601 | Created after this time | | `limit` | int | `50` | max `200` | Page size | | `offset` | int | `0` | — | Pagination offset | - **Response:** Array of order objects #### `GET /api/orders/{order_id}` Get a single order with events, decision trace, and full audit trail. - **Path params:** `order_id` (UUID string) - **Response:** Order object + `events[]` + `audit_trail` - **Errors:** `404` — Order not found ### 1.8 Positions #### `GET /api/positions` List current positions. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `ticker` | string | — | Filter by ticker | - **Response:** Array of position objects with `id`, `broker_account_id`, `ticker`, `quantity`, `avg_entry_price`, `current_price`, `unrealized_pnl`, `realized_pnl`, `updated_at` ### 1.9 Audit Trail #### `GET /api/audit/{entity_type}/{entity_id}` Get audit events for any entity type and ID. - **Path params:** `entity_type` (string), `entity_id` (string) - **Response:** Array of audit event objects - **Errors:** `404` — No audit events found ### 1.10 Admin: Source Health #### `GET /api/admin/sources/health` Source health overview with latest ingestion status and failure counts. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `source_type` | string | — | Filter by source type | | `company_id` | string | — | Filter by company UUID | | `active_only` | bool | `true` | Only show active sources | - **Response:** Array of source health objects with `source_id`, `source_type`, `source_name`, `credibility_score`, `ticker`, `legal_name`, `last_run_status`, `last_run_at`, `last_error`, `last_items_fetched`, `last_items_new`, `total_runs_24h`, `failed_runs_24h`, `total_items_24h` #### `GET /api/admin/sources/{source_id}/runs` Recent ingestion runs for a specific source. | Parameter | Type | Default | Constraints | Description | |-----------|------|---------|-------------|-------------| | `limit` | int | `20` | max `100` | Page size | | `offset` | int | `0` | — | Pagination offset | #### `PUT /api/admin/sources/{source_id}/toggle` Enable or disable a source. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `active` | bool | `true` | New active state | - **Errors:** `404` — Source not found #### `PUT /api/admin/sources/{source_id}/credibility` Update a source's credibility score. | Parameter | Type | Default | Constraints | Description | |-----------|------|---------|-------------|-------------| | `credibility_score` | float | — | 0.0–1.0 | New credibility score | - **Errors:** `404` — Source not found ### 1.11 Admin: Company Management #### `PUT /api/admin/companies/{company_id}/toggle` Enable or disable a tracked company. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `active` | bool | `true` | New active state | - **Errors:** `404` — Company not found #### `PUT /api/admin/companies/{company_id}/sector` Update a company's sector and industry classification. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `sector` | string | — | **Required.** New sector | | `industry` | string | — | Optional new industry | - **Errors:** `404` — Company not found #### `GET /api/admin/companies/coverage` Source coverage overview per active company. Shows active source counts by type. - **Response:** Array of objects with `company_id`, `ticker`, `legal_name`, `sector`, `active_sources`, `market_sources`, `news_sources`, `filings_sources`, `web_scrape_sources`, `broker_sources` ### 1.12 Admin: Trading Configuration #### `GET /api/admin/trading/config` Get the current active risk/trading configuration. - **Response:** Risk config object with `id`, `name`, `trading_mode`, `config` (JSONB), `active`, timestamps #### `PUT /api/admin/trading/mode` Switch the active trading mode. | Parameter | Type | Constraints | Description | |-----------|------|-------------|-------------| | `mode` | string | `paper`, `live`, or `disabled` | **Required.** New trading mode | #### `PUT /api/admin/trading/config` Update the active risk configuration JSON. - **Body:** `dict[str, Any]` — partial or full risk config object - **Response:** Updated config object #### `GET /api/admin/trading/approvals` List pending operator approval requests for live trading orders. - **Response:** Array of approval objects with `order_job` (JSONB), `recommendation_id`, `ticker`, `side`, `quantity`, `estimated_value`, `status`, `expires_at`, etc. #### `PUT /api/admin/trading/approvals/{approval_id}` Approve or reject a pending operator approval request. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `approved` | bool | — | **Required.** Approve or reject | | `reviewed_by` | string | `"operator"` | Reviewer identity | | `review_note` | string | `""` | Optional note | - **Errors:** `404` — Approval not found or no longer pending #### `GET /api/admin/trading/lockouts` List active symbol lockouts (news-shock, cooldown, manual). #### `POST /api/admin/trading/lockouts` Create a manual symbol lockout. - **Body:** `{ ticker: string, reason: string, duration_minutes: int, lockout_type?: string }` - **Errors:** `400` — Missing or invalid fields #### `DELETE /api/admin/trading/lockouts/{lockout_id}` Delete a symbol lockout (early removal). - **Errors:** `404` — Lockout not found #### `GET /api/admin/trading/approval-config` Get operator approval settings from the active risk config. - **Response:** `{ auto_approve_paper, require_approval_for_live, approval_timeout_minutes }` #### `PUT /api/admin/trading/approval-config` Update operator approval settings. - **Body:** `{ auto_approve_paper?: bool, require_approval_for_live?: bool, approval_timeout_minutes?: int }` - **Response:** Updated approval settings ### 1.13 Operational Dashboard #### `GET /api/ops/ingestion/throughput` Ingestion throughput over time, bucketed by interval. | Parameter | Type | Default | Constraints | Description | |-----------|------|---------|-------------|-------------| | `hours` | int | `24` | 1–168 | Time window | | `bucket` | string | `"1h"` | `15m`, `1h`, `6h`, `1d` | Bucket interval | - **Response:** Array of bucketed throughput objects by source type #### `GET /api/ops/ingestion/summary` High-level ingestion summary for the operational dashboard. | Parameter | Type | Default | Constraints | Description | |-----------|------|---------|-------------|-------------| | `hours` | int | `24` | 1–168 | Time window | - **Response:** `{ total_runs, completed, failed, pending, running, total_items_fetched, total_items_new, active_sources, active_companies, by_source_type[], hours }` #### `GET /api/ops/model/failures` Recent model extraction failures with error details. | Parameter | Type | Default | Constraints | Description | |-----------|------|---------|-------------|-------------| | `hours` | int | `24` | 1–168 | Time window | | `limit` | int | `50` | max `200` | Max results | #### `GET /api/ops/model/performance` Aggregated model performance metrics. | Parameter | Type | Default | Constraints | Description | |-----------|------|---------|-------------|-------------| | `hours` | int | `24` | 1–168 | Time window | | `model_name` | string | — | — | Filter by model | #### `GET /api/ops/pipeline/health` Pipeline stage health summary across ingestion, parsing, extraction, and aggregation. | Parameter | Type | Default | Constraints | Description | |-----------|------|---------|-------------|-------------| | `hours` | int | `24` | 1–168 | Time window | - **Response:** `{ hours, pipeline_enabled, document_stages[], parsing, extraction, aggregation, queue_depths }` #### `GET /api/ops/pipeline/stream` Server-Sent Events stream of live pipeline status. Pushes queue depths and document stage counts every 3 seconds. - **Response:** `text/event-stream` with JSON data payloads #### `POST /api/ops/pipeline/retry-failed` Re-enqueue documents stuck in `extraction_failed` for another attempt (up to 200). - **Response:** `{ retried: int, message: string }` #### `GET /api/ops/pipeline/toggle` Get the current pipeline enabled/disabled state. - **Response:** `{ pipeline_enabled: bool }` #### `POST /api/ops/pipeline/toggle` Toggle the pipeline on or off. - **Body:** `{ enabled: bool }` - **Response:** `{ pipeline_enabled: bool, message: string }` #### `GET /api/ops/sources/coverage-gaps` Identify symbols with missing or insufficient source coverage. - **Response:** `{ missing_source_types[], stale_sources[] }` ### 1.14 System #### `GET /api/system/rate-limits` Current rate limit configuration and usage. - **Response:** `{ polygon_global_limit, polygon_source_types, per_type_limits, cadences_seconds, market_api: { rate_per_minute, cadence_seconds, max_tickers_per_cycle, active_sources }, news_api: {...} }` ### 1.15 Analytics #### `POST /api/analytics/query` Proxy SQL to Trino with row limits. - **Body:** `{ sql: string, limit?: int (default 1000, max 10000) }` - **Response:** `{ columns[], rows[][], row_count, elapsed_ms }` - **Errors:** `400` — Empty SQL; `502` — Trino connection error; `504` — Trino timeout #### `GET /api/analytics/schema` Trino catalog/schema/table/column metadata for the schema browser. - **Response:** `{ catalog, schema, tables[{ name, columns[{ name, type }] }] }` #### `GET /api/analytics/pg-schema` PostgreSQL table/column metadata with primary keys, foreign keys, and row estimates. - **Response:** `{ catalog: "postgresql", schema: "public", tables[] }` #### `POST /api/analytics/pg-query` Run read-only SQL against PostgreSQL directly. Only SELECT statements allowed. - **Body:** `{ sql: string, limit?: int (default 1000, max 10000) }` - **Response:** `{ columns[], rows[][], row_count, elapsed_ms }` - **Errors:** `400` — Non-SELECT query, syntax error, table not found, or query error #### `GET /api/analytics/saved-queries` List all saved queries. #### `POST /api/analytics/saved-queries` (201) Save a new query. - **Body:** `{ name: string, description?: string, sql_text: string }` #### `DELETE /api/analytics/saved-queries/{query_id}` Delete a saved query. - **Errors:** `404` — Query not found ### 1.16 Macro Signal Layer #### `GET /api/admin/macro/status` Return the current macro signal layer enabled/disabled state. - **Response:** `{ macro_enabled: bool, source: "default" | "risk_configs" }` #### `PUT /api/admin/macro/toggle` Toggle the macro signal layer on or off. Records an audit event. - **Body:** `{ enabled: bool, operator?: string (default "operator") }` - **Response:** `{ macro_enabled, previous_enabled, toggled_by }` ### 1.17 Macro Events and Impacts #### `GET /api/macro/events` List recent global events with filtering. | Parameter | Type | Default | Constraints | Description | |-----------|------|---------|-------------|-------------| | `severity` | string | — | — | Filter by severity | | `region` | string | — | — | Filter by affected region | | `sector` | string | — | — | Filter by affected sector | | `since` | string | — | ISO 8601 | Events after this time | | `until` | string | — | ISO 8601 | Events before this time | | `limit` | int | `50` | max `200` | Page size | | `offset` | int | `0` | — | Pagination offset | #### `GET /api/macro/events/{event_id}` Event detail with affected companies and macro impact scores. - **Errors:** `404` — Global event not found #### `GET /api/macro/impacts/{ticker}` Macro impacts and exposure profile for a specific company. | Parameter | Type | Default | Constraints | Description | |-----------|------|---------|-------------|-------------| | `since` | string | — | ISO 8601 | Impacts after this time | | `limit` | int | `50` | max `200` | Page size | | `offset` | int | `0` | — | Pagination offset | - **Response:** `{ exposure_profile, impacts[] }` ### 1.18 Competitive Signal Layer #### `GET /api/admin/competitive/status` Return the current competitive signal layer enabled/disabled state. - **Response:** `{ competitive_enabled: bool, source: "default" | "risk_configs" }` #### `PUT /api/admin/competitive/toggle` Toggle the competitive signal layer on or off. Records an audit event. - **Body:** `{ enabled: bool, operator?: string (default "operator") }` - **Response:** `{ competitive_enabled, previous_enabled, toggled_by }` ### 1.19 Patterns and Competitive Signals #### `GET /api/patterns/{ticker}` Historical patterns for a company. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `catalyst_type` | string | — | Filter by catalyst type | | `time_horizon` | string | — | Filter by time horizon | - **Response:** `{ ticker, patterns[], count }` #### `GET /api/patterns/{ticker}/competitors` Cross-company patterns showing how this company's catalysts affected competitors. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `catalyst_type` | string | — | Filter by catalyst type | | `time_horizon` | string | — | Filter by time horizon | - **Response:** `{ ticker, cross_company_patterns[], count }` #### `GET /api/patterns/{ticker}/competitive-signals` Recent competitive signals targeting this company (limit 100). - **Response:** `{ ticker, competitive_signals[], count }` #### `GET /api/patterns/{ticker}/decisions` Major corporate decision history with trend outcomes and pattern statistics. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `time_horizon` | string | — | Filter by time horizon | - **Response:** `{ ticker, decisions[], count }` — each decision includes `pattern_statistics[]` ### 1.20 AI Agents #### `GET /api/agents` List all AI agent configurations. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `active_only` | bool | `false` | Only show active agents | #### `GET /api/agents/{agent_id}` Get a single agent configuration. - **Errors:** `404` — Agent not found #### `POST /api/agents` (201) Create a new user-defined agent. - **Body:** `AgentCreateBody` | Field | Type | Default | Description | |-------|------|---------|-------------| | `name` | string | — | **Required** | | `slug` | string | auto-generated | URL-safe identifier | | `purpose` | string | `""` | Agent purpose | | `model_provider` | string | `"ollama"` | LLM provider | | `model_name` | string | `"llama3.1:8b"` | Model identifier | | `system_prompt` | string | `""` | System prompt | | `user_prompt_template` | string | `""` | User prompt template | | `prompt_version` | string | `""` | Prompt version tag | | `schema_version` | string | `"1.0.0"` | Output schema version | | `temperature` | float | `0.0` | Sampling temperature | | `max_tokens` | int | `32768` | Max output tokens | | `timeout_seconds` | int | `120` | Request timeout | | `max_retries` | int | `2` | Max retry attempts | #### `PUT /api/agents/{agent_id}` Update an agent configuration. Partial updates supported. - **Body:** `AgentUpdateBody` — all fields optional (same fields as create) - **Errors:** `400` — No fields to update; `404` — Agent not found #### `DELETE /api/agents/{agent_id}` Delete a user-created agent. System agents cannot be deleted. - **Errors:** `403` — Cannot delete system agents; `404` — Agent not found #### `GET /api/agents/{agent_id}/performance` Aggregated performance metrics for an agent. | Parameter | Type | Default | Constraints | Description | |-----------|------|---------|-------------|-------------| | `hours` | int | `24` | max `720` | Time window | - **Response:** `{ total_invocations, successes, failures, avg_duration_ms, p95_duration_ms, avg_confidence, avg_retries, total_input_tokens, total_output_tokens, success_rate }` #### `GET /api/agents/{agent_id}/performance/history` Hourly performance time-series for an agent. | Parameter | Type | Default | Constraints | Description | |-----------|------|---------|-------------|-------------| | `hours` | int | `24` | max `720` | Time window | - **Response:** Array of `{ hour, invocations, successes, avg_duration_ms, avg_confidence }` ### 1.21 Agent Variants #### `GET /api/agents/{agent_id}/variants` List all variants for an agent, ordered by `created_at` ascending. #### `GET /api/agents/{agent_id}/variants/{variant_id}` Get a single variant. - **Errors:** `404` — Variant not found #### `POST /api/agents/{agent_id}/variants` (201) Create a new variant for an agent. - **Body:** `VariantCreateBody` | Field | Type | Default | Description | |-------|------|---------|-------------| | `variant_name` | string | — | **Required** | | `variant_slug` | string | auto-generated | URL-safe identifier | | `description` | string | `""` | Variant description | | `model_provider` | string | `"ollama"` | LLM provider | | `model_name` | string | — | **Required.** Model identifier | | `system_prompt` | string | `""` | System prompt | | `user_prompt_template` | string | `""` | User prompt template | | `prompt_version` | string | `""` | Prompt version tag | | `temperature` | float | `0.0` | Sampling temperature | | `max_tokens` | int | `32768` | Max output tokens | | `context_window` | int | `0` | Context window size | | `input_token_limit` | int | `0` | Input token limit | | `token_budget` | int | `0` | Token budget | | `timeout_seconds` | int | `120` | Request timeout | | `max_retries` | int | `2` | Max retry attempts | - **Errors:** `409` — Duplicate variant slug #### `PUT /api/agents/{agent_id}/variants/{variant_id}` Partial update a variant. - **Body:** `VariantUpdateBody` — all fields optional - **Errors:** `400` — No fields to update; `404` — Variant not found #### `DELETE /api/agents/{agent_id}/variants/{variant_id}` Delete a variant. Cannot delete active variants. - **Errors:** `400` — Cannot delete active variant; `404` — Variant not found #### `POST /api/agents/{agent_id}/clone` (201) Clone an agent's configuration as a new variant with optional overrides. - **Body:** `VariantCloneBody { variant_name, variant_slug?, ...optional overrides }` - **Errors:** `404` — Agent not found; `409` — Duplicate slug #### `POST /api/agents/{agent_id}/variants/{variant_id}/clone` (201) Clone an existing variant as a new variant with optional overrides. - **Body:** `VariantCloneBody` - **Errors:** `404` — Source variant not found; `409` — Duplicate slug #### `POST /api/agents/{agent_id}/variants/{variant_id}/activate` Set a variant as the active variant for its agent. Deactivates any currently active variant in a transaction. - **Errors:** `404` — Variant not found #### `POST /api/agents/{agent_id}/variants/deactivate` Deactivate the currently active variant. Agent falls back to base configuration. #### `GET /api/agents/{agent_id}/variants/{variant_id}/performance` Aggregated performance metrics for a specific variant. | Parameter | Type | Default | Constraints | Description | |-----------|------|---------|-------------|-------------| | `hours` | int | `24` | max `720` | Time window | #### `GET /api/agents/{agent_id}/variants/{variant_id}/performance/history` Hourly performance time-series for a specific variant. | Parameter | Type | Default | Constraints | Description | |-----------|------|---------|-------------|-------------| | `hours` | int | `24` | max `720` | Time window | --- ## 2. Symbol Registry API Source: `services/symbol_registry/app.py` with routers from `exposure.py`, `competitors.py`, `competitor_inference.py` ### 2.1 Health #### `GET /health` Liveness probe. Verifies database connectivity. - **Response:** `{"status": "ok"}` - **Errors:** `503` — Database unavailable ### 2.2 Companies #### `POST /companies` (201) Create a new tracked company. - **Body:** `CompanyCreate` | Field | Type | Default | Constraints | Description | |-------|------|---------|-------------|-------------| | `ticker` | string | — | 1–10 uppercase letters | **Required.** Stock ticker | | `legal_name` | string | — | — | **Required.** Company name | | `exchange` | string | `null` | — | Stock exchange | | `sector` | string | `null` | — | Sector classification | | `industry` | string | `null` | — | Industry classification | | `market_cap_bucket` | string | `null` | — | Market cap bucket | - **Response:** `CompanyResponse { id, ticker, legal_name, exchange, sector, industry, market_cap_bucket, active }` - **Errors:** `409` — Company already exists; `422` — Invalid ticker format #### `GET /companies` List tracked companies. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `active` | bool | `true` | Filter by active status | - **Response:** Array of `CompanyResponse` #### `GET /companies/{company_id}` Get a single company. - **Errors:** `404` — Company not found #### `PUT /companies/{company_id}` Update a company. - **Body:** `CompanyCreate` (same as create) - **Errors:** `404` — Company not found ### 2.3 Aliases #### `POST /companies/{company_id}/aliases` (201) Add an alias for a company. - **Body:** `{ alias: string, alias_type?: string (default "brand") }` - **Response:** `{ id, alias, alias_type }` #### `GET /companies/{company_id}/aliases` List aliases for a company. - **Response:** Array of `{ id, alias, alias_type }` ### 2.4 Watchlists #### `POST /watchlists` (201) Create a new watchlist. - **Body:** `{ name: string, description?: string }` - **Errors:** `409` — Watchlist name already exists #### `GET /watchlists` List all watchlists. #### `POST /watchlists/{watchlist_id}/members/{company_id}` (201) Add a company to a watchlist. - **Errors:** `409` — Already a member; `404` — Watchlist or company not found #### `GET /watchlists/{watchlist_id}/members` List companies in a watchlist. - **Response:** Array of `CompanyResponse` ### 2.5 Sources #### `POST /companies/{company_id}/sources` (201) Add a data source for a company. - **Body:** `SourceCreate` | Field | Type | Default | Constraints | Description | |-------|------|---------|-------------|-------------| | `source_type` | string | — | `market_api`, `news_api`, `filings_api`, `web_scrape`, `broker` | **Required** | | `source_name` | string | — | — | **Required** | | `config` | dict | `{}` | URLs validated for `base_url` | Source configuration | | `credibility_score` | float | `0.5` | — | Source credibility | | `retention_days` | int | `365` | — | Data retention period | | `access_policy` | string | `"internal"` | `internal`, `public`, `restricted` | Access policy | - **Errors:** `404` — Company not found; `422` — Invalid source_type or access_policy #### `GET /companies/{company_id}/sources` List sources for a company. ### 2.6 Exposure Profiles #### `GET /companies/{company_id}/exposure` Get the current active exposure profile for a company. - **Response:** `ExposureProfileResponse { id, company_id, geographic_revenue_mix, supply_chain_regions, key_input_commodities, regulatory_jurisdictions, market_position_tier, export_dependency_pct, source, confidence, version, active, created_at, updated_at }` - **Errors:** `404` — No active exposure profile found #### `PUT /companies/{company_id}/exposure` Create or update an exposure profile. Archives the previous active version. - **Body:** `ExposureProfileCreate` | Field | Type | Default | Constraints | Description | |-------|------|---------|-------------|-------------| | `geographic_revenue_mix` | dict[str, float] | `{}` | — | Region to revenue percentage | | `supply_chain_regions` | list[str] | `[]` | — | Supply chain regions | | `key_input_commodities` | list[str] | `[]` | — | Key input commodities | | `regulatory_jurisdictions` | list[str] | `[]` | — | Regulatory jurisdictions | | `market_position_tier` | string | `"regional"` | `global_leader`, `multinational`, `regional`, `domestic` | Market position | | `export_dependency_pct` | float | `0.0` | 0.0–1.0 | Export dependency | | `source` | string | `"manual"` | `manual`, `inferred` | Data source | | `confidence` | float | `1.0` | 0.0–1.0 | Confidence score | - **Errors:** `404` — Company not found #### `GET /companies/{company_id}/exposure/history` Get all exposure profile versions for a company, ordered by version descending. ### 2.7 Competitor Relationships #### `POST /companies/{company_id}/competitors` (201) Create a competitor relationship. Records an audit event. - **Body:** `CompetitorRelationshipCreate` | Field | Type | Default | Constraints | Description | |-------|------|---------|-------------|-------------| | `company_b_id` | string | — | UUID | **Required.** Other company | | `relationship_type` | string | — | `direct_rival`, `same_sector`, `overlapping_products`, `supply_chain_adjacent` | **Required** | | `strength` | float | `0.5` | 0–1 | Relationship strength | | `bidirectional` | bool | `true` | — | Bidirectional relationship | | `source` | string | `"manual"` | `manual`, `inferred` | Data source | - **Errors:** `400` — Self-reference; `404` — Company not found; `409` — Relationship already exists #### `GET /companies/{company_id}/competitors` List active competitor relationships, enriched with ticker and legal_name of the other company. - **Errors:** `404` — Company not found #### `PUT /companies/{company_id}/competitors/{relationship_id}` Update a competitor relationship. Records an audit event with previous state. - **Body:** `CompetitorRelationshipCreate` - **Errors:** `404` — Relationship not found #### `DELETE /companies/{company_id}/competitors/{relationship_id}` Soft-delete a competitor relationship (sets `active=false`). Records an audit event. - **Errors:** `404` — Active relationship not found ### 2.8 Competitor Inference #### `POST /companies/{company_id}/competitors/infer` Auto-infer competitor relationships based on sector/industry match and document co-mention frequency. Strength formula: `0.3 * sector_match + 0.7 * normalized_co_mention_count` Upserts relationships with `source='inferred'` and `relationship_type='same_sector'`. - **Response:** Array of `CompetitorRelationship` objects sorted by strength descending - **Errors:** `400` — Company missing sector or industry; `404` — Company not found --- ## 3. Trading Engine API Source: `services/trading/app.py` ### 3.1 Health and Readiness #### `GET /health` Liveness probe. - **Response:** `{"status": "ok"}` #### `GET /ready` Readiness probe — reports whether the engine is running. - **Response:** `{"ready": bool}` ### 3.2 Debug #### `GET /api/trading/debug` Diagnostic endpoint showing engine internals for troubleshooting. - **Response:** `{ running, has_pool, has_redis, config_enabled, polling_interval, last_poll, portfolio_state: { active_pool, reserve_pool, total_value, open_positions }, risk_tier, tasks, processed_rec_count }` ### 3.3 Engine Status and Control #### `GET /api/trading/status` Return current engine state. - **Response:** `{ enabled, paused, risk_tier, circuit_breaker_status, active_pool, reserve_pool, portfolio_heat, open_positions, last_decision_at }` - **Errors:** `503` — Engine not initialised #### `PUT /api/trading/config` Update trading engine configuration. Returns previous and new values for audit trail. - **Body:** `ConfigUpdateRequest` | Field | Type | Description | |-------|------|-------------| | `enabled` | bool | Enable/disable engine | | `risk_tier` | string | Risk tier level | | `reserve_siphon_pct` | float | Reserve pool siphon percentage | | `polling_interval_seconds` | int | Polling interval | | `absolute_position_cap` | float | Max position value | | `active_pool_minimum` | float | Minimum active pool | | `micro_trading_enabled` | bool | Enable micro trades | | `max_open_positions` | int | Max concurrent positions | All fields optional. Only provided fields are updated. - **Response:** `{ previous, updated, change_source: "api", changed_at }` - **Errors:** `503` — Engine not initialised #### `POST /api/trading/pause` Pause the trading engine. - **Response:** `{"paused": true}` #### `POST /api/trading/resume` Resume the trading engine. - **Response:** `{"paused": false}` #### `POST /api/trading/reset` Full paper trading reset: liquidate broker positions, cancel orders, clear trading state, reset capital. - **Body:** `{ initial_capital?: float (default 0.0) }` — if 0, uses broker balance or defaults to 100,000 - **Response:** `{ reset: true, initial_capital, active_pool, reserve_pool, broker: { orders_cancelled, positions_closed, portfolio_value, cash, buying_power } }` - **Errors:** `503` — Engine not initialised; `500` — Database reset failed ### 3.4 Decision Audit Trail #### `GET /api/trading/decisions` Return recent trading decisions from the database. | Parameter | Type | Default | Constraints | Description | |-----------|------|---------|-------------|-------------| | `ticker` | string | — | — | Filter by ticker | | `decision` | string | — | — | Filter by decision type | | `is_micro_trade` | bool | — | — | Filter micro trades | | `limit` | int | `50` | max `200` | Page size | | `offset` | int | `0` | — | Pagination offset | ### 3.5 Performance Metrics #### `GET /api/trading/metrics` Return current performance metrics. - **Response:** `{ total_portfolio_value, active_pool, reserve_pool, unrealized_pnl, realized_pnl, daily_pnl, win_rate, profit_factor, sharpe_ratio, max_drawdown, portfolio_heat }` - **Errors:** `503` — Engine not initialised #### `GET /api/trading/metrics/history` Return historical daily portfolio snapshots. | Parameter | Type | Default | Constraints | Description | |-----------|------|---------|-------------|-------------| | `limit` | int | `30` | max `365` | Max snapshots | ### 3.6 Backtesting #### `POST /api/trading/backtest` Launch a backtest run asynchronously. - **Body:** `BacktestRequest` | Field | Type | Default | Description | |-------|------|---------|-------------| | `start_date` | string | — | **Required.** ISO date (YYYY-MM-DD) | | `end_date` | string | — | **Required.** ISO date (YYYY-MM-DD) | | `initial_capital` | float | `500.0` | Starting capital | | `risk_tier` | string | `"moderate"` | Risk tier for backtest | - **Response:** `{ id: string, status: "running" }` - **Errors:** `503` — Engine not initialised #### `GET /api/trading/backtest/{backtest_id}` Retrieve backtest results. - **Response:** `{ id, start_date, end_date, initial_capital, risk_tier, config, total_return, sharpe_ratio, max_drawdown, win_rate, profit_factor, trade_count, equity_curve[], trades[], status, completed_at, created_at }` - Status values: `running`, `completed`, `not_found`, `pending` ### 3.7 Notifications #### `GET /api/trading/notifications/config` Return current notification configuration. - **Response:** `{ sms_enabled, email_enabled, phone_number, email_recipient }` - **Errors:** `503` — Engine not initialised #### `PUT /api/trading/notifications/config` Update notification preferences. - **Body:** `NotificationConfigRequest` | Field | Type | Description | |-------|------|-------------| | `sms_enabled` | bool | Enable SMS notifications | | `email_enabled` | bool | Enable email notifications | | `phone_number` | string | SMS phone number | | `email_recipient` | string | Email recipient address | All fields optional. - **Errors:** `503` — Engine not initialised #### `GET /api/trading/notifications/history` Return recent notifications. | Parameter | Type | Default | Constraints | Description | |-----------|------|---------|-------------|-------------| | `limit` | int | `50` | max `200` | Max results | ### 3.8 Override Orders #### `POST /api/trading/override/order` (202) Submit a manual override order to the broker queue. Auto-registers untracked tickers in the Symbol Registry. - **Body:** `OverrideOrderRequest` | Field | Type | Default | Constraints | Description | |-------|------|---------|-------------|-------------| | `ticker` | string | — | 1–10 alphabetic chars | **Required.** Stock ticker | | `side` | string | — | `buy` or `sell` | **Required.** Order side | | `quantity` | float | — | Must be positive | **Required.** Share quantity | | `order_type` | string | `"market"` | `market`, `limit`, `stop`, `stop_limit` | Order type | | `limit_price` | float | `null` | Required for `limit`/`stop_limit` | Limit price | | `stop_price` | float | `null` | Required for `stop`/`stop_limit` | Stop price | - **Response:** `OverrideOrderResponse { job_id, status: "queued", ticker, side, quantity, auto_registered }` - **Errors:** `503` — Engine not initialised or broker queue unavailable; `422` — Validation errors --- ## 4. Risk Engine API Source: `services/risk/app.py` ### 4.1 Health #### `GET /health` Liveness probe. - **Response:** `{"status": "ok"}` ### 4.2 Order Evaluation #### `POST /evaluate` Evaluate a proposed order against risk rules. - **Body:** `EvaluateRequest` | Field | Type | Description | |-------|------|-------------| | `order` | ProposedOrder | **Required.** The order to evaluate | | `config` | PortfolioRiskConfig | Optional. Risk config (uses defaults if null) | | `state` | AccountRiskState | Optional. Current account state | **ProposedOrder schema:** | Field | Type | Default | Description | |-------|------|---------|-------------| | `recommendation_id` | string | `null` | Source recommendation | | `ticker` | string | — | **Required.** Stock ticker | | `sector` | string | `""` | Company sector | | `action` | string | `"buy"` | `buy` or `sell` | | `quantity` | float | `0.0` | Share quantity | | `estimated_value` | float | `0.0` | Estimated order value | | `confidence` | float | `0.0` | Recommendation confidence | - **Response:** `RiskEvaluation { evaluation_id, recommendation_id, ticker, eligible, allowed_mode, checks[], rejection_reasons[], config_snapshot, state_snapshot, evaluated_at }` ### 4.3 Approvals #### `GET /approvals/pending` List pending approval requests. - **Response:** Array of approval request objects (serialized via `to_dict()`) - **Errors:** `503` — Database not ready #### `GET /approvals/{approval_id}` Get a single approval request. - **Errors:** `404` — Approval not found; `503` — Database not ready #### `POST /approvals/{approval_id}/review` Approve or reject a pending approval request. - **Body:** `ReviewRequest` | Field | Type | Default | Description | |-------|------|---------|-------------| | `approved` | bool | — | **Required.** Approve or reject | | `reviewed_by` | string | `"operator"` | Reviewer identity | | `review_note` | string | `""` | Optional review note | - **Response:** `{ approval_id, status }` - **Errors:** `404` — Approval not found or no longer pending; `503` — Database not ready ### 4.4 Approval Expiration #### `POST /approvals/expire` Expire stale approvals that have passed their expiration time. - **Response:** `{ expired: int, items: [] }` - **Errors:** `503` — Database not ready