HTTP API Reference

REST API for querying bot state and automating workflows


Overview

The Wealth trading bot exposes an HTTP API alongside the metrics server for querying live bot state. This enables:

  • Automation scripts to monitor positions and balances
  • Custom dashboards to display real-time data
  • Alert integrations with external monitoring systems
  • Programmatic position management workflows

Base URL

http://localhost:9090

The port is configurable via WEALTH__OBSERVABILITY__METRICS_PORT (default: 9090).

Data Freshness

EndpointData SourceAPI Quota Impact
/api/positionsLive exchange fetchConsumes quota
/api/funding_ratesCached repositoryNone (fast)
/api/balancesCached repositoryNone (may be stale)
/api/arbitrage_positionsCached repositoryNone (fast)

Endpoints

GET /api/positions

Fetches current open positions directly from exchanges. This endpoint queries each configured exchange in parallel.

⚠️ Note: This endpoint consumes exchange API quota. Use /api/arbitrage_positions for cached data without API impact.

Request:

curl http://localhost:9090/api/positions

Response:

{
  "positions": [
    {
      "exchange": "BinanceFutures",
      "position": {
        "symbol": "BTCUSDT",
        "side": "Long",
        "quantity": "0.1",
        "entry_price": "95000",
        "mark_price": "96000",
        "unrealized_pnl": "100",
        "leverage": "10",
        "liquidation_price": "86000"
      }
    },
    {
      "exchange": "Bybit",
      "position": {
        "symbol": "BTCUSDT",
        "side": "Short",
        "quantity": "0.1",
        "entry_price": "95100",
        "mark_price": "96000",
        "unrealized_pnl": "-90",
        "leverage": "10",
        "liquidation_price": "104500"
      }
    }
  ],
  "timestamp": "2024-12-27T10:30:00Z"
}

Response Fields:

FieldTypeDescription
positionsarrayList of positions with exchange info
positions[].exchangestringExchange name (BinanceFutures, Bybit, HyperLiquid, Aster)
positions[].position.symbolstringTrading pair symbol
positions[].position.sidestringLong or Short
positions[].position.quantitydecimalPosition size in base asset
positions[].position.entry_pricedecimalAverage entry price
positions[].position.mark_pricedecimalCurrent mark price
positions[].position.unrealized_pnldecimalUnrealized P&L in quote asset
positions[].position.leveragedecimalCurrent leverage
positions[].position.liquidation_pricedecimalLiquidation price (null if not available)
timestampdatetimeISO 8601 timestamp of response

Error Handling:

The endpoint returns partial results if some exchanges fail. Only returns an error if all exchanges fail:

{
  "error": "Failed to fetch positions from all exchanges"
}

Check logs for details on individual exchange failures.


GET /api/funding_rates

Returns cached funding rates from the in-memory repository. Fast and does not consume API quota.

Request:

curl http://localhost:9090/api/funding_rates

Response:

{
  "rates": [
    {
      "exchange": "BinanceFutures",
      "symbol": "BTCUSDT",
      "rate": "0.0001",
      "next_funding_time": "2024-12-27T16:00:00Z",
      "last_updated": "2024-12-27T10:25:00Z"
    },
    {
      "exchange": "Bybit",
      "symbol": "BTCUSDT",
      "rate": "-0.00015",
      "next_funding_time": "2024-12-27T16:00:00Z",
      "last_updated": "2024-12-27T10:25:30Z"
    },
    {
      "exchange": "HyperLiquid",
      "symbol": "BTCUSDT",
      "rate": "0.00008",
      "next_funding_time": "2024-12-27T11:00:00Z",
      "last_updated": "2024-12-27T10:29:00Z"
    }
  ],
  "timestamp": "2024-12-27T10:30:00Z"
}

Response Fields:

FieldTypeDescription
ratesarrayList of funding rate entries
rates[].exchangestringExchange name
rates[].symbolstringTrading pair symbol
rates[].ratedecimalCurrent funding rate (e.g., 0.0001 = 0.01%)
rates[].next_funding_timedatetimeNext funding settlement time
rates[].last_updateddatetimeWhen this rate was last fetched
timestampdatetimeISO 8601 timestamp of response

Note: Funding rate values are in decimal form. Multiply by 100 to get percentage (e.g., 0.0001 = 0.01%).


GET /api/balances

Returns cached balances from the balance repository. Fast response but may be slightly stale.

Request:

curl http://localhost:9090/api/balances

Response:

