Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Bot API Reference

opentrader-bot exposes an HTTP API on port 8000 (internal Docker network — not publicly accessible).

Base URL (from inside the openclaw container): http://opentrader-bot:8000


Summary

SectionEndpoints
HealthGET /health
LicenseGET /license/status, POST /license/register, POST /license/activate
DashboardGET /dashboard, POST /api/agent/{name}, GET /api/state
NewsGET /api/news
PortfolioGET /api/portfolio
Bot ActionsGET /scan, POST /trade, GET /custom_check, GET /trailing, GET /status, POST /api/closeall, POST /api/reset-daily
TelegramGET /api/notify
Trade ManagementGET /api/trades, GET /api/trades/recover, POST /api/trades/restore
Signal FlowPOST /api/scan-and-signal, POST /api/signal, GET /api/signal/list, GET /api/signal/pending, GET /api/signal/{symbol}, POST /api/signal/{symbol}/confirm, POST /api/signal/{symbol}/reject, POST /api/signal/cleanup

Health

GET /health

Health check. Always available, no license required.

Response

{
  "ok": true,
  "license_status": "active",
  "plan": "free",
  "commit": "a1b2c3d"
}

commit is the 7-character git SHA baked in at Docker build time (GIT_COMMIT build arg). Shows "dev" on local builds.


License

GET /license/status

Returns the current license state of this machine.

Response (active)

{
  "status": "active",
  "plan": "free",
  "features": {},
  "expires_at": null,
  "validated_at": "2026-04-18T10:00:00",
  "machine_id": "abc123"
}

Response (unlicensed)

{
  "status": "unlicensed",
  "plan": null,
  "features": {},
  "message": "Chưa có license. Dùng /license/register hoặc /license/activate."
}

POST /license/register

Register and receive a free license key. The key is automatically activated and saved to /app/state/license.json.

Request body

{ "email": "[email protected]", "name": "Trader Name" }

Response

{
  "ok": true,
  "license_key": "OT-XXXX-XXXX-XXXX-XXXX",
  "plan": "free",
  "message": "License đã được kích hoạt thành công! Bot sẵn sàng hoạt động."
}

POST /license/activate

Activate an existing license key (e.g., after resetting the machine).

Request body

{ "license_key": "OT-XXXX-XXXX-XXXX-XXXX" }

Response

{
  "ok": true,
  "plan": "free",
  "message": "License kích hoạt thành công — plan=free."
}

Dashboard

GET /dashboard

Serves the web dashboard HTML (app/static/dashboard.html).


POST /api/agent/{name}

An agent reports its current status. name must be one of: coo, ops, tech, finance, scout.

Request body

{ "status": "running", "action": "Scan ta_trend" }

status values: idle | running | waiting | error

Response

{ "ok": true }

GET /api/state

Returns the full dashboard state (all agent statuses + activity log). Polled every 3 seconds by the dashboard.

Response

{
  "agents": {
    "coo":     { "status": "idle",    "action": "Chờ lệnh...", "updated": "10:45:02" },
    "ops":     { "status": "idle",    "action": "Chờ lệnh...", "updated": "10:45:02" },
    "tech":    { "status": "running", "action": "Scan ta_trend", "updated": "10:46:01" },
    "finance": { "status": "idle",    "action": "Chờ lệnh...", "updated": "10:45:02" },
    "scout":   { "status": "idle",    "action": "Chờ lệnh...", "updated": "10:45:02" }
  },
  "log": [
    { "time": "10:46:01", "agent": "tech", "action": "Scan ta_trend", "status": "running" }
  ],
  "ts": "10:46:04"
}

News

GET /api/news

Proxy crypto news from RSS sources. Tries each source in sequence and returns the first with valid items. Result is cached for 10 minutes. No license required.

Response (success)

{
  "ok": true,
  "data": [
    { "title": "Bitcoin hits new high", "url": "https://...", "source": "cointelegraph.com" }
  ],
  "cached": false,
  "source": "cointelegraph.com"
}

Response (cached)

{ "ok": true, "data": [...], "cached": true }

Response (all sources failed)

{ "ok": false, "data": [], "error": "cointelegraph.com: timed out | coindesk.com: ..." }

Portfolio

GET /api/portfolio

Fetch current balance from the active exchange. No license required.

