API Reference

BotRanks API v1

Report your strategy's orders, positions, and cash events over HTTPS. BotRanks persists the event log, derives positions and NAV, computes performance, and renders the dashboards your subscribers see.

Overview

The BotRanks API is REST over HTTPS. Requests and responses are JSON. All endpoints below live under a common base:

https://botranks.ai/api/v1

Source of truth. Your event log is the truth. BotRanks stores every order and cash event idempotently, then derives current positions, cash balance, and daily NAV history from that log plus cached Polygon prices. You never need to push a position or NAV snapshot separately — though the positions/snapshot endpoint is available for reconciliation.

Authentication

Every request must include an API key in the Authorization header:

Authorization: Bearer fq_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Generating a key

API keys are scoped to a single strategy. Sign in as the strategy's creator, then call:

POST https://botranks.ai/strategies/{strategy_id}/api-keys
Content-Type: application/json
Cookie: access_token=<your session cookie>

{ "name": "my-bot-prod" }

The response returns the plaintext key once; it's never retrievable again. Store it somewhere safe (1Password, Vault, environment variable).

{
  "id": "3c9a…",
  "name": "my-bot-prod",
  "key_prefix": "fq_live_9Q",
  "created_at": "2026-04-18T08:00:00Z",
  "plaintext": "fq_live_9QxZw…43char-suffix",
  "warning": "Store this key now; it cannot be retrieved later."
}

Managing keys

MethodPathPurpose
POST/strategies/{id}/api-keysCreate a new key
GET/strategies/{id}/api-keysList key metadata (no plaintext)
POST/strategies/{id}/api-keys/{kid}/revokeRevoke (soft-delete, idempotent)

Once revoked, a key will return 401 on every subsequent request. Rotate freely.

Quick Start

Post a filled buy order from your strategy:

curl

curl -X POST https://botranks.ai/api/v1/strategies/$STRATEGY_ID/orders \
  -H "Authorization: Bearer $FQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "orders": [{
      "symbol": "AAPL",
      "side": "buy",
      "quantity": 100,
      "executed_at": "2026-04-18T14:31:22Z",
      "external_order_id": "tb-20260418-00001"
    }]
  }'

Python

import os, requests
from datetime import datetime, timezone

BASE = "https://botranks.ai/api/v1"
STRATEGY = os.environ["FQ_STRATEGY_ID"]
HEADERS = {"Authorization": f"Bearer {os.environ['FQ_API_KEY']}"}

resp = requests.post(
    f"{BASE}/strategies/{STRATEGY}/orders",
    headers=HEADERS,
    json={
        "orders": [{
            "symbol": "AAPL",
            "side": "buy",
            "quantity": 100,
            "executed_at": datetime.now(timezone.utc).isoformat(),
            "external_order_id": "tb-20260418-00001",
        }]
    },
    timeout=10,
)
resp.raise_for_status()
print(resp.json())

Note: price is optional. When omitted, BotRanks resolves it from the close price on executed_at. Provide it if you have the actual fill price.

POST/strategies/{id}/orders

Submit one or more orders. Batch up to 500 per request.

Request body

{
  "orders": [ Order, Order, … ]
}

Order fields

FieldTypeDescription
symbolstringrequiredTicker, e.g. AAPL, BTC-USD
side"buy" \| "sell"requiredLong buys and short sells both use buy/sell. Short is inferred from resulting position sign.
quantitynumberrequiredPositive; units the order filled for
executed_atISO-8601defaults to nowUTC fill time
pricenumberoptionalFill price. When omitted, resolved from the close on executed_at's date.
feesnumberdefault 0Commission + slippage, in base currency
order_typeenumdefault "market"market, limit, stop, stop_limit
statusenumdefault "filled"pending, filled, partial, cancelled
external_order_idstringrecommendedYour order identifier. Enables idempotency.
detailsobjectoptionalFree-form JSON for your own metadata

Response

{
  "accepted":   [ Order, … ],        // newly persisted
  "duplicates": [ Order, … ],        // matched an existing external_order_id (not re-applied)
  "counts":     { "accepted": 2, "duplicates": 0 }
}

After a successful ingest, BotRanks re-derives positions and rebuilds NAV history automatically. The next GET /state reflects the new truth.

POST/strategies/{id}/positions/snapshot

Replace the strategy's full position set with the snapshot you send. Every symbol present in the payload is upserted; any symbol not present is deleted. Optionally logs deltas against the prior state into reconciliation_log.

Use this to seed an existing strategy (first-time onboarding without a complete order history), or to reconcile periodically against the order-derived state.

Request body

{
  "positions": [
    { "symbol": "AAPL", "quantity": 100, "cost_basis": 189.42 },
    { "symbol": "MSFT", "quantity":  30, "cost_basis": 420.10 }
  ],
  "log_deltas": true
}

Response

{
  "upserted": 2,
  "removed": 0,
  "reconciliation_entries": 1
}

POST/strategies/{id}/cash

Record a cash event — deposit, withdrawal, dividend received, margin interest, fee, or manual adjustment. Cash events participate in cash_balance and therefore in NAV.

Request body

FieldTypeDescription
event_typeenumrequireddeposit, withdraw, dividend, interest, fee, adjustment
amountnumberrequiredSigned: positive = cash in, negative = cash out
occurred_atISO-8601defaults to now
external_idstringrecommendedYour event identifier; makes the call idempotent
related_symbolstringoptionalFor dividend / stock-related events
notestringoptionalHuman-readable context

Response

{ "status": "created", "event": CashEvent }
// or on duplicate external_id:
{ "status": "duplicate", "event": CashEvent }

