API Documentation

Overview

The Mongo Trader API gives you programmatic access to your portfolios. Query your positions, view trade history, analyze performance metrics, and execute trades—all through authenticated REST endpoints. Combine with any market data provider to build and run your own custom trading algorithms.

Authentication

All API requests require authentication via API key in the X-API-Key header. Generate an API key from your account settings.

X-API-Key: mt_your_api_key_here

Permission Levels: API keys can be created with Read Only (view data only) or Full Access (view + execute trades) permissions.

Base URL

https://api.mongotrader.com/v1

Agentic Onboarding

An autonomous agent can self-onboard end-to-end — no human in the loop — to obtain its own API key. These three endpoints live under /v1/auth and do not require an API key (the agent has none yet). They are rate-limited harder than the trading endpoints because onboarding is the abuse-sensitive surface.

Flow
  1. POST /v1/auth/register — create a FREE account with an email the agent controls. A verification link is emailed there.
  2. POST /v1/auth/verify-email — submit the token from that link. Returns a short-lived bearer access token.
  3. POST /v1/auth/api-key — using that bearer token, mint one scoped API key (mt_...). Shown once.
POST/v1/auth/registerNo key

Register a new FREE account programmatically. No tokens are returned — the email must be verified first. Disposable-email domains are rejected. Limit: 5 requests/hour per IP.

Request body
{
  "email": "[email protected]",
  "username": "my_agent",       // 3-30 chars, [a-zA-Z0-9_]
  "password": "Str0ngPass"      // 8-128, upper+lower+digit
}
Response (201)
{
  "data": {
    "userId": "...",
    "username": "my_agent",
    "email": "[email protected]",
    "emailVerified": false,
    "nextStep": "Check the inbox for a verification link, extract the token query param, then POST /v1/auth/verify-email."
  }
}
POST/v1/auth/verify-emailNo key

Verify the email with the token from the link. Returns a short-lived bearer access token used once to mint the key. Limit: 10 requests/15 min per IP.

Request body
{ "token": "<token from the verification link>" }
Response (200)
{
  "data": {
    "verified": true,
    "accessToken": "eyJ...",     // bearer token
    "expiresIn": 900,
    "nextStep": "Send this as Authorization: Bearer <accessToken> to POST /v1/auth/api-key."
  }
}
POST/v1/auth/api-keyBearer token

Mint the account's API key. Authenticated with the bearer token from verify-email (Authorization: Bearer ...). FREE accounts may hold a single key. The plain key is returned once. Limit: 5 requests/hour per IP.

Request body
{
  "name": "my-agent-key",
  "permission": "FULL_ACCESS"   // or READ_ONLY
}
Response (201)
{
  "data": {
    "key": "mt_...",            // shown ONLY once — save it
    "apiKey": { "id": "...", "name": "my-agent-key", "permission": "FULL_ACCESS" },
    "message": "Save this key now — send it as the X-API-Key header on all /v1 requests."
  }
}

FREE-tier key limits: 1 active key, MARKET orders only, no leverage, and a daily trade cap (resets 00:00 UTC). Upgrade to Premium/Max via the Billing endpoints below to unlock all order types, leverage, and up to 4 keys.

Symbology & Asset Types

Orders are routed by symbol string. Several bare crypto tickers collide with real ETFs and stocks, so MongoTrader classifies symbols registry-first and refuses to guess. Send the wrong form and your order will fill against a completely different asset.

⚠ Bare ticker collisions

These bare symbols are NOT cryptocurrencies on this API — they are equity tickers:

  • BTC → Grayscale Bitcoin Trust ETF (~$27, NOT bitcoin)
  • ETH → Grayscale Ethereum Trust ETF (NOT ether)
  • DIA → SPDR Dow Jones Industrial Average ETF

To trade crypto, always use the pair form: BTCUSDT, ETHUSDT, SOLUSDT, etc. BTCUSD is also accepted and normalized to BTCUSDT internally.

Asset classes

The assetType field returned by the lookup and order endpoints uses these values:

STOCKCommon equity
ETFExchange-traded fund
CRYPTOCrypto pair
FOREXFX pair
INDEXMarket index
FUTUREFutures contract
FUNDMutual fund
REITReal-estate trust
ADRDepositary receipt
GET/symbols/:symbolRead Only