Response (success)

{
  "ok": true,
  "balance": 1042.50,
  "currency": "USDT",
  "holdings": [{ "asset": "USDT", "qty": 1042.50, "value": 1042.50, "price": 1.0 }],
  "exchange": "binance",
  "testnet": false
}

Response (error)

{
  "ok": false,
  "balance": null,
  "currency": "—",
  "exchange": "—",
  "error": "Invalid API key"
}

Bot Actions (require license)

All bot endpoints require a valid license. Returns 503 if unlicensed.

The agent query parameter is optional (default shown below) — it controls which agent card on the dashboard reflects the action.


GET /scan

Scan the watchlist using TA indicators. Returns candidates ready to trade.

Query params

ParamRequiredDefaultValues
botyesta_trend, ta_mean_revert
agentnotechcoo, ops, tech, finance, scout

Example

curl "http://opentrader-bot:8000/scan?bot=ta_trend"

Response

{
  "status": "ok",
  "exchange": "hyperliquid",
  "testnet": false,
  "bot": "ta_trend",
  "scanned": 12,
  "candidates": [
    { "symbol": "ETHUSDT", "direction": "buy", "score": 6 }
  ],
  "all": [...],
  "trades_today": 1,
  "remaining": 2
}

POST /trade

Place a bracket order (entry + SL + TP) on the active exchange.

Query params

ParamRequiredDescription
symbolyese.g. ETHUSDT
directionyesbuy or sell
botyesta_trend or ta_mean_revert
slyesStop-loss % (e.g. 3.0)
tpyesTake-profit % (e.g. 7.0)
evyesExpected value from Finance AI confirm — must be > 25
confidenceyesConfidence from Finance AI confirm — must be >= 8
agentnoDefault: ops

Both ev and confidence are validated server-side. Returns 422 if either threshold is not met — the trade is blocked regardless of what the agent intended.

Example

curl -X POST "http://opentrader-bot:8000/trade?symbol=ETHUSDT&direction=buy&bot=ta_trend&sl=3.0&tp=7.0&ev=32.5&confidence=8"

Response

{
  "symbol": "ETHUSDT",
  "direction": "buy",
  "bot": "ta_trend",
  "size": 0.05,
  "entry_px": 2450.5,
  "sl_px": 2377.0,
  "tp_px": 2622.0,
  "sl_pct": 3.0,
  "tp_pct": 7.0,
  "sl_placed": "ok",
  "tp_placed": "ok",
  "date": "2026-04-18"
}

GET /custom_check

Run custom indicator checks on a specific symbol and direction. Returns a checklist used by Finance AI to confirm before trading.

Query params

ParamRequiredDescription
symbolyese.g. ETHUSDT
directionyesbuy or sell
botyesta_trend or ta_mean_revert
agentnoDefault: tech

Example

curl "http://opentrader-bot:8000/custom_check?symbol=ETHUSDT&direction=buy&bot=ta_trend"

Response

{
  "symbol": "ETHUSDT",
  "direction": "buy",
  "bot": "ta_trend",
  "signal": "buy",
  "passed": 5,
  "total": 7,
  "checklist": [
    { "name": "RSI", "pass": true, "detail": "RSI=42 < 50" },
    { "name": "MACD", "pass": true, "detail": "bullish crossover" }
  ]
}

GET /trailing

Update trailing stop-loss for all open positions based on current price.

Query params

ParamRequiredDefault
agentnotech

Example

curl "http://opentrader-bot:8000/trailing"

Response

{
  "updated": [
    { "symbol": "ETHUSDT", "old_sl": 2377.0, "new_sl": 2410.0, "profit_pct": 2.5 }
  ]
}

GET /status

Returns today’s trade count, consecutive losses, recent trades, and bot config.

Query params

ParamRequiredDefault
agentnocoo

Example

curl "http://opentrader-bot:8000/status"

Response

{
  "exchange": "hyperliquid",
  "testnet": false,
  "trades_today": 1,
  "consecutive_losses": 0,
  "recent_trades": [...],
  "config": { "max_trades": 3, "sl_pct": 3.0, "tp_pct": 7.0 }
}

POST /api/closeall

Cancel all SL/TP orders and close all open positions at market. Positions successfully closed are removed from state.

