API Reference

8 endpoints. Stable v1 contract. All requests require Authorization: Bearer <key>.

Authentication

Every API call must include your key in the Authorization header:

Authorization: Bearer axt_live_<your-key>

On missing or invalid key:

{
  "error": "unauthorized",
  "message": "Missing or invalid API key. Include header: Authorization: Bearer axt_live_<your-key>",
  "docs": "https://axistruth.com/docs/api#authentication"
}

Rate limits

Limits are monthly (calendar month, UTC). On breach:

{
  "error": "rate_limit_exceeded",
  "message": "Rate limit exceeded. Free tier: 100 calls/mo. Pro: 50,000 calls/mo.",
  "used": 100,
  "limit": 100,
  "resetAt": 1748822400
}

Response headers on every call:

HeaderDescription
X-RateLimit-LimitMonthly call ceiling for your tier
X-RateLimit-RemainingCalls remaining this month
X-RateLimit-ResetUnix timestamp of next quota reset
Retry-AfterPresent on 429 only — seconds until quota resets

Error codes

HTTP statuserror fieldWhen
400bad_requestSchema validation failure or malformed JSON
401unauthorizedMissing or invalid API key
422validation_failedField-level constraint violation (with field pointer)
429rate_limit_exceededMonthly quota exhausted
500internal_server_errorUnexpected server error
503service_unavailableBilling service not configured (non-production environments)

POST /v1/optimize-portfolio

POST /v1/optimize-portfolio

Allocates portfolio weights using Hierarchical Risk Parity (HRP). Requires at least 2 assets and 2 time-period rows. All auth + rate-limit rules apply.

Request body

FieldTypeRequiredDescription
returnsnumber[][]YesReturn series. Outer array = time periods (min 2). Inner array = assets (min 1, must match assetNames length).
assetNamesstring[]YesAsset labels in the same order as the inner return arrays.
useShrinkagebooleanNoApply Ledoit-Wolf shrinkage to the covariance matrix. Default: false. Recommended when n_periods < 3 * n_assets.

Response (200)

{
  "weights": { "SPY": 0.38, "AGG": 0.62 },
  "method": "hrp",
  "riskContributions": { "SPY": 0.5, "AGG": 0.5 }
}

Example (curl)

curl -X POST https://api.axistruth.com/v1/optimize-portfolio \
  -H "Authorization: Bearer axt_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "returns": [
      [0.01, -0.005, 0.002],
      [0.02,  0.010, -0.003],
      [-0.01, 0.005,  0.004],
      [0.005, -0.002, 0.001],
      [0.015, 0.008, -0.002]
    ],
    "assetNames": ["SPY", "AGG", "GLD"]
  }'

Example (Python)

import requests

resp = requests.post(
    "https://api.axistruth.com/v1/optimize-portfolio",
    headers={"Authorization": "Bearer axt_live_YOUR_KEY_HERE"},
    json={
        "returns": [[0.01, -0.005], [0.02, 0.010], [-0.01, 0.005],
                    [0.005, -0.002], [0.015, 0.008]],
        "assetNames": ["SPY", "AGG"],
    },
)
data = resp.json()
print(data["weights"])  # {'SPY': 0.38, 'AGG': 0.62}

POST /v1/size-position

POST /v1/size-position

Computes Kelly-criterion position size. Two modes: continuous (variance-based) and discrete (win-probability-based). Applies a configurable cap (default 25%) to prevent over-sizing.

Request body — continuous mode

FieldTypeRequiredDescription
expectedReturnnumberYesAnnualized expected excess return (e.g., 0.12 for 12%).
variancenumberYesAnnualized variance (e.g., 0.04 for 20% vol).
riskFreeRatenumberNoRisk-free rate. Default: 0.
fractionnumberNoFractional Kelly multiplier (0, 1]. Default: 0.25.
maxDrawdownFractionnumberNoHard cap on recommended fraction. Default: 0.25.

Request body — discrete mode

FieldTypeRequiredDescription
mode"discrete"YesSelects discrete Kelly formula.
winProbabilitynumberYesProbability of a winning trade [0, 1].
winLossRationumberYesRatio of average win to average loss (e.g., 2.0 = win twice as much as you lose).
fractionnumberNoFractional Kelly multiplier. Default: 0.25.
maxDrawdownFractionnumberNoHard cap. Default: 0.25.

Response (200)

{
  "kellyOptimal": 3.0,
  "recommended": 0.75,
  "clamped": true,
  "cap": 0.25
}

kellyOptimal is the raw f* before cap. recommended is min(kellyOptimal * fraction, maxDrawdownFraction). clamped: true means the cap was binding.

Example (curl — continuous)

curl -X POST https://api.axistruth.com/v1/size-position \
  -H "Authorization: Bearer axt_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{"expectedReturn": 0.12, "variance": 0.04, "fraction": 0.5}'

Example (curl — discrete)

curl -X POST https://api.axistruth.com/v1/size-position \
  -H "Authorization: Bearer axt_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{"mode": "discrete", "winProbability": 0.6, "winLossRatio": 1.0, "fraction": 1.0}'

POST /v1/factor-score

POST /v1/factor-score

Scores assets on up to 4 factors: momentum, low-volatility, quality, value. Returns raw scores and cross-sectional ranks.

Request body