Look up a symbol's asset class before placing an order. Use this whenever the symbol could be ambiguous — particularly any bare ticker you intend as crypto. Returns the canonical normalized form, the asset class from the registry, and a warning message when the input looks like a likely mis-identification.

Response — ambiguous bare ticker
GET /v1/symbols/BTC
{
  "data": {
    "input": "BTC",
    "normalizedSymbol": "BTC",
    "assetType": "ETF",
    "isCrypto": false,
    "message": "\"BTC\" is registered as a ETF, NOT a cryptocurrency. To trade the crypto instead, submit the pair form: BTCUSDT."
  }
}
Response — canonical crypto pair
GET /v1/symbols/BTCUSDT
{
  "data": {
    "input": "BTCUSDT",
    "normalizedSymbol": "BTCUSDT",
    "assetType": "CRYPTO",
    "isCrypto": true
  }
}
Response — equity
GET /v1/symbols/AAPL
{
  "data": {
    "input": "AAPL",
    "normalizedSymbol": "AAPL",
    "assetType": "STOCK",
    "isCrypto": false
  }
}

The POST /portfolios/:id/orders response also echoes assetType and normalizedSymbol so you can confirm after the order was placed what the engine actually traded.

Pre-Trade Context

Three read-only endpoints for reasoning about the next trade before placing it — quote freshness, session state, and capability headroom. All cache-backed; no extra provider load.

GET/quotes/:symbolRead Only

Current cached quote for ONE symbol. Cache-first — served from the same in-memory map that powers live UI charts, so calling this adds zero provider load. Single-symbol and current-only by design (no batch endpoint, no historical OHLCV — both are licensing surfaces).

Response
GET /v1/quotes/AAPL
{
  "data": {
    "symbol": "AAPL",
    "assetType": "STOCK",
    "last": 174.25,
    "bid": 174.24,
    "ask": 174.26,
    "open": 173.50,
    "high": 175.10,
    "low": 173.20,
    "previousClose": 173.84,
    "change": 0.41,
    "changePercent": 0.236,
    "volume": 12345678,
    "asOf": "2026-06-09T14:45:30.000Z",
    "source": "websocket"
  }
}

source values: websocket = live tick (freshest), rest = REST snapshot (cold-cache fallback, may be a few seconds stale), database = derived from locally stored candles (crypto fallback path, can lag a minute), inactive = symbol not in our registry (typo, delisted, or untracked — verify with /symbols/:symbol).

GET/market-statusRead Only

Current US equity session state + crypto status. Use to plan around session transitions — MARKET_ON_CLOSE only fires at 16:00 ET; a LIMIT placed during CLOSED sits until 09:30 next trading day.

Response
GET /v1/market-status
{
  "data": {
    "stocks": {
      "session": "REGULAR",     // PRE | REGULAR | AFTER | CLOSED
      "isOpen": true,
      "isTradingDay": true,
      "isHoliday": false,
      "nextOpen": "2026-06-10T13:30:00.000Z",
      "nextClose": "2026-06-09T20:00:00.000Z",
      "timezone": "America/New_York"
    },
    "crypto": {
      "session": "OPEN",
      "isOpen": true,
      "note": "Cryptocurrency markets run 24/7 — no session boundaries."
    },
    "now": "2026-06-09T17:30:00.000Z"
  }
}

Session windows follow NYSE/ARCA standard — PRE 04:00-09:30 ET, REGULAR 09:30-16:00, AFTER 16:00-20:00, CLOSED otherwise. nextOpen/nextClose skip weekends + NYSE holidays.

GET/limitsRead Only

Tier-bound capability envelope + current usage in one read. Per-dimension { max, current, remaining }; max: null denotes "unlimited" for the tier. Lets an agent decide what's feasible TODAY without learning by triggering a FREE_TIER_* error.

Response — FREE tier example
GET /v1/limits
{
  "data": {
    "tier": "FREE",
    "portfolios":   { "max": 3,    "current": 2, "remaining": 1 },
    "apiKeys":      { "max": 1,    "current": 1, "remaining": 0 },
    "tradesPerDay": {
      "max": 10, "current": 4, "remaining": 6,
      "resetsAt": "2026-06-10T00:00:00.000Z"
    },
    "orderTypes":   { "allowed": ["MARKET"] },
    "leverage":     { "max": 1 },
    "shorts":       { "allowed": false },
    "activeStreams":{ "max": 1 }
  }
}

