TESTUDO / DOCS

API Reference

REST endpoints, WebSocket channels, and authentication.

The Testudo backend exposes a REST API on port 8080 and a WebSocket server on port 4000. All REST endpoints are under the /api/v1 base path.

Authentication

Testudo uses Sign In with Ethereum (SIWE) for authentication. There are two auth flows:

  1. Get nonce: GET /api/v1/auth/nonce returns a one-time nonce
  2. Sign message: Client constructs an EIP-4361 message and signs it with the user’s wallet
  3. Verify: POST /api/v1/auth/verify-siwe with { "message": "...", "signature": "0x..." }
  4. Server sets HttpOnly cookies: access_token (15 min TTL) and refresh_token (7 day TTL)
  5. Refresh: POST /api/v1/auth/refresh uses the cookie to issue new tokens

Cookies are Secure, HttpOnly, SameSite=Strict.

Extension Flow (Token-Based)

  1. Pair: User generates a 6-digit code on the Desk via POST /api/v1/auth/pair-extension
  2. Claim: Extension sends POST /api/v1/auth/extension-pair with { "code": "123456" }
  3. Server returns { "tokens": { "access_token": "...", "refresh_token": "...", "expires_in": 900 } }
  4. Refresh: POST /api/v1/auth/extension-refresh with { "refresh_token": "..." }

The extension sends tokens via Authorization: Bearer <token> header.

Session Management

EndpointMethodAuthDescription
/auth/nonceGETNoGet SIWE nonce
/auth/verify-siwePOSTNoVerify signature, issue tokens
/auth/refreshPOSTCookieRefresh web session
/auth/extension-refreshPOSTBodyRefresh extension tokens
/auth/pair-extensionPOSTYesGenerate 6-digit pairing code
/auth/extension-pairPOSTNoClaim tokens with pairing code
/auth/meGETYesGet current user
/auth/logoutPOSTYesRevoke current session
/auth/revoke-allPOSTYesRevoke all sessions

Health

EndpointMethodDescription
/healthGETLiveness probe (always 200)
/health/readyGETReadiness probe (checks DB + sidecar)
/health/sidecarGETCEX sidecar health status
/metricsGETPrometheus metrics (text/plain)

Exchange Accounts

All exchange endpoints require authentication.

EndpointMethodDescription
/exchangesGETList available exchanges
/exchanges/supportedGETList supported exchange IDs
/exchanges/accountsGETList user’s exchange accounts
/exchanges/accountsPOSTAdd exchange (API keys or wallet)
/exchanges/accounts/{id}DELETERemove exchange account
/exchanges/accounts/{id}/testPOSTTest exchange connection
/exchanges/accounts/{id}/balanceGETFetch live balance
/exchanges/accounts/{id}/positionsGETFetch open positions

Add Exchange Account

POST /api/v1/exchanges/accounts
Content-Type: application/json

{
  "exchange_name": "binance",
  "api_key": "your-api-key",
  "secret": "your-secret",
  "passphrase": "okx-only"
}

Credentials are validated by fetching the account balance. On success, a trade history import is automatically triggered.

Agent Wallet (Hyperliquid)

EndpointMethodDescription
/exchanges/agent-wallet/initPOSTGenerate agent wallet keypair
/exchanges/agent-wallet/approvePOSTApprove agent for trading
/exchanges/agent-wallet/{id}/revokeDELETERevoke agent wallet

Trade Execution

All trade endpoints require authentication.

EndpointMethodDescription
/tradesPOSTCreate trade with entry + SL + TP
/tradesGETList active trade groups
/trades/{id}GETGet trade group details
/trades/{id}DELETECancel entire trade group
/trades/{id}/slPUTUpdate stop-loss price
/trades/{id}/tpPUTUpdate take-profit targets
/trades/{id}/breakevenPUTToggle break-even protection
/trades/cleanupPOSTPurge stale ghost orders

Create Trade

POST /api/v1/trades
Content-Type: application/json