FieldTypeRequiredDescription
assetsAsset[]YesArray of assets. Each asset must have ticker and priceHistory. See Asset schema below.
factorsstring[]NoSubset of ["momentum", "lowVol", "quality", "value"]. Default: all four.
lookbackintegerNoLookback window in trading days for momentum. Default: 252.

Asset object

FieldTypeRequiredDescription
tickerstringYesIdentifier for the asset (used as key in response).
priceHistorynumber[]YesDaily close prices, oldest first. Min length: lookback + 1.
roenumberNoReturn on equity (for quality factor).
debtToEquitynumberNoDebt-to-equity ratio (for quality factor).
bookValuePerSharenumberNoBook value per share (for value factor).
pricenumberNoCurrent price (for value factor — used with bookValuePerShare to compute P/B).

Response (200)

{
  "scores": {
    "SPY": { "momentum": 0.82, "lowVol": 0.41, "quality": 0.67, "value": 0.55 },
    "GLD": { "momentum": 0.63, "lowVol": 0.78, "quality": null,  "value": null  }
  },
  "ranks": {
    "SPY": { "momentum": 1, "lowVol": 2, "quality": 1, "value": 1 },
    "GLD": { "momentum": 2, "lowVol": 1, "quality": null, "value": null }
  }
}

null values indicate the factor could not be computed for that asset (insufficient data or missing fields).

Example (curl)

curl -X POST https://api.axistruth.com/v1/factor-score \
  -H "Authorization: Bearer axt_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "assets": [
      {
        "ticker": "AAPL",
        "priceHistory": [<253 daily prices, oldest first>],
        "roe": 0.15,
        "debtToEquity": 0.3,
        "bookValuePerShare": 4.0,
        "price": 180.0
      }
    ],
    "factors": ["momentum", "lowVol"]
  }'

POST /v1/stress-test

POST /v1/stress-test

Monte Carlo stress test across up to 3 historical regime scenarios (GFC 2008, COVID 2020, Dotcom 2000). Returns max drawdown, VaR 95, and CVaR 95 per scenario.

Request body

FieldTypeRequiredDescription
weightsRecord<string, number>YesAsset weights. Must sum to 1.0 (±1e-6 tolerance).
expectedReturnsRecord<string, number>YesDaily expected returns per asset.
dailyVolsRecord<string, number>YesDaily volatilities per asset (annualize by multiplying by √252).
correlationMatrixnumber[][]YesN×N correlation matrix. Must be positive semi-definite.
assetOrderstring[]YesAsset names defining row/column order of correlationMatrix.
scenariosstring[]NoSubset of ["GFC_2008", "COVID_2020", "DOTCOM_2000"]. Default: all three.
simulationsintegerNoNumber of Monte Carlo paths. Default: 1000. Min: 50.

Response (200)

{
  "scenarios": [
    {
      "name": "GFC_2008",
      "durationDays": 127,
      "maxDrawdown": -0.312,
      "var95": -0.187,
      "cvar95": -0.243
    },
    {
      "name": "COVID_2020",
      "durationDays": 23,
      "maxDrawdown": -0.198,
      "var95": -0.124,
      "cvar95": -0.156
    }
  ]
}

All return values are in decimal form (e.g., -0.312 = -31.2% drawdown). CVaR is the expected loss in the worst-5% of simulations; it is always ≤ VaR 95.

Example (curl)

curl -X POST https://api.axistruth.com/v1/stress-test \
  -H "Authorization: Bearer axt_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "weights":          { "SPY": 0.6, "AGG": 0.4 },
    "expectedReturns":  { "SPY": 0.0003, "AGG": 0.0002 },
    "dailyVols":        { "SPY": 0.01,   "AGG": 0.008 },
    "correlationMatrix": [[1.0, 0.4], [0.4, 1.0]],
    "assetOrder":       ["SPY", "AGG"],
    "scenarios":        ["GFC_2008", "COVID_2020"],
    "simulations":      500
  }'

GET /v1/usage

GET /v1/usage

Returns current-month call count vs tier limit. Use this to build quota tracking into your application.

Response (200)

{
  "used": 47,
  "limit": 100,
  "tier": "free",
  "resetAt": 1748822400
}

Example (curl)

curl https://api.axistruth.com/v1/usage \
  -H "Authorization: Bearer axt_live_YOUR_KEY_HERE"

Billing endpoints

The three billing endpoints below are for server-to-server use. They do not require an API Bearer token but have their own validation requirements.

POST /v1/billing/checkout

POST /v1/billing/checkout

Creates a Stripe Checkout session for upgrading a tenant to a paid tier.

FieldTypeRequiredDescription
tenantIdstringYesYour internal tenant identifier.
tier"pro" | "quant" | "enterprise"YesTarget tier for the checkout session.
{
  "url": "https://checkout.stripe.com/pay/cs_test_..."
}

POST /v1/billing/webhook

POST /v1/billing/webhook

Stripe webhook receiver. This endpoint is for Stripe to call, not your application. Requires the stripe-signature header with a valid HMAC. Configure your Stripe webhook to point here.

Important: Stripe must deliver the raw request body exactly as received. Any body-parsing middleware that re-serializes JSON will break HMAC verification and cause 400 errors on every event.

Handled events:

GET /health

GET /health

Uptime probe. No authentication required. Returns 200 when the server is operational.

{ "status": "ok", "version": "0.2.0" }

Returns 503 if a critical dependency is unavailable.