{
  "balances": [
    {
      "exchange": "BinanceFutures",
      "asset": "USDT",
      "free": "10000",
      "total": "12000",
      "wallet_balance": "11500",
      "reserved": "2000"
    },
    {
      "exchange": "Bybit",
      "asset": "USDT",
      "free": "8500",
      "total": "8500",
      "wallet_balance": "8200",
      "reserved": "0"
    },
    {
      "exchange": "HyperLiquid",
      "asset": "USDC",
      "free": "5000",
      "total": "7500",
      "wallet_balance": "7000",
      "reserved": "2500"
    }
  ],
  "timestamp": "2024-12-27T10:30:00Z"
}

Response Fields:

FieldTypeDescription
balancesarrayList of balance entries
balances[].exchangestringExchange name
balances[].assetstringAsset symbol (e.g., USDT, USDC)
balances[].freedecimalAvailable balance for trading (use for position sizing)
balances[].totaldecimalTotal balance including unrealized P&L
balances[].wallet_balancedecimalDeposited capital (stable, excludes unrealized P&L)
balances[].reserveddecimalBalance locked in orders/positions
timestampdatetimeISO 8601 timestamp of response

Balance Types: free is the correct balance for position sizing as it respects locked margin and automatically de-risks when positions are underwater. wallet_balance provides stable deposited capital for reporting/tracking that doesn't fluctuate with unrealized P&L.


GET /api/arbitrage_positions

Returns cached arbitrage positions with cumulative funding collected. This is the recommended endpoint for monitoring active arbitrage pairs.

Request:

curl http://localhost:9090/api/arbitrage_positions

Response:

{
  "positions": [
    {
      "symbol": "BTCUSDT",
      "long_exchange": "Bybit",
      "short_exchange": "BinanceFutures",
      "entry_spread": "0.00025",
      "position_size": "0.1",
      "entry_time": "2024-12-26T08:00:00Z",
      "funding_collected": "12.50"
    },
    {
      "symbol": "ETHUSDT",
      "long_exchange": "HyperLiquid",
      "short_exchange": "BinanceFutures",
      "entry_spread": "0.0003",
      "position_size": "1.5",
      "entry_time": "2024-12-25T16:00:00Z",
      "funding_collected": "28.75"
    }
  ],
  "total_funding_collected": "41.25",
  "timestamp": "2024-12-27T10:30:00Z"
}

Response Fields:

FieldTypeDescription
positionsarrayList of active arbitrage positions
positions[].symbolstringTrading pair symbol
positions[].long_exchangestringExchange holding long position
positions[].short_exchangestringExchange holding short position
positions[].entry_spreaddecimalFunding spread at entry
positions[].position_sizedecimalPosition size in base asset
positions[].entry_timedatetimeWhen position was opened
positions[].funding_collecteddecimalCumulative funding collected (USD)
total_funding_collecteddecimalSum of all positions' funding
timestampdatetimeISO 8601 timestamp of response

Health Endpoints

In addition to the data API, the metrics server exposes health check endpoints for monitoring and container orchestration:

EndpointDescriptionUse Case
/healthOverall health status with details (JSON)Human monitoring, dashboards
/readyKubernetes readiness probeK8s readiness gate
/liveKubernetes liveness probeK8s container restart decisions
/metricsOpenTelemetry metrics infoOTLP configuration verification

GET /health

Returns comprehensive health status including exchange connectivity and system metrics.

Request:

curl http://localhost:9090/health | jq

Response:

{
  "status": "healthy",
  "exchanges": {
    "binance": "connected",
    "bybit": "connected",
    "hyperliquid": "connected"
  },
  "uptime_seconds": 3600
}

Response Fields:

FieldTypeDescription
statusstringOverall status: healthy, degraded, or unhealthy
exchangesobjectPer-exchange connection status
uptime_secondsintegerBot uptime in seconds

Status Meanings:

  • healthy - All exchanges connected, bot operating normally
  • degraded - Some exchanges unavailable, bot still operating
  • unhealthy - Critical failure, bot not functioning

GET /ready

Kubernetes-style readiness probe. Returns HTTP 200 when the bot is ready to accept traffic.

Request:

curl -s -o /dev/null -w "%{http_code}" http://localhost:9090/ready

Response:

  • 200 OK - Bot is ready (connected to at least one exchange)
  • 503 Service Unavailable - Bot is not ready (still initializing or all exchanges failed)

Body (when ready):

{
  "ready": true,
  "timestamp": "2024-12-27T10:30:00Z"
}

GET /live

Kubernetes-style liveness probe. Returns HTTP 200 if the bot process is alive and responsive.

Request:

curl -s -o /dev/null -w "%{http_code}" http://localhost:9090/live

Response:

  • 200 OK - Bot is alive
  • 503 Service Unavailable - Bot is unresponsive (should trigger container restart)

Body:

{
  "alive": true,
  "timestamp": "2024-12-27T10:30:00Z"
}

GET /metrics

Returns information about the OpenTelemetry metrics configuration (not Prometheus scrape format).