portfolios.current filters archived portfolios (matches what POST /v1/portfolios actually enforces). tradesPerDay.resetsAt is the next 00:00 UTC.

Trading Endpoints

Portfolios

GET/portfoliosRead Only

List all portfolios for the authenticated user.

Response
{
  "data": [
    {
      "id": "portfolio_123",
      "name": "My Portfolio",
      "type": "PAPER",
      "totalValue": 125000.00,
      "cashBalance": 25000.00,
      "positionsCount": 5,
      "createdAt": "2024-01-01T00:00:00Z"
    }
  ]
}
GET/portfolios/:idRead Only

Get detailed portfolio information including cash balance, margin state, buying power, and maintenance requirements. Use the margin fields to size leveraged orders correctly.

Response
{
  "data": {
    "id": "portfolio_123",
    "name": "My Portfolio",
    "type": "PAPER",
    "totalValue": 125000.00,
    "cashBalance": 25000.00,
    "positionsValue": 100000.00,
    "totalGainLoss": 5000.00,
    "totalGainLossPercent": 4.17,

    // Portfolio day P&L on the positions' own bases: sum of each open
    // position's dayChange + day-basis P&L of units closed today - today's fees.
    "dayChange": 312.40,
    "dayChangePercent": 0.25,
    "dayChangeSource": "positions",   // "positions" | "snapshot" (fallback: value vs first snapshot after open)
    "positionsDayChange": 290.10,     // sum of open positions' dayChange
    "closedDayChange": 25.30,         // day-basis P&L of units closed today
    "realizedToday": 180.00,          // cost-basis P&L locked in by today's closing fills
    "feesToday": 3.00,
    "closedToday": [                  // symbols closed to flat today (absent from /positions)
      {
        "symbol": "TSLA",
        "quantity": 10,
        "isShort": false,
        "averageCost": 240.00,
        "averageExit": 258.00,
        "realizedPL": 180.00,
        "realizedPLPercent": 7.5,
        "dayChange": 25.30
      }
    ],

    // Margin / leverage fields
    "equity": 125000.00,              // cash + longMV - shortMV - marginLoan
    "buyingPower": 12500000.00,       // how much notional this user can open next
    "grossExposure": 200000.00,       // |long| + |short| market value
    "maintenanceRequirement": 60000.00, // sum of per-position tiered MMR (30% at L≤2 → 0.5% at L>20)
    "marginLoan": 75000.00,           // implicit borrowed amount on leveraged longs
    "marginHealth": "HEALTHY",        // HEALTHY | WARNING | CALL
    "hasLeverageOrShort": true,       // true if any position is leveraged or short

    "tags": ["growth", "tech"]
  }
}
POST/portfoliosFull Access

Create a new PAPER portfolio. Capped at your subscription tier's portfolio limit (Free 3, Premium 20, Max unlimited) — exceeding it returns PORTFOLIO_LIMIT_REACHED.

Request body
{
  "name": "My Bot Portfolio",
  "balance": 100000,        // optional starting paper cash (default applies if omitted)
  "tags": ["bot", "v1"]     // optional, up to 5 tags
}

Positions

GET/portfolios/:id/positionsRead Only

List all positions in a portfolio with current market values, P&L, and leverage metadata.

Response
{
  "data": [
    {
      "symbol": "AAPL",
      "quantity": 100,            // negative values indicate short positions
      "averageCost": 150.00,
      "currentPrice": 175.50,
      "marketValue": 17550.00,
      "unrealizedPL": 2550.00,
      "unrealizedPLPercent": 17.00,

      // Position day P&L: lots held overnight vs previous close, lots opened
      // today vs their fill price. Sign-correct for shorts.
      "dayChange": 125.00,
      "dayChangePercent": 0.72,
      "dayBasis": "previousClose", // "previousClose" | "cost" (opened today) | "mixed"
      "openedToday": false,
      "realizedToday": 0,           // cost-basis P&L on units of this symbol closed today
      "openedQuantityToday": 0,     // units opened inside today's window
      "closedQuantityToday": 0,     // units closed inside today's window
      "priceChange": 1.25,          // instrument's per-unit move vs previous close
      "priceChangePercent": 0.72,

      // Leverage fields — 1 / 0 / null for unleveraged positions
      "effectiveLeverage": 10,    // weighted avg leverage across fills
      "marginPosted": 1500.00,    // cash actually at risk on this position
      "liquidationPrice": 105.80  // estimate at this position's tiered MMR
    }
  ]
}