Query params

ParamRequiredDefault
agentnoops

Example

curl -X POST "http://opentrader-bot:8000/api/closeall?agent=ops"

Response

{
  "status": "ok",
  "count": 2,
  "closed": [
    { "symbol": "ETHUSDT", "status": "closed", "size": 0.05 },
    { "symbol": "BTCUSDT", "status": "closed", "size": 0.001 }
  ]
}

If a position fails to close, it remains in state with "status": "error":

{ "symbol": "SOLUSDT", "status": "error", "error": "Insufficient balance" }

Hyperliquid: closes via IOC limit order at ±5% slippage.
Binance: cancels OCO pair then places MARKET order.


POST /api/reset-daily

Reset the trades_today counter to 0, allowing the bot to place new trades within the same day. Existing open positions and state are preserved — trailing stop continues to work normally.

Example

curl -X POST "http://opentrader-bot:8000/api/reset-daily"

Response

{ "ok": true, "trades_today_before": 3, "trades_today_after": 0 }

Use this when the daily limit has been reached but you want to allow additional trades (e.g., after manually closing positions or adjusting strategy).


Telegram

GET /api/notify

Send a raw Telegram message to the boss chat. No license required.

Query params

ParamRequiredDescription
messageyesText to send

Example

curl -sG "http://opentrader-bot:8000/api/notify" \
  --data-urlencode "message=Bot khởi động xong ✅"

Response

{ "ok": true }

Requires TELEGRAM_BOT_TOKEN_COO and TELEGRAM_CHAT_ID_COO in .env.


Trade Management

GET /api/trades

Returns all trades placed today (resets at midnight). Used by Scout to check for existing positions before sending a new signal.

Data is read directly from opentrader_state.json. If Finance placed a trade today, it appears here until midnight.

Example

curl "http://opentrader-bot:8000/api/trades"

Response

{
  "trades": [
    {
      "symbol": "ETHUSDT",
      "direction": "buy",
      "bot": "ta_trend",
      "exchange": "hyperliquid",
      "size": 0.05,
      "entry_px": 2450.5,
      "sl_px": 2377.0,
      "tp_px": 2622.0,
      "sl_pct": 3.0,
      "tp_pct": 7.0,
      "date": "2026-04-18"
    }
  ],
  "count": 1
}

Used by POST /api/signal for automatic filtering:

ConditionSkip status returned
Signal already pending for symbolduplicate_skipped
A trade already exists for symbol todaytrade_exists_skipped
Direction is sell but no trade exists for symbolno_position_sell_skipped

Scout treats all three statuses identically — silently skips the candidate.


GET /api/trades/recover

Auto-recover trade records from open OCO orders on the exchange. Use when the state file is lost (container restart, volume migration) — trailing stop will resume correctly after recovery.

How it works: Scans the full watchlist, finds open OCO (SL+TP) orders, reconstructs entry_px, sl_oid, tp_oid, and size from exchange order history, then writes them to the state file.

Example

curl "http://opentrader-bot:8000/api/trades/recover"

Response

{
  "ok": true,
  "recovered": 2,
  "trades": [
    {
      "symbol": "DOGEUSDT",
      "direction": "buy",
      "entry_px": 0.1012,
      "sl_px": 0.0981,
      "tp_px": 0.1082,
      "size": 4696.02,
      "sl_oid": 10293847,
      "tp_oid": 10293848,
      "sl_placed": "ok",
      "source": "recovered",
      "date": "2026-04-18"
    }
  ]
}

Only adds symbols not already in state — does not overwrite actively tracked positions.


POST /api/trades/restore

Manually restore a single trade record into state. Use when you need to enter sl_oid / entry_px by hand (e.g., a position opened outside the system).

Request body

{
  "symbol": "BNBUSDT",
  "direction": "buy",
  "size": 1.5,
  "entry_px": 598.0,
  "sl_px": 580.0,
  "tp_px": 638.0,
  "sl_oid": 10293001,
  "tp_oid": 10293002,
  "bot": "ta_trend"
}
FieldRequiredDescription
symbolyese.g. BNBUSDT
directionyesbuy or sell
sizeyesCoin quantity
entry_pxyesFilled entry price
sl_pxyesStop-loss price
tp_pxyesTake-profit price
sl_oidnoSL order ID on exchange (required for trailing to work)
tp_oidnoTP order ID on exchange
botnoDefault: ta_trend