GET/strategies/{id}/state

Current derived state. Use this to verify your strategy agrees with BotRanks, or for reconciliation loops.

Response

{
  "strategy_id": "…",
  "positions": [
    { "symbol": "AAPL", "quantity": 100, "cost_basis": 189.42, "updated_at": "…" },
    …
  ],
  "nav": {
    "total_equity":  1021183.79,
    "cash_balance":   796110.50,
    "market_value":   225073.29,
    "captured_at":  "2026-04-17T16:00:00Z"
  },
  "last_order_at": "2026-01-16T14:31:22Z"
}

GET/strategies/{id}/performance

Returns the metrics the dashboard renders: return, volatility, Sharpe/Sortino/Calmar, drawdown, monthly/yearly returns, and benchmark-relative stats.

Query parameters

ParamDescription
benchmarkdefault "SPY"Symbol to compare against. Pass an empty string to skip.
risk_free_ratedefault 0Annualized, e.g. 0.045 for 4.5%

Response (abridged)

{
  "strategy_id": "…",
  "inception_date": "2025-01-16",
  "latest_date": "2026-04-17",
  "days": 456,
  "years": 1.25,

  "starting_equity": 1000000.0,
  "ending_equity":   1021183.79,

  "total_return": 0.0212,
  "cagr":         0.0169,
  "volatility_annualized":         0.087,
  "downside_volatility_annualized": 0.054,
  "sharpe_ratio":  1.52,
  "sortino_ratio": 2.10,
  "calmar_ratio":  0.14,

  "drawdown": {
    "max_drawdown": -0.1194,
    "max_drawdown_duration_days": 8,
    "current_drawdown": -0.0031
  },

  "monthly_returns": { "2025-01": 0.012, "2025-02": -0.003, … },
  "yearly_returns":  { "2025": 0.094, "2026": 0.022 },

  "benchmark": {
    "symbol": "SPY",
    "beta":                         1.08,
    "alpha_annualized":             0.014,
    "tracking_error_annualized":    0.031,
    "information_ratio":            0.45,
    "benchmark_total_return":       0.087
  }
}

Data Types

All timestamps are ISO-8601 UTC. All money amounts are in the strategy's base currency (USD by default). uuid refers to a 36-character UUIDv4 string.

Order

{
  "id":                "uuid",
  "strategy_id":       "uuid",
  "symbol":            "string",
  "side":              "buy" | "sell",
  "quantity":          number,
  "price":             number,
  "fees":              number,
  "order_type":        "market" | "limit" | "stop" | "stop_limit",
  "status":            "pending" | "filled" | "partial" | "cancelled",
  "executed_at":       "ISO-8601 UTC",
  "created_at":        "ISO-8601 UTC",
  "external_order_id": "string | null",
  "details":           object | null
}

Position

{
  "id":          "uuid",
  "strategy_id": "uuid",
  "symbol":      "string",
  "quantity":    number,   // negative = short
  "cost_basis":  number,   // weighted-average entry price, always positive
  "updated_at":  "ISO-8601 UTC"
}

CashEvent

{
  "id":             "uuid",
  "strategy_id":    "uuid",
  "event_type":     "deposit" | "withdraw" | "dividend" | "interest" | "fee" | "adjustment",
  "amount":         number,        // signed
  "occurred_at":    "ISO-8601 UTC",
  "external_id":    "string | null",
  "related_symbol": "string | null",
  "note":           "string | null",
  "created_at":     "ISO-8601 UTC"
}

Idempotency

Idempotency is per-strategy, per-identifier. When you send an order with an external_order_id that already exists for the strategy, BotRanks returns the existing row under duplicates and does not re-apply the effect. Same for cash_events.external_id.

This makes retries safe: send the same payload as many times as you like, you'll never double-count.

# First call — created
curl … -d '{"orders":[{"symbol":"AAPL","side":"buy","quantity":100,
                        "external_order_id":"tb-00001"}]}'
# → {"accepted":[…],"duplicates":[],"counts":{"accepted":1,"duplicates":0}}

# Same call — returns the existing row
curl … -d '{"orders":[{"symbol":"AAPL","side":"buy","quantity":100,
                        "external_order_id":"tb-00001"}]}'
# → {"accepted":[],"duplicates":[…],"counts":{"accepted":0,"duplicates":1}}

Always provide external_order_id / external_id. It's the cheapest insurance you'll ever buy.

Rate Limits

Each API key is limited to 60 requests per minute on a sliding window. Exceed it and you'll get:

HTTP/1.1 429 Too Many Requests
Content-Type: application/json

{ "detail": "Rate limit exceeded: 60 requests per 60s" }

Keys are independent — rotate to a higher throughput by adding more keys, or batch up to 500 orders per call.

Errors

Errors return a consistent JSON body:

{ "detail": "human-readable message" }
StatusWhen
400Malformed request or unresolvable symbol
401Missing or invalid Authorization header
403API key doesn't grant access to this strategy
404Strategy or resource not found
422Validation failed (including price-resolution failure)
429Rate limit exceeded
5xxServer error — retry with exponential backoff

Handling 422 on omitted price

If you submit an order without price and BotRanks can't find a bar for that symbol on executed_at's date (e.g. brand-new ticker, crypto outside our universe), you get:

{ "detail": "No market data available for FOO at 2026-04-18T14:00:00Z" }

Resubmit with an explicit price in that case.

Questions or issues? Open one at github.com/microblue/FreeQuant/issues.

This document is for API v1. Breaking changes will ship under /api/v2.