Skip to content

API Endpoint Audit — Dev Environment

Date: 2026-04-23 ALB: http://tradai-dev-1942285475.eu-central-1.elb.amazonaws.com Region: eu-central-1, Account 600802701449 Tested from: macOS / Windows (curl)

Routing Architecture

Internet
   |
   ALB (port 80)
   ├── /mlflow, /mlflow/*  ──►  MLflow ECS Service (port 5000)
   ├── /api/v1/live*       ──►  Live-Trading ECS Service (port 8004)
   ├── /api/v1/dry-run*    ──►  Dry-Run-Trading ECS Service (port 8005)
   └── /api/v1/*           ──►  Backend ECS Service (port 8000)
                                    ├── internal proxy ──► strategy-service:8003 (Service Discovery)
                                    └── internal proxy ──► data-collection:8002  (Service Discovery)

Strategy-service and data-collection are not exposed via ALB — only reachable through the backend's internal HTTP proxy over ECS Service Discovery.

Summary

Category Count Details
Working (200) 14 Production-ready
Validation (422/405) 7 Endpoint works, needs correct payload
Real bugs 7 Need code / infra fixes
Not found (by design) 10+ Route does not exist in code

1. Working Endpoints (200)

ALB="http://tradai-dev-1942285475.eu-central-1.elb.amazonaws.com"

# Health
curl -s "$ALB/api/v1/health" | jq .
# → {"healthy":true,"status":"healthy","service":"Backend","version":"0.1.0",
#    "checks":[dynamodb:ok, sqs:ok, data_collection:ok, strategy_service:ok]}

# Strategy list (proxied to strategy-service)
curl -s "$ALB/api/v1/strategies" | jq .
# → {"configs":["pascal-v2"],"count":1}

# Strategy detail (by config name, not strategy class name)
curl -s "$ALB/api/v1/strategies/pascal-v2" | jq .
# → {"name":"PascalStrategy","version":"2.0.0","timeframe":"1h","pairs":["BTC/USDT:USDT"],...}

# Catalog
curl -s "$ALB/api/v1/catalog/strategies" | jq .
# → {"strategies":[],"total":0,"limit":20,"offset":0}

curl -s "$ALB/api/v1/catalog/strategies/PascalStrategy" | jq .
# → 200 (individual lookup works even when list is empty)

curl -s "$ALB/api/v1/catalog/leaderboard" | jq .
# → {"entries":[],"total":0,"limit":10,"offset":0}

# Config versions
curl -s "$ALB/api/v1/configs/E2ETestStrategy" | jq .
# → {"strategy_name":"E2ETestStrategy","items":[],"total":0}

# Model versions (from MLflow Registry)
curl -s "$ALB/api/v1/models/E2ETestStrategy/versions" | jq .
# → {"model_name":"E2ETestStrategy","versions":[{"version":"34","status":"READY",...}]}

# Data
curl -s "$ALB/api/v1/data/symbols" | jq '.symbols[:3]'
# → ["0G/USDT:USDT","1000000BOB/USDT:USDT","1000000MOG/USDT:USDT"]

curl -s "$ALB/api/v1/data/freshness?symbols=BTC%2FUSDT%3AUSDT" | jq .
# → {"symbols":[{"symbol":"BTC/USDT:USDT","latest_date":"2026-04-22T00:00:00Z","is_stale":true}]}

# Trading status
curl -s "$ALB/api/v1/trading/status" | jq .
# → {"instances":[],"summary":{"running":0,"total":0,...}}

# A/B routing info
curl -s "$ALB/api/v1/strategies/E2ETestStrategy/routing-info" | jq .
# → {"active_test":false,"champion_version":null,...}

# WebSocket ticket
curl -s -X POST "$ALB/api/v1/ws/ticket" | jq .
# → 200

# MLflow UI
curl -s -o /dev/null -w "%{http_code}" "$ALB/mlflow/"
# → 200

2. Validation Errors (422 / 405) — Endpoints work, need correct payload

# POST /api/v1/backtests — missing: config, experiment_name
curl -s -X POST -H "Content-Type: application/json" \
  -d '{"strategy":"E2ETestStrategy"}' "$ALB/api/v1/backtests" | jq .

# POST /api/v1/configs — missing: config_data
curl -s -X POST -H "Content-Type: application/json" \
  -d '{"strategy_name":"X"}' "$ALB/api/v1/configs" | jq .

# POST /api/v1/strategies/{name}/stage — missing: version
curl -s -X POST -H "Content-Type: application/json" \
  -d '{}' "$ALB/api/v1/strategies/E2ETestStrategy/stage" | jq .

# POST /api/v1/strategies/{name}/promote — missing required body
curl -s -X POST "$ALB/api/v1/strategies/E2ETestStrategy/promote" | jq .

# GET /api/v1/catalog/strategies/{name}/compare — missing: version_a, version_b
curl -s "$ALB/api/v1/catalog/strategies/E2ETestStrategy/compare" | jq .

# GET /api/v1/data/freshness (without symbols param)
curl -s "$ALB/api/v1/data/freshness" | jq .

# GET /api/v1/configs → 405 (only POST allowed — create new config version)
curl -s "$ALB/api/v1/configs" | jq .

3. Real Bugs

B1 — GET /api/v1/backtests → 503 (Pydantic deserialization)

Impact: High — blocks the dashboard backtest list page.

curl -s "$ALB/api/v1/backtests" | jq .
# → {"detail":"Unexpected error during list_all: 1 validation error for BacktestJobStatus
#    result
#      Input should be a valid dictionary or instance of BacktestResult
#      [type=model_type, input_value='{\"total_trades\":66,...}', input_type=str]"}

Root cause: The result field in the DynamoDB tradai-workflow-state-dev table is stored as a JSON string (DynamoDB S type), but the BacktestJobStatus Pydantic model expects a dict (BacktestResult). Deserialization fails because Pydantic receives a raw string instead of a parsed object.

Fix: Add a @field_validator('result', mode='before') that calls json.loads() when the input is a string, or fix the serialization path that writes result as a string instead of a Map.


B2 — GET /api/v1/strategies/{id}/instances → 503 (IAM)

Impact: High — blocks live strategy instance management in dashboard.

curl -s "$ALB/api/v1/strategies/E2ETestStrategy/instances" | jq .
# → {"detail":"Access denied to ECS cluster: arn:aws:ecs:eu-central-1:600802701449:cluster/tradai-dev"}

Root cause: The backend ECS task role is missing ecs:ListTasks and ecs:DescribeTasks permissions on the tradai-dev cluster.

Fix: Add ECS read permissions to the backend task role in infra/compute/modules/ecs_services.py or the IAM policy module.


B3 — POST /api/v1/strategies/{id}/run → 503 (IAM)

Impact: High — cannot launch strategy instances from the dashboard.

curl -s -X POST "$ALB/api/v1/strategies/E2ETestStrategy/run" | jq .
# → {"detail":"Access denied to ECS cluster: arn:aws:ecs:eu-central-1:600802701449:cluster/tradai-dev"}

Root cause: Same as B2 — additionally needs ecs:RunTask permission.


B4 — GET /api/v1/strategies/pnl → 503 (proxy 404)

Impact: Medium — P&L dashboard panel broken.

curl -s "$ALB/api/v1/strategies/pnl" | jq .
# → {"detail":"Get strategy failed: 404"}

Root cause: The P&L handler calls the strategy config lookup internally. If the strategy config is not found in strategy-service, it surfaces as 503. Backend wraps all non-2xx proxy responses as 503 (ExternalServiceError).


B5 — POST /api/v1/models/{name}/rollback → 503 (MLflow error)

Impact: Medium — model rollback not functional.

curl -s -X POST -H "Content-Type: application/json" \
  -d '{}' "$ALB/api/v1/models/E2ETestStrategy/rollback" | jq .
# → {"detail":"Rollback model failed: 400"}

Root cause: Downstream MLflow model registry returns 400. Likely missing required fields in the rollback request or no previous version to roll back to.


B6 — GET /api/v1/strategies/{id}/trades → 400 (DynamoDB schema)

Impact: Medium — open trades panel broken.

curl -s "$ALB/api/v1/strategies/E2ETestStrategy/trades" | jq .
# → {"detail":"Invalid parameter for get: The provided key element does not match the schema"}

Root cause: The code queries DynamoDB with a key that doesn't match the table's key schema — likely using strategy_id (String) when the table expects a different partition key name or type.


B7 — GET /mlflow/api/2.0/mlflow/* → 404 (all MLflow REST API)

Impact: High — MLflow API not accessible through ALB; only the UI works.

curl -s "$ALB/mlflow/api/2.0/mlflow/experiments/list"
# → <html>404 Not Found</html>

curl -s -X POST -H "Content-Type: application/json" \
  -d '{"experiment_ids":["10"]}' "$ALB/mlflow/api/2.0/mlflow/runs/search"
# → <html>404 Not Found</html>

Root cause: MLflow is started with --static-prefix=/mlflow, which only configures the UI (static asset paths). The Flask REST API still registers routes at /api/2.0/mlflow/.... When ALB forwards a request to /mlflow/api/2.0/mlflow/experiments/list, Flask sees the full path (including the /mlflow prefix) and finds no matching route.

Fix options: 1. Set SCRIPT_NAME=/mlflow in Gunicorn/WSGI config so Flask mounts all routes under /mlflow 2. Configure ALB target group with path rewrite (strip /mlflow prefix) 3. Route MLflow API calls through the backend proxy (strategy-service already has /api/v1/mlflow/models/{name} and /api/v1/mlflow/experiments/{id} routes, but these are not ALB-exposed)


4. Routes That Don't Exist (404 by design)

These URLs return 404 because no route is registered for them:

Tested URL Why 404 Correct alternative
GET /docs Swagger disabled in dev N/A
GET /api/v1/trading/positions Route never implemented GET /api/v1/trading/status
GET /api/v1/trading/balance Route never implemented
GET /api/v1/trading/orders Route never implemented
GET /api/v1/models No list-all route GET /api/v1/models/{name}/versions
GET /api/v1/models/{name} No single-model route GET /api/v1/models/{name}/versions
GET /api/v1/strategies/{name}/status Route never implemented GET /api/v1/strategies/{name}/instances
GET /api/v1/strategies/{name}/metrics Route never implemented
GET /api/v1/catalog/strategies/{name}/performance Route never implemented GET /api/v1/catalog/leaderboard
GET /api/v1/mlflow/models/{name} Strategy-service route, not ALB-exposed

5. Full Endpoint Inventory

Backend Service (14 routes via ALB)

Method Path Status
GET /api/v1/health 200
POST /api/v1/backtests 422 (works)
GET /api/v1/backtests 503 (B1)
GET /api/v1/backtests/{job_id} 200*
POST /api/v1/backtests/{job_id}/cancel 200*
GET /api/v1/backtests/{job_id}/equity 200*
GET /api/v1/backtests/{job_id}/report-data 200*
POST /api/v1/ws/ticket 200
POST /api/v1/configs 422 (works)
GET /api/v1/configs/{strategy_name} 200
GET /api/v1/configs/{strategy_name}/{config_id} 200*
POST /api/v1/configs/{strategy_name}/{config_id}/activate 200*
POST /api/v1/configs/{strategy_name}/{config_id}/deprecate 200*
GET /api/v1/strategies 200 (proxy)
GET /api/v1/strategies/{config_name} 200 (proxy)
POST /api/v1/strategies/{id}/run 503 (B3)
GET /api/v1/strategies/{id}/instances 503 (B2)
POST /api/v1/strategies/{id}/instances/{iid}/stop 503 (B2)*
GET /api/v1/strategies/{id}/instances/{iid}/logs 503 (B2)*
POST /api/v1/strategies/{id}/instances/{iid}/pause 503 (B2)*
POST /api/v1/strategies/{id}/instances/{iid}/resume 503 (B2)*
GET /api/v1/trading/status 200
GET /api/v1/strategies/{name}/routing-info 200
GET /api/v1/strategies/pnl 503 (B4)
GET /api/v1/strategies/{id}/trades 400 (B6)
POST /api/v1/strategies/{name}/stage 422 (works)
POST /api/v1/strategies/{name}/promote 422 (works)
GET /api/v1/models/{name}/versions 200
POST /api/v1/models/{name}/rollback 503 (B5)
GET /api/v1/data/symbols 200 (proxy)
GET /api/v1/data/freshness 200 (proxy, needs symbols param)
GET /api/v1/catalog/strategies 200
GET /api/v1/catalog/strategies/{name} 200
GET /api/v1/catalog/leaderboard 200
GET /api/v1/catalog/strategies/{name}/compare 422 (works)

* = untestable without valid job_id / config_id, expected to work.

MLflow (via ALB)

Method Path Status
GET /mlflow/ 200 (UI only)
* /mlflow/api/2.0/mlflow/* 404 (B7)

Strategy-Service (9 route groups, NOT ALB-exposed)

Accessible only through backend proxy or Service Discovery. See services/strategy-service/src/ for full route definitions.

Data-Collection (5 routes, NOT ALB-exposed)

Accessible only through backend proxy or Service Discovery. See services/data-collection/src/ for full route definitions.