WELTIQ / DOCS / QUICK STARTS
Quick starts — MCP, REST API, TradingView
Three complete walkthroughs to a first paper order: Claude or Codex over MCP, your own code over the REST API, and a TradingView alert hook — with the real endpoints and field names.
Three complete paths to a first paper order. Every path needs a free account; the trading limits are always approved by you in the browser, never by a key, an agent or a script.
Claude · Codex · MCP
MCP: API key → scope → approve limits → first order → check the fill
- Issue an API key — Sign up at weltiq.ai/login (email or Google), then open My → Connections → API keys (/connections). Choose a label and scopes and issue the key. The wq_… secret is shown once — store it as WELTIQ_API_KEY.
- Pick the scopes — read-data reads markets, account, fills and journal; operate-sim places paper orders and runs resident slots; run-backtest runs backtests. For a trading agent issue read-data + operate-sim. The analyst server (https://weltiq.ai/mcp/analyst) accepts read-data-only keys.
- Approve order limits in the browser — Open My → Connections and use the Order limits section (/connections#envelope): symbols (e.g. BTCUSDT), max notional per order, max notional per day, validity in days. Keys and MCP cannot create or widen limits — they get 403. Without an approved limit the first order is rejected with envelope_required.
- Connect the MCP server — Claude Code: one command. Claude Desktop or any client that speaks streamable HTTP MCP (Codex included): add the server URL with the Authorization header to the client's MCP config. The trading server exposes order_oneoff, oneoff_orders, fills, account, slots and more; the analyst server has no order tools.
claude mcp add --transport http weltiq https://weltiq.ai/mcp --header "Authorization: Bearer $WELTIQ_API_KEY" {"mcpServers": {"weltiq": {"url": "https://weltiq.ai/mcp", "headers": {"Authorization": "Bearer wq_..."}}}} - Place the first order — Ask: "Buy 0.001 BTCUSDT at market on binance-spot. Thesis: the 4-hour support holds; I am wrong below 60000." The agent calls order_oneoff(venue="binance-spot", symbol="BTCUSDT", side="buy", qty="0.001", thesis_why=…, px_below="60000"). thesis_why is mandatory; opening a position also needs px_below and/or px_above (the engine closes the position when a level is hit). MCP adds client_ts for you. The same call over REST is POST /v1/orders/oneoff.
curl -sS -X POST https://weltiq.ai/v1/orders/oneoff -H "Authorization: Bearer $WELTIQ_API_KEY" -H "Content-Type: application/json" \ -d '{"venue":"binance-spot","symbol":"BTCUSDT","side":"buy","type":"market","qty":"0.001", "thesis":{"why":"4h support holds"},"invalidation":{"px_below":"60000"}, "client_ts":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'"}' # {"queue_id":"dq_…","status":"queued","est_notional":"…","closing":false,"target":"paper"} curl -sS https://weltiq.ai/v1/orders/oneoff -H "Authorization: Bearer $WELTIQ_API_KEY" # queued → filled | rejected (result.code) - Check the fill or the rejection — Ask "did it fill?" — the agent calls oneoff_orders: status queued → filled (result.fills: qty, px, fee) or rejected with result.code. Typical codes: thesis_required, invalidation_required, envelope_required, order_notional_exceeded, daily_notional_exceeded, no_price, spot_short_not_allowed. Then fills(mode="paper") and account for the ledger. A sell against the open position is a close (reduce-only, no levels needed).
REST API · your own code
REST API: connection approval → curl or Python order → status
- Approve a connection in the browser — My → Connections (/connections) → New connection: one Binance spot USDT symbol (e.g. BTCUSDT), virtual capital, max_order_notional and max_daily_notional in USDT, stop_loss_pct, allowed rule_ids (default buy: entry, sell: exit), a strategy description of 10+ characters, validity in days, optionally fee_venue. Approve & create. Copy the connection_id (pc_…). This screen is the only place limits are set — POST /v1/paper/connections needs a browser session, not a key.
- Send the order with curl — Body fields: schema_version 1, event_id (unique inside the connection), sequence (positive integer, strictly increasing), emitted_at (UTC ISO 8601, must arrive within 60 s), action buy or sell, qty as a string Decimal aligned to the lot size, rule_id from the allowed list. Any other field (target, live, cred_id, user_id) is rejected with 422. 202 means accepted, not filled.
curl -sS -X POST "https://weltiq.ai/v1/paper/connections/$CID/orders" \ -H "Authorization: Bearer $WELTIQ_API_KEY" -H "Content-Type: application/json" \ -d '{"schema_version":1,"event_id":"bot-1","sequence":1,"emitted_at":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'","action":"buy","qty":"0.001","rule_id":"entry"}' # 202 {"status":"queued","event_id":"bot-1","order_id":"dq_…","exec_mode":"delayed","status_url":"/v1/paper/connections/pc_…/orders/bot-1"} - Or from Python — Use the API key with the Authorization header and keep sequence monotonic across restarts (a millisecond timestamp works). Re-sending the same body returns 200 with duplicate: true; the same event_id with a different body returns 409 idempotency_conflict.
import os, time, datetime, httpx BASE, CID = "https://weltiq.ai", os.environ["WELTIQ_CID"] H = {"Authorization": "Bearer " + os.environ["WELTIQ_API_KEY"]} seq = int(time.time() * 1000) # strictly increasing sequence body = {"schema_version": 1, "event_id": f"bot-{seq}", "sequence": seq, "emitted_at": datetime.datetime.now(datetime.timezone.utc).isoformat(), "action": "buy", "qty": "0.001", "rule_id": "entry"} r = httpx.post(f"{BASE}/v1/paper/connections/{CID}/orders", json=body, headers=H, timeout=10) print(r.status_code, r.json()) # 202 queued — accepted, not filled st = httpx.get(BASE + r.json()["status_url"], headers=H).json() print(st["status"], st.get("filled_qty"), st.get("fill_vwap"), st.get("protection")) - Read the status — GET /v1/paper/connections/{cid}/orders/{event_id}: status queued → submitted → filled (or partial, rejected, expired, canceled), filled_qty, fill_vwap, fills [{qty, px, fee}], and protection — the stop derived from the fill VWAP and stop_loss_pct. GET …/orders lists recent orders plus events rejected at admission; GET …/fills lists fills of the connection's own paper book.
curl -sS "https://weltiq.ai/v1/paper/connections/$CID/orders/bot-1" -H "Authorization: Bearer $WELTIQ_API_KEY" # {"event_id":"bot-1","status":"filled","filled_qty":"0.001","fill_vwap":"…","fills":[{"qty":"0.001","px":"…","fee":"…"}],"protection":{…}} curl -sS "https://weltiq.ai/v1/paper/connections/$CID/fills" -H "Authorization: Bearer $WELTIQ_API_KEY" - Rejections you will meet — 422 invalid_payload (field format), event_expired (emitted_at older than 60 s), future_event, rule_not_allowed, insufficient_position (sell larger than held), connection_inactive; 409 position_exists (buy while a long is open or queued) and out_of_order (sequence not increasing); 429 rate_limited (60 per minute, 2,000 per day per connection); 404 or 410 unknown or revoked connection. buy opens one long per connection; sell only reduces it.
- Cancel or close — DELETE /v1/paper/connections/{cid}/orders/{event_id} cancels an order the engine has not claimed yet (409 not_cancelable afterwards). A filled position is reduced with a sell order, or from the connection screen in the browser.
TradingView · alert hook
TradingView: hook URL → alert() JSON → fill
- Create the connection and copy the hook URL — Same connection screen as the REST path (My → Connections → New connection). Right after Approve & create the hook URL https://weltiq.ai/hooks/tv-orders/<token> is shown once — copy it. If you lose it, revoke the connection and create a new one. One connection is free; Pro has three.
- Build the order JSON in Pine with alert() — TradingView placeholders cannot produce an increasing integer sequence or a unique event_id, so the script builds the body. Use bar_index for both, str.format_time for emitted_at in UTC, and the rule_ids you allowed on the connection.
//@version=5 strategy("weltiq hook", overlay=true) fast = ta.sma(close, 20), slow = ta.sma(close, 50) msg(action, rule) => '{"schema_version":1,"event_id":"tv-' + str.tostring(bar_index) + '-' + action + '","sequence":' + str.tostring(bar_index) + ',"emitted_at":"' + str.format_time(timenow, "yyyy-MM-dd'T'HH:mm:ss'Z'", "UTC") + '","action":"' + action + '","qty":"0.001","rule_id":"' + rule + '"}' if ta.crossover(fast, slow) alert(msg("buy", "entry"), alert.freq_once_per_bar_close) if ta.crossunder(fast, slow) alert(msg("sell", "exit"), alert.freq_once_per_bar_close) - Create the alert — Add the strategy to the chart → Create alert → condition "alert() function calls only", Webhook URL = the hook URL from step 1, leave the message box empty (the alert() text is sent as is), expiration none, 2FA on. TradingView sends webhooks on its paid plans only.
- Watch the first fill — Open the connection in My → Connections: the event appears as queued, then filled with fill price, fee and the protection stop, or rejected with a code. The same data is at GET /v1/paper/connections/{cid}/orders/{event_id} and in the MCP tool paper_order_status. Free plan: filled on the first closed 1-minute bar after emitted_at; Pro: on the live quote.
- When nothing arrives — 404 or 410: wrong or revoked token. 422 event_expired: emitted_at is more than 60 s old — check the alert frequency. 409 position_exists: a long is already open or queued on this connection. 200 duplicate: the same alert was delivered twice and was ignored. TradingView does not guarantee delivery; compare its alert log with the connection's order list.
Free plan: orders fill on the first closed 1-minute bar after the order time (exec_mode "delayed"), so allow one to two minutes. Pro fills on the live quote. Markets: crypto spot for external connections; crypto spot and perpetuals for direct orders.
Your first experiment starts with one order.
Check availability, then choose how you want to connect.
Open external order connectionsGetting started →