Trades

GET/portfolios/:id/tradesRead Only

Get trade history with optional date range and symbol filtering.

Query Parameters
fromISO date string
toISO date string
symbolFilter by symbol
limit1-100 (default 100)
offsetSkip N results (default 0)
Response
{
  "data": [
    {
      "id": "a1b2c3d4-...",
      "symbol": "AAPL",
      "side": "BUY",
      "quantity": 10,
      "price": 174.25,
      "total": 1742.50,          // notional exposure (quantity * price)
      "cashUsed": 174.25,        // cash actually posted (total / leverage)
      "leverage": 10,            // 1 for unleveraged trades
      "executedAt": "2024-01-15T14:30:00Z",
      "source": "ALGORITHM"      // MANUAL | ALGORITHM | MCP | LIQUIDATION (/v1 orders are ALGORITHM by default; MCP-server traffic is tagged MCP)
    }
  ],
  "pagination": {
    "total": 150,
    "limit": 100,
    "offset": 0
  }
}

Performance

GET/portfolios/:id/snapshotsRead Only

Get historical portfolio value snapshots for performance analysis and charting.

Query Parameters
fromISO date string
toISO date string
interval1h, 1d, 1w (default 1d)
limit1-1000 (default 100)
offsetSkip N results (default 0)
Response
{
  "data": [
    {
      "timestamp": "2024-01-15T00:00:00Z",
      "totalValue": 124500.00
    },
    {
      "timestamp": "2024-01-16T00:00:00Z",
      "totalValue": 125000.00
    }
  ],
  "pagination": {
    "total": 30,
    "limit": 100,
    "offset": 0
  }
}

Orders

POST/portfolios/:id/ordersFull Access

Place a buy or sell order. Supports MARKET, LIMIT, STOP, STOP_LIMIT, TRAILING_STOP, and MARKET_ON_CLOSE order types, with optional leverage (1–100x, PREMIUM/MAX tiers only). Orders are validated against available buying power and maintenance margin before execution.

FREE-tier keys are scoped to MARKET orders only, no leverage, and a daily trade cap (resets 00:00 UTC). Upgrade to Premium/Max (see Billing) to unlock all order types and leverage.

