Files
stonks-oracle/docs/api-reference.md
T
Celes Renata 88ad1e8d99 feat: comprehensive docs, unit tests, docker-compose app services
- Add scheduler and ingestion unit tests (test_scheduler_unit.py, test_ingestion_unit.py)
- Add all 13 app services + dashboard to docker-compose.yml
- Add full documentation suite: API reference, Helm reference, Docker deployment guide,
  3 architecture diagrams (K8s, Docker Compose, data pipeline), AI agent guide,
  backup/restore guide, observability/metrics reference, per-service docs
- Add intelligence pipeline deep-dive docs with Mermaid diagrams
- Update README with documentation index and links
- Add specs for comprehensive-quality-docs, intelligence-pipeline-deep-dive,
  sanitized-pipeline-docs
2026-04-22 02:56:41 +00:00

1141 lines
42 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.01.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.01.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` | 1168 | 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` | 1168 | 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` | 1168 | 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` | 1168 | 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` | 1168 | 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 | — | 110 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.01.0 | Export dependency |
| `source` | string | `"manual"` | `manual`, `inferred` | Data source |
| `confidence` | float | `1.0` | 0.01.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` | 01 | 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 | — | 110 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