Response

{ "ok": true, "trade": { "symbol": "BNBUSDT", "sl_placed": "ok", "..." } }

Signal Flow

The Scout → COO → Boss → Finance pipeline for human-in-the-loop confirmation.

Scout: POST /api/scan-and-signal   ← scan + signal creation in one call
                ↓
        Telegram notify boss (per signal)
                ↓
        Boss replies "YES SYMBOL"
                ↓
        COO: GET /api/signal/{symbol}   ← returns 410 if > 5 min
                ↓
        Finance: AI confirm (EV/confidence) → POST /trade

POST /api/scan-and-signal

Run a full TA scan and automatically create signals for all qualifying candidates. The bot handles scanning, filtering, signal creation, and Telegram notification in one call — Scout does not need to loop through candidates manually.

Query parameters

ParameterTypeRequiredDescription
botstringyesta_trend or ta_mean_revert
agentstringnoCalling agent name (default: scout)

Example

curl -X POST "http://opentrader-bot:8000/api/scan-and-signal?bot=ta_trend&agent=scout"

Response

{
  "ok": true,
  "signaled": ["BTCUSDT", "ETHUSDT"],
  "skipped": [
    {"symbol": "SOLUSDT", "reason": "duplicate_skipped"}
  ],
  "scan": {
    "bot": "ta_trend",
    "scanned": 10,
    "trades_today": 1,
    "remaining": 2
  }
}

If the daily trade limit or consecutive loss limit has already been reached, the bot skips the scan entirely and returns:

{
  "ok": true,
  "signaled": [],
  "skipped": [],
  "reason": "daily_limit_reached"
}

skipped reasons

ReasonMeaning
duplicate_skippedSignal already pending for this symbol
trade_exists_skippedA trade for this symbol exists today
no_position_sell_skippedSELL signal but no open position
telegram_failedSignal created but Telegram notification failed

POST /api/signal

Scout submits a new pending signal. Sends a Telegram message to boss with the signal summary for review.

Duplicate signals for the same symbol (while pending) are silently skipped.

Request body

{
  "symbol": "ETHUSDT",
  "direction": "buy",
  "bot": "ta_trend",
  "price": 2450.5,
  "sl_pct": 3.0,
  "tp_pct": 7.0,
  "checklist": [
    { "name": "RSI", "pass": true, "detail": "RSI=42" }
  ],
  "passed": 5,
  "total": 7
}

Response

{ "ok": true, "symbol": "ETHUSDT", "status": "pending" }

Signal status lifecycle: pendingconfirmed | skipped | expired


GET /api/signal/list

List all signals (any status). Intended for internal / debugging use.

Response

{
  "signals": [
    {
      "symbol": "ETHUSDT",
      "direction": "buy",
      "bot": "ta_trend",
      "price": 2450.5,
      "sl_pct": 3.0,
      "tp_pct": 7.0,
      "passed": 5,
      "total": 7,
      "status": "pending",
      "tg_sent": true,
      "age_seconds": 142
    }
  ]
}

GET /api/signal/pending

Returns a formatted plain-text list of pending signals — ready to forward to the boss via COO.

Response (plain text)

📋 PENDING SIGNALS
━━━━━━━━━━━━━━━━━━
• ETHUSDT BUY | ta_trend
  Price: 2450.5 | SL: 3.0% | TP: 7.0%
  Score: 5/7 | Chờ: 2 phút
  ✅ "1-YES ETHUSDT"  ❌ "0-NO ETHUSDT"
━━━━━━━━━━━━━━━━━━

GET /api/signal/{symbol}

Fetch full signal details for a specific symbol. Called by COO before spawning Finance.

Expiry guard: If the signal is older than 5 minutes the trade is blocked regardless of when the boss confirmed.

Example

curl "http://opentrader-bot:8000/api/signal/ETHUSDT"

Response (valid)