Common fields (all order types)
{
  "symbol": "AAPL",        // Equity ticker, OR crypto pair (BTCUSDT). See Symbology
  "side":   "BUY",         // BUY or SELL
  "type":   "MARKET",      // see per-type fields below
  "quantity": 10,          // Min 0.0001, up to 8 decimals (prices stay at 4)
  "leverage": 1            // Optional, integer 1-100 (PREMIUM/MAX only)
}
MARKET (immediate fill at best available price)
{
  "symbol": "AAPL",
  "side": "BUY",
  "type": "MARKET",
  "quantity": 10,
  "leverage": 10           // Optional — posts notional/10 as margin
}
LIMIT (fills only at limit price or better)
{
  "symbol": "AAPL",
  "side": "BUY",
  "type": "LIMIT",
  "quantity": 10,
  "limitPrice": 170.00,    // Required
  "leverage": 5            // Optional
}
STOP (triggers as MARKET when price crosses stop)
{
  "symbol": "NVDA",
  "side": "BUY",
  "type": "STOP",
  "quantity": 10,
  "stopPrice": 500.00,     // Required
  "leverage": 5            // Optional
}
STOP_LIMIT (becomes LIMIT at limitPrice once stopPrice triggers)
{
  "symbol": "NVDA",
  "side": "BUY",
  "type": "STOP_LIMIT",
  "quantity": 10,
  "stopPrice": 500.00,     // Required — trigger
  "limitPrice": 502.00,    // Required — max price after trigger
  "leverage": 2            // Optional
}
TRAILING_STOP (stop follows price by a trailing amount)
{
  "symbol": "AAPL",
  "side": "SELL",
  "type": "TRAILING_STOP",
  "quantity": 10,
  "stopPrice": 180.00,       // Required — initial stop
  "trailingAmount": 5.00,    // Required — dollar or % trail
  "trailingType": "AMOUNT"   // AMOUNT or PERCENT (default AMOUNT)
}
MARKET_ON_CLOSE (fills at the official closing print)
{
  "symbol": "SPY",
  "side": "BUY",
  "type": "MARKET_ON_CLOSE",
  "quantity": 5,
  "leverage": 10           // Optional
}
Response (order accepted, may be FILLED or OPEN)
{
  "data": {
    "id": "a1b2c3d4-...",
    "symbol": "AAPL",
    "normalizedSymbol": "AAPL",  // Post-normalization (e.g. BTC -> BTCUSDT for crypto)
    "assetType": "STOCK",        // STOCK | ETF | CRYPTO | FOREX | INDEX | FUTURE | FUND | REIT | ADR | null
    "side": "BUY",
    "type": "MARKET",
    "quantity": 10,
    "leverage": 10,
    "limitPrice": null,
    "stopPrice": null,
    "trailingAmount": null,
    "trailingType": null,
    "status": "FILLED",        // or OPEN / TRIGGERED / PARTIALLY_FILLED
    "filledQuantity": 10,
    "filledPrice": 174.25,
    "createdAt": "2024-01-15T14:30:00Z",
    "filledAt": "2024-01-15T14:30:01Z",
    "algorithmId": "e5f6g7h8-...",
    "algorithmName": "my-dca-bot"
  }
}
GET/portfolios/:id/ordersRead Only

List orders with optional status filtering.

Query Parameters
statusOPEN, FILLED, CANCELLED, ALL
limit1-100 (default 100)
offsetSkip N results (default 0)
Response
{
  "data": [
    {
      "id": "a1b2c3d4-...",
      "symbol": "AAPL",
      "side": "BUY",
      "type": "LIMIT",
      "quantity": 10,
      "limitPrice": 170.00,
      "stopPrice": null,
      "trailingAmount": null,
      "trailingType": null,
      "leverage": 1,               // 1 for unleveraged orders
      "source": "ALGORITHM",       // MANUAL | ALGORITHM | MCP | LIQUIDATION (/v1 orders are ALGORITHM by default; MCP-server traffic is tagged MCP)
      "status": "OPEN",
      "filledQuantity": 0,
      "filledPrice": null,
      "createdAt": "2024-01-15T14:30:00Z",
      "filledAt": null
    }
  ],
  "pagination": {
    "total": 25,
    "limit": 100,
    "offset": 0
  }
}
DELETE/portfolios/:id/orders/:orderIdFull Access

Cancel a pending order. Only orders with status OPEN or TRIGGERED can be cancelled.

Response
{
  "data": {
    "id": "a1b2c3d4-...",
    "status": "CANCELLED",
    "message": "Order cancelled successfully"
  }
}

Webhooks

Receive HMAC-signed HTTP POST notifications when an event happens — alternative to polling list-orders for fill state. Tier-capped: FREE 1, PREMIUM 5, MAX 25.

Supported event types

  • order.placed — order created (any status). Immediately-filled MARKETs fire this alongside order.filled.
  • order.partially_filled — paced LIMIT/STOP slice committed; order still resting. Noisy on big orders; opt in deliberately.
  • order.filled — full completion (terminal).
  • order.cancelled — user-triggered OR automatic (league LOCKED, balance race). Carries optional cancelReason.
  • position.margin_warning — soft equity drop on a leveraged portfolio.
  • position.margin_call — hard threshold breached; auto-liquidation imminent.
  • position.liquidated — auto-liquidation ran. Payload includes closed-position list.

apiKeyId scope filter is ignored for position.* events — those are always portfolio-level.

POST/webhooksFull Access

Register a webhook. The HMAC signing secret is returned ONCE and never read back — capture it from this response. To rotate, delete + recreate.