Request:

curl http://localhost:9090/metrics | jq

Response:

{
  "status": "operational",
  "backend": "OpenTelemetry OTLP",
  "endpoint": "http://localhost:4317",
  "protocol": "gRPC"
}

Note: For Prometheus-style metrics, configure OTLP export to your observability backend (Grafana Cloud, etc.) and query metrics there. See Monitoring Guide for details.

Kubernetes Deployment Example

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wealth-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wealth
  template:
    metadata:
      labels:
        app: wealth
    spec:
      containers:
        - name: wealth
          image: ghcr.io/thiras/wealth:latest
          ports:
            - containerPort: 9090
          livenessProbe:
            httpGet:
              path: /live
              port: 9090
            initialDelaySeconds: 10
            periodSeconds: 30
          readinessProbe:
            httpGet:
              path: /ready
              port: 9090
            initialDelaySeconds: 5
            periodSeconds: 10

Usage Examples

Shell Scripts

Monitor total balance across exchanges:

#!/bin/bash
curl -s http://localhost:9090/api/balances | jq '
  .balances 
  | group_by(.asset) 
  | map({asset: .[0].asset, total: (map(.total | tonumber) | add)})
'

Get current funding spread for a symbol:

#!/bin/bash
SYMBOL="BTCUSDT"
curl -s http://localhost:9090/api/funding_rates | jq --arg sym "$SYMBOL" '
  .rates 
  | map(select(.symbol == $sym)) 
  | sort_by(.rate) 
  | {min: .[0], max: .[-1], spread: ((.[-1].rate | tonumber) - (.[0].rate | tonumber))}
'

Python Integration

import requests
from datetime import datetime

BASE_URL = "http://localhost:9090"

def get_positions():
    """Fetch current positions from all exchanges."""
    response = requests.get(f"{BASE_URL}/api/positions")
    response.raise_for_status()
    return response.json()

def get_arbitrage_summary():
    """Get arbitrage positions with funding collected."""
    response = requests.get(f"{BASE_URL}/api/arbitrage_positions")
    response.raise_for_status()
    data = response.json()
    
    print(f"Active positions: {len(data['positions'])}")
    print(f"Total funding collected: ${data['total_funding_collected']}")
    
    for pos in data['positions']:
        print(f"  {pos['symbol']}: {pos['long_exchange']} ↔ {pos['short_exchange']}, "
              f"funding: ${pos['funding_collected']}")

def check_funding_opportunity(symbol: str, min_spread: float = 0.0004):
    """Check if funding spread exceeds threshold."""
    response = requests.get(f"{BASE_URL}/api/funding_rates")
    response.raise_for_status()
    
    rates = [r for r in response.json()['rates'] if r['symbol'] == symbol]
    if len(rates) < 2:
        return None
    
    rates_sorted = sorted(rates, key=lambda x: float(x['rate']))
    spread = float(rates_sorted[-1]['rate']) - float(rates_sorted[0]['rate'])
    
    if spread >= min_spread:
        return {
            'symbol': symbol,
            'spread': spread,
            'long_exchange': rates_sorted[0]['exchange'],
            'short_exchange': rates_sorted[-1]['exchange']
        }
    return None

if __name__ == "__main__":
    get_arbitrage_summary()

Prometheus/Alertmanager Integration

While the bot exports native Prometheus metrics, you can also create custom scrapers:

# prometheus.yml
scrape_configs:
  - job_name: 'wealth-api'
    metrics_path: /metrics
    static_configs:
      - targets: ['localhost:9090']

For custom alerts based on API data, use a script with Alertmanager webhook receiver.


Rate Limiting

The HTTP API itself has no rate limiting. However:

  • /api/positions triggers exchange API calls (subject to exchange rate limits)
  • Other endpoints use cached data with no external API impact

Recommendation: Poll /api/positions no more than once per minute. Use /api/arbitrage_positions for frequent monitoring.


Error Responses

All endpoints return standard HTTP status codes:

CodeMeaning
200Success
500Internal error (check logs)

Error responses include a message:

{
  "error": "Failed to fetch positions from all exchanges"
}

Configuration

Binding Address

By default, the API binds to all interfaces (0.0.0.0). For production, restrict to localhost:

[observability]
metrics_bind_address = "127.0.0.1"
metrics_port = 9090

Or via environment:

export WEALTH__OBSERVABILITY__METRICS_BIND_ADDRESS=127.0.0.1

Reverse Proxy

For external access, use a reverse proxy (nginx, Caddy) with authentication:

location /api/ {
    auth_basic "Wealth API";
    auth_basic_user_file /etc/nginx/.htpasswd;
    proxy_pass http://127.0.0.1:9090;
}

See Also