{
  "symbol": "BTC_USDT",
  "side": "buy",
  "entry_price": "50000.00",
  "quantity": "0.5",
  "stop_loss_price": "49000.00",
  "take_profit": [
    { "price": "51500.00", "quantity": "0.25", "order_type": "limit" },
    { "price": "53000.00", "quantity": "0.25", "order_type": "limit" }
  ],
  "idempotency_key": "uuid"
}

All trades pass through the Decision Loop which validates:

  • Stop-loss presence (if required by risk config)
  • Leverage within limits
  • Max open positions not exceeded
  • Daily drawdown within limit
  • Risk:Reward ratio above minimum
  • Position size within all four constraints

Returns the trade group ID and individual order details.

Trade History Import

EndpointMethodDescription
/trades/importPOSTStart history import for an exchange account
/trades/import/statusGETGet import job status
POST /api/v1/trades/import
Content-Type: application/json

{ "exchange_account_id": "uuid" }

Journal & Analytics

All journal endpoints require authentication.

Analytics

EndpointMethodDescription
/journal/analytics/overviewGETTrade count, win rate, avg R
/journal/analytics/equity-curveGETCumulative P&L data points
/journal/analytics/daily-pnlGETP&L by day
/journal/analytics/symbol-breakdownGETP&L by symbol
/journal/analytics/duration-profitGETDuration vs. profitability
/journal/analytics/return-distributionGETReturn histogram buckets
/journal/analytics/time-distributionGETTrades by time of day/week
/journal/analytics/filter-optionsGETAvailable filter values

Trade Journal

EndpointMethodDescription
/journal/tradesGETList trades (paginated)
/journal/trades/{id}GETGet trade with notes and tags
/journal/trades/{id}/notesPATCHUpdate trade notes
/journal/trades/{id}/tagsPOSTAdd tag to trade
/journal/trades/{id}/tags/{tag_id}DELETERemove tag from trade

Journal Entries

EndpointMethodDescription
/journal/entriesGETList entries
/journal/entriesPOSTCreate entry
/journal/entries/{id}GETGet entry
/journal/entries/{id}PUTUpdate entry
/journal/entries/{id}DELETEDelete entry

Tags

EndpointMethodDescription
/journal/tagsGETList all tags
/journal/tagsPOSTCreate tag
/journal/tags/{id}PUTUpdate tag
/journal/tags/{id}DELETEDelete tag

Storage

EndpointMethodDescription
/journal/uploadPOSTUpload image (multipart)
/journal/storageGETGet storage usage
/journal/images/{id}DELETEDelete image

Risk Configuration

EndpointMethodDescription
/risk-configGETGet risk config
/risk-configPUTUpdate risk config
PUT /api/v1/risk-config
Content-Type: application/json

{
  "account_risk_percent": 2.0,
  "max_risk_amount": 150.0,
  "max_position_size": 0.5,
  "max_leverage": 5,
  "daily_max_drawdown_percent": 5.0,
  "max_open_positions": 5,
  "require_stop_loss": true,
  "min_risk_reward_ratio": 1.5
}

WebSocket

The WebSocket server runs on port 4000. Messages use JSON.

Connecting

Connect to ws://host:4000 (or wss:// in production). No authentication is required at the WebSocket layer.

Subscribing

{
  "method": "SUBSCRIBE",
  "params": ["order.{user_id}"],
  "id": 1
}

Unsubscribing

{
  "method": "UNSUBSCRIBE",
  "params": ["order.{user_id}"],
  "id": 2
}

Channels

ChannelFormatEvents
orderorder.{user_id}Order fills, cancellations, status changes
depthdepth.{symbol}Orderbook updates
tradetrade.{symbol}Trade stream
tickerticker.{symbol}Price ticker updates
balancebalance.{user_id}Balance changes

Event Format

{
  "stream": "order.550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "e": "order_update",
    "s": "BTC_USDT",
    "status": "filled"
  }
}

The WebSocket server uses PostgreSQL LISTEN/NOTIFY internally — when the router processes an order event, it calls pg_notify() which the WebSocket server receives and broadcasts to subscribed clients.