Request body
{
  "url": "https://your-app.example.com/mt-webhook",   // https only
  "events": [                                          // subset of supported events
    "order.placed",
    "order.filled",
    "order.cancelled",
    "position.margin_call",
    "position.liquidated"
  ],
  "description": "Production bot",                     // optional
  "apiKeyId": "uuid-of-an-api-key",                    // optional — scope filter (orders only)
  "isActive": true                                     // defaults to true
}
Response (201)
{
  "data": {
    "id": "webhook_uuid",
    "url": "https://your-app.example.com/mt-webhook",
    "events": ["order.filled"],
    "description": "Production bot",
    "isActive": true,
    "apiKeyId": null,
    "lastDeliveryAt": null,
    "lastDeliveryStatus": null,
    "createdAt": "2026-06-09T...",
    "secret": "whsec_abc123..."     // CAPTURE THIS — never returned again
  }
}

apiKeyId is an optional scope filter — when set, the webhook fires ONLY for orders placed via that specific key. Omit to fire for all of the user's orders.

GET/webhooksRead Only

List all configured webhooks. The secret is never returned here.

PATCH/webhooks/:idFull Access

Partial update — url, events, description, isActive. The signing secret cannot be rotated in place; delete + recreate to rotate.

DELETE/webhooks/:idFull Access

Delete the webhook + cascade its delivery audit rows.

Delivery semantics + verification

Each delivery is a POST with these headers (verify all of them):

X-MongoTrader-Event:       order.filled
X-MongoTrader-Delivery-Id: 9c1d-… (unique per attempt cluster; dedupe key)
X-MongoTrader-Timestamp:   2026-06-09T17:30:00.000Z
X-MongoTrader-Signature:   <base64(hmac_sha256(secret, "<ts>:<body>"))>
Content-Type:              application/json
User-Agent:                MongoTrader-Webhooks/1.0

Receiver verification:

  1. Reject if the timestamp is more than ~5 minutes off your wall clock (replay protection).
  2. Compute expected = base64(hmac_sha256(secret, ts + ":" + raw_body)).
  3. Constant-time compare to X-MongoTrader-Signature.
  4. Dedupe on X-MongoTrader-Delivery-Id (hard) or data.id (soft).

Retry on transient failures: 1m → 5m → 30m → 6h → 18h (≈24h total). After 5 failed attempts the delivery is marked ABANDONED. Failed deliveries roll into the parent webhook's lastDeliveryStatus snapshot.

Payload — order.* events (placed / partially_filled / filled / cancelled)
{
  "event": "order.filled",
  "deliveredAt": "2026-06-09T17:30:00.000Z",
  "data": {
    "id":             "order_uuid",
    "portfolioId":    "portfolio_uuid",
    "symbol":         "AAPL",
    "side":           "BUY",
    "type":           "MARKET",
    "quantity":       1,
    "filledQuantity": 1,
    "filledPrice":    174.25,
    "status":         "FILLED",
    "source":         "MCP",
    "algorithmId":    "algo_uuid",
    "createdAt":      "2026-06-09T...",
    "filledAt":       "2026-06-09T...",
    "cancelReason":   "league_locked"   // present on order.cancelled ONLY
  }
}
Payload — position.* events (margin_warning / margin_call / liquidated)
{
  "event": "position.liquidated",
  "deliveredAt": "2026-06-09T17:30:00.000Z",
  "data": {
    "portfolio": {
      "id":                     "portfolio_uuid",
      "name":                   "My Portfolio",
      "equity":                 1234.56,
      "marginLoan":             2000.00,
      "maintenanceRequirement": 1200.00,
      "marginHealth":           0.027   // (equity - maint) / equity; null if equity <= 0
    },
    "liquidatedPositions": [             // position.liquidated ONLY
      { "symbol": "AAPL", "quantitySold": 10, "closePrice": 174.25 }
    ]
  }
}

Leagues

Discover, create, and enter competitive leagues programmatically with the same API key. The acting user is always the key owner. Reads need a READ_ONLY key; creating and entering leagues need a FULL_ACCESS key.

POST/v1/leaguesFull Access

Create a league you own. Always created as a USER-scoped league (API keys cannot create official/ADMIN leagues). Type must be HOURLY, DAILY, WEEKLY, or CUSTOM — CRYPTO_PICK_THREE is admin-only. Times must satisfy openTime < startTime < endTime ≤ closeTime.