{
  "symbol": "ETHUSDT",
  "direction": "buy",
  "bot": "ta_trend",
  "price": 2450.5,
  "sl_pct": 3.0,
  "tp_pct": 7.0,
  "passed": 5,
  "total": 7,
  "checklist": [{ "name": "RSI", "pass": true, "detail": "RSI=42" }],
  "rsi": 42.1,
  "macd_hist": 0.012,
  "bb_lower": 2410.0,
  "bb_upper": 2490.0,
  "vol_ratio": 1.35,
  "buy_signals": 5,
  "sell_signals": 0,
  "status": "pending",
  "tg_sent": true
}

Error responses

CodeCondition
404No signal exists for this symbol
410Signal exists but age > 5 min — boss confirmed too late, trade blocked
409Signal exists but status is skipped or expired — cannot enter trade

POST /api/signal/{symbol}/confirm

Called by Finance after a trade is successfully executed. The signal is removed from memory (consumed) — Scout can signal again for the same symbol once the position closes.

Example

curl -X POST "http://opentrader-bot:8000/api/signal/ETHUSDT/confirm"

Response

{ "ok": true, "symbol": "ETHUSDT", "status": "confirmed" }

Idempotent — calling multiple times does not error.


POST /api/signal/{symbol}/reject

Called by COO when the boss replies NO. Signal moves to skipped and remains in memory until cleanup runs.

Example

curl -X POST "http://opentrader-bot:8000/api/signal/ETHUSDT/reject"

Response

{ "ok": true, "symbol": "ETHUSDT", "status": "skipped" }

POST /api/signal/cleanup

Expire pending signals older than N minutes and delete stale entries. Called by Ops every 5 minutes.

Query params

ParamDefaultDescription
max_age_minutes5Pending signals older than this are marked expired

Response

{
  "ok": true,
  "expired": ["ETHUSDT"],
  "deleted": ["BTCUSDT"],
  "count_expired": 1,
  "count_deleted": 1
}

expired = pending → expired (age > N min). deleted = skipped/expired entries older than 30 min, removed from memory entirely.


Error codes

CodeMeaning
400Bad request / license validation error
404Signal not found for symbol
409Signal exists but status is not pending
410Signal expired — boss confirmed after the 5-minute window
503License required, or Telegram not configured
504Bot subprocess timed out (> 300s)
500Bot internal error or non-JSON output
502Telegram API error

Quick reference (curl)

BASE=http://opentrader-bot:8000

# Health
curl "$BASE/health"

# License
curl "$BASE/license/status"
curl -X POST "$BASE/license/register" \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","name":"Trader"}'

# Dashboard
curl "$BASE/api/state"

# News
curl "$BASE/api/news"

# Portfolio
curl "$BASE/api/portfolio"

# Bot actions
curl "$BASE/scan?bot=ta_trend"
curl "$BASE/status"
curl "$BASE/trailing"
curl "$BASE/custom_check?symbol=ETHUSDT&direction=buy&bot=ta_trend"
curl -X POST "$BASE/trade?symbol=ETHUSDT&direction=buy&bot=ta_trend&sl=3.0&tp=7.0"
curl -X POST "$BASE/api/closeall"
curl -X POST "$BASE/api/reset-daily"

# Telegram
curl -sG "$BASE/api/notify" --data-urlencode "message=Test thông báo"

# Trade management
curl "$BASE/api/trades"
curl "$BASE/api/trades/recover"
curl -X POST "$BASE/api/trades/restore" \
  -H "Content-Type: application/json" \
  -d '{"symbol":"BNBUSDT","direction":"buy","size":1.5,"entry_px":598.0,"sl_px":580.0,"tp_px":638.0,"sl_oid":10293001}'

# Signal flow — scan-and-signal (recommended)
curl -X POST "$BASE/api/scan-and-signal?bot=ta_trend&agent=scout"

# Signal flow — manual
curl -X POST "$BASE/api/signal" \
  -H "Content-Type: application/json" \
  -d '{"symbol":"ETHUSDT","direction":"buy","bot":"ta_trend","price":2450.5,"sl_pct":3.0,"tp_pct":7.0,"passed":5,"total":7}'
curl "$BASE/api/signal/list"
curl "$BASE/api/signal/pending"
curl "$BASE/api/signal/ETHUSDT"
curl -X POST "$BASE/api/signal/ETHUSDT/confirm"
curl -X POST "$BASE/api/signal/ETHUSDT/reject"
curl -X POST "$BASE/api/signal/cleanup?max_age_minutes=5"