API for developers Simulated
Everything you can do with scanners, strategies, backtests, sweeps and paper accounts on this site, from your own code - and events sent to your server as they happen. JSON over HTTPS, one key per program. Results are simulated with virtual money; nothing is sent to a broker.
Start
- Make a key on your API page. Choose what it may do: read, run (start scanner runs, backtests and tests; manage webhooks), trade (simulated paper orders). The key is shown once.
- Send it with every call:
Authorization: Bearer hotw_… - Everything answers JSON with
"ok": true, or"ok": falseand anerrorwith acodeand amessage.
curl -s https://finance.brandingbrandz.com/api/v1/me \
-H "Authorization: Bearer $HOTW_KEY"
curl -s -X POST https://finance.brandingbrandz.com/api/v1/backtests \
-H "Authorization: Bearer $HOTW_KEY" -H "Content-Type: application/json" \
-d '{"version_id": 12, "from": "2023-01-02", "to": "2026-06-30"}'
import os, time, requests
H = {"Authorization": "Bearer " + os.environ["HOTW_KEY"]}
job = requests.post("https://finance.brandingbrandz.com/api/v1/research", headers=H, json={
"kind": "sweep", "version_id": 12, "from": "2022-01-03", "to": "2026-06-30",
"axes": [{"path": "stop.value", "from": 1, "to": 3, "steps": 5}]}).json()
while True:
r = requests.get("https://finance.brandingbrandz.com/api/v1/research/%d" % job["id"], headers=H).json()
if r["status"] in ("done", "failed"):
break
time.sleep(20)
print(r["result"]["verdict"])
Endpoints
all under https://finance.brandingbrandz.com/api/v1| Method | Path | Needs | What it does |
|---|---|---|---|
| GET | /me | read | Your account, plan, this key and every plan limit. |
| GET | /regimes/current | read | The current NIFTY 50 market regime. |
| GET | /scanners | read | Your scanners and the example scanners. |
| GET | /scanners/{id} | read | One scanner with its versions and recent runs. |
| POST | /scanners/{id}/run | run | Run its latest version (or {"version_id"}) on the latest close; optional {"asof": "YYYY-MM-DD"}. |
| GET | /scanner-runs/{id} | read | One run: every match and why it matched. |
| GET | /strategies | read | Your strategies and the examples. |
| GET | /strategies/{id} | read | One strategy with its versions. |
| GET | /strategy-versions/{id}/knobs | read | The numbers in a version a sweep can vary, with their paths. |
| GET | /backtests | read | Your backtests. |
| POST | /backtests | run | Queue one: {"version_id", "from", "to", "participation_pct"?, "slippage"?}. |
| GET | /backtests/{id} | read | Status, metrics, checks. |
| GET | /backtests/{id}/trades | read | The trade ledger with each trade's evidence. |
| GET | /research | read | Your sweeps and walk-forward tests. |
| POST | /research | run | Queue one: {"kind": "sweep"|"walkforward", "version_id", "from", "to", "axes": [{"path", "from", "to", "steps"}], "objective"?, "train_months"?, "test_months"?, "anchored"?}. |
| GET | /research/{id} | read | Status and the whole result. |
| GET | /paper/accounts | read | Your paper accounts with balances. |
| GET | /paper/accounts/{id} | read | Positions, orders, fills, ledger, performance. |
| POST | /paper/accounts/{id}/orders | trade | A simulated order: {"symbol", "side", "qty", "order_type"?, "limit_price"?, "stop_price"?, "tif"?, "trail_pct"?, "bracket_stop"?, "bracket_target"?}. Send an Idempotency-Key header to make retries safe. |
| POST | /paper/orders/{id}/cancel | trade | Cancel an open simulated order. |
| GET | /webhooks | read | Your webhooks and their last deliveries. |
| POST | /webhooks | run | Add one: {"url", "events": ["paper.fill", ...] or "*"}. The answer carries its signing secret, once. |
| DELETE | /webhooks/{id} | run | Remove one. |
| POST | /webhooks/{id}/test | run | Queue a ping (and switch a stopped webhook back on). |
Limits
| Plan | Keys | Calls a day | Webhooks |
|---|---|---|---|
| Free | 1 | 500 | 1 |
| Plus | 3 | 5,000 | 5 |
| Pro | 10 | 50,000 | 20 |
And at most 60 calls a minute per key. Every answer carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (the day's reset, Unix time). A refused call answers 429, with Retry-After for the minute limit. Backtests, tests and scanner runs also count against the plan's own daily limits, exactly as on the site.
Errors: 400 invalid input · 401 no key, or an unknown or revoked one · 403 the key lacks the scope · 404 not found or not yours · 429 a limit · 503 try again shortly.
No price or index feed is offered: exchange data is not ours to redistribute. What the API returns is your own work - your runs, your backtests' results, your accounts.
Webhooks
Add a webhook on your API page or with POST /webhooks. Each event is a POST of JSON to your address:
{"id": 4812, "event": "paper.fill", "created": "2026-09-26 04:10:02",
"data": {"account_id": 3, "order_id": 91, "symbol": "INFY", "side": "buy", "qty": 20,
"price": "1512.40", "charges": "4.87", "simulated": true, "url": "…"}}
| Event | Sent when |
|---|---|
| backtest.done | a backtest finished or failed |
| research.done | a sweep or walk-forward finished or failed |
| paper.fill | a simulated order filled |
| paper.order | a simulated order was rejected, cancelled, expired or its stop triggered |
| paper.alert | a paper account alert |
| alert.pattern | one of your pattern alerts fired |
| regime.changed | NIFTY 50 moved into a different market regime |
| ping | a test you asked for |
Answer with any 2xx within 5 seconds. Anything else is retried after 1, 5, 30, 120, 360 and 720 minutes; the delivery id stays the same, so use it to ignore repeats. After 20 failed deliveries in a row a webhook is stopped; "Send a test" switches it back on.
Check the signature before trusting a delivery. Headers: X-Hotw-Event, X-Hotw-Delivery, X-Hotw-Timestamp, X-Hotw-Signature: sha256=… - the HMAC-SHA256 of timestamp + "." + raw body with your webhook's secret. Refuse timestamps more than five minutes old.
import hashlib, hmac, time
def verified(secret: str, headers, raw_body: bytes) -> bool:
ts = headers["X-Hotw-Timestamp"]
if abs(time.time() - int(ts)) > 300:
return False
want = "sha256=" + hmac.new(secret.encode(), ts.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(want, headers["X-Hotw-Signature"])
Addresses must be https:// on port 443 at a public host; private and internal addresses are refused, and redirects are not followed.