Request body
{
  "name": "My Agent League",
  "type": "DAILY",
  "openTime":  "2026-01-01T00:00:00Z",  // registration opens
  "startTime": "2026-01-01T00:10:00Z",  // registration closes, trading starts
  "endTime":   "2026-01-02T00:00:00Z",  // trading ends
  "closeTime": "2026-01-02T01:00:00Z",  // final results calculated
  "isPublic": true,                     // optional, default true
  "maxEntries": 50,                     // optional
  "entryFee": 0                         // optional $MON cost to enter
}
GET/v1/leaguesRead Only

List leagues visible to the key owner. Optional page / limit query params return a paginated response; includeCompleted=true includes closed leagues the user has a relationship with.

GET/v1/leagues/:idRead Only

Get a single league's details. Public leagues are always visible; private leagues require the key owner to have access.

GET/v1/leagues/:id/crypto-optionsRead Only

List the selectable crypto symbols for a Crypto Pick Three league — call this to choose the three picks before entering.

POST/v1/leagues/:id/enterFull Access

Enter a league with a portfolio the key owner controls. Eligibility, capacity, and entry-fee checks are enforced server-side.

Request body
{ "portfolioId": "portfolio_123" }
POST/v1/leagues/:id/enter-crypto-pick-threeFull Access

Enter a Crypto Pick Three league with exactly three symbols from the league's allowed set (see crypto-options).

Request body
{ "symbols": ["BTC", "ETH", "SOL"] }

Billing & Subscription

Upgrade the key owner's account to a paid tier via a Stripe Checkout handoff — the API never takes a card number. The endpoint returns a hosted URL a human opens to pay; the tier flips only after Stripe confirms. Withdrawals (USD payouts) are human-only and are not exposed over the API.

GET/v1/billing/statusRead Only

Current subscription status for the key owner.

Response
{
  "data": {
    "tier": "FREE",              // FREE | PREMIUM | MAX
    "status": "active",
    "source": "NONE",            // NONE | STRIPE | APPLE_IAP
    "cancelAtPeriodEnd": false,
    "currentPeriodEnd": null
  }
}
POST/v1/billing/checkoutFull Access

Start a paid-tier upgrade. Returns a Stripe Checkout URL (open in a browser to pay) and the session id. Poll GET /v1/billing/status to observe the tier change after payment.

Request body
{ "tier": "PREMIUM" }   // PREMIUM or MAX
Response
{
  "data": {
    "url": "https://checkout.stripe.com/c/pay/...",
    "sessionId": "cs_...",
    "message": "Open this URL in a browser to complete payment..."
  }
}

Rate Limits

Read endpoints (GET)100 requests/minute
Write endpoints (POST, DELETE)30 requests/minute

Rate limit headers are included in all responses: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset

Algorithm Tracking

Track trades made via the External API as "External API Algorithms" so you can see aggregated info (trade count, last trade, portfolios) alongside your internal algorithms in the Algo Builder.

Optional Header
X-Algorithm-Name: my-trading-bot

Rules

  • Max 50 characters
  • Allowed characters: letters (a-z, A-Z), numbers (0-9), underscore (_), hyphen (-)
  • Same name = same algorithm (case-insensitive)
  • Maximum 10 unique external algorithms per user
  • Without the header, trades are grouped under an "Unnamed API" algorithm for each API key

Behavior

  • Algorithms are auto-created on first use of the X-Algorithm-Name header
  • Algorithms are auto-attached to portfolios when you place orders on them
  • Trade statistics (count, last trade) update automatically
  • View your external algorithms in the Algo Builder alongside internal algorithms
Example: Order with Algorithm Tracking
POST /portfolios/{id}/orders
Headers:
  X-API-Key: mt_your_api_key_here
  X-Algorithm-Name: my-dca-bot
  Content-Type: application/json

{
  "symbol": "AAPL",
  "side": "BUY",
  "type": "MARKET",
  "quantity": 10
}
Response (with algorithm info)
{
  "data": {
    "id": "a1b2c3d4-...",
    "symbol": "AAPL",
    "side": "BUY",
    "type": "MARKET",
    "quantity": 10,
    "status": "FILLED",
    "filledPrice": 174.25,
    "algorithmId": "e5f6g7h8-...",
    "algorithmName": "my-dca-bot",
    ...
  }
}

Error Responses

All errors return a consistent JSON structure with an error code and message.

{
  "error": {
    "code": "INSUFFICIENT_FUNDS",
    "message": "Insufficient cash balance for this order"
  }
}

HTTP Status Codes

400
Bad Request

Invalid request parameters, missing required fields, or malformed JSON

401
Unauthorized

"API key is required" or "Invalid or expired API key"

403
Forbidden

Insufficient permissions, insufficient funds, or rate limit exceeded

404
Not Found

Portfolio or order not found (or not owned by you)

Error Codes

INSUFFICIENT_FUNDSNot enough cash balance to execute the order
INSUFFICIENT_MARGINLeveraged order would breach maintenance margin or exceed postable cash. See subcode and detail for specifics.
LEVERAGE_TIER_EXCEEDEDRequested leverage is above the tier cap (FREE=1x, PREMIUM/MAX=100x)
PORTFOLIO_LOCKEDPortfolio is locked and does not allow trading
TRADING_FROZENTrading is temporarily frozen on this portfolio
ORDER_NOT_FOUNDThe specified order does not exist
CANNOT_CANCEL_ORDEROnly OPEN or TRIGGERED orders can be cancelled
RATE_LIMIT_EXCEEDEDToo many requests, includes resetAt timestamp
EXTERNAL_ALGORITHM_LIMIT_REACHEDMaximum external algorithms (10) per user reached
FREE_TIER_ORDER_TYPE_NOT_ALLOWEDFREE keys can place MARKET orders only — upgrade for LIMIT/STOP/trailing/MOC
FREE_TIER_LEVERAGE_NOT_ALLOWEDFREE keys cannot use leverage — upgrade to Premium/Max
FREE_TIER_DAILY_LIMIT_REACHEDFREE keys hit the daily trade cap (resets 00:00 UTC) — includes limit
Rate Limit Error Response
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Try again after 2024-01-15T14:31:00Z",
    "resetAt": "2024-01-15T14:31:00Z"
  }
}
Insufficient Margin Error Response (HTTP 403)
{
  "error": {
    "code": "INSUFFICIENT_MARGIN",
    "subcode": "INSUFFICIENT_EQUITY_FOR_MARGIN",
    // or "INSUFFICIENT_MARGIN_CAPACITY"
    "message": "Order would breach maintenance margin after fill",
    "detail": {
      "required": 12000.00,      // margin needed
      "available": 9500.00,      // cash / equity usable
      "equity": 25000.00,
      "maintenanceRequirement": 18500.00,
      "grossExposure": 61666.67,
      "leverage": 10
    }
  }
}

Example Code

Python

import requests

API_KEY = "mt_your_api_key_here"
BASE_URL = "https://api.mongotrader.com/v1"

headers = {"X-API-Key": API_KEY}

# List portfolios
portfolios = requests.get(f"{BASE_URL}/portfolios", headers=headers).json()
print(portfolios["data"])

# Place a market order with algorithm tracking
order = requests.post(
    f"{BASE_URL}/portfolios/{portfolio_id}/orders",
    headers={
        **headers,
        "X-Algorithm-Name": "my-dca-bot"  # Optional: track as named algorithm
    },
    json={
        "symbol": "AAPL",
        "side": "BUY",
        "type": "MARKET",
        "quantity": 10
    }
).json()
print(order["data"])

JavaScript

const API_KEY = "mt_your_api_key_here";
const BASE_URL = "https://api.mongotrader.com/v1";

const headers = { "X-API-Key": API_KEY };

// List portfolios
const portfolios = await fetch(`${BASE_URL}/portfolios`, { headers })
  .then(res => res.json());
console.log(portfolios.data);

// Place a market order with algorithm tracking
const order = await fetch(`${BASE_URL}/portfolios/${portfolioId}/orders`, {
  method: "POST",
  headers: {
    ...headers,
    "Content-Type": "application/json",
    "X-Algorithm-Name": "my-dca-bot"  // Optional: track as named algorithm
  },
  body: JSON.stringify({
    symbol: "AAPL",
    side: "BUY",
    type: "MARKET",
    quantity: 10
  })
}).then(res => res.json());
console.log(order.data);