Preview. Every bot, developer and number on this site is sample data. Real records start at launch.
Developer docs

Quickstart

Connect a bot in three steps. You need a GitHub account and a bot or a TradingView alert that can send an HTTP request.

  1. 1

    Create a bot

    Sign in with GitHub and choose New bot. You get a bot_id and a secret. The secret is shown once.

    Sign in to create a bot
  2. 2

    Send a signal

    A signal says what share of the bot's equity you want in each asset. This one asks for 50% in BTC and 20% in ETH.

    curl -X POST https://api.marginbots.com/v1/signals \
      -H "Content-Type: application/json" \
      -d '{
        "bot_id": "bot_7Hq2",
        "secret": "YOUR_SECRET",
        "targets": { "BTC/USDT": 0.5, "ETH/USDT": 0.2 },
        "client_ref": "rebalance-2026-10-10-1200"
      }'
  3. 3

    See it accepted

    The response comes back in under a second. The signal also appears in Your bots → Manage → Signals, live, with its status.

    HTTP/1.1 202 Accepted
    
    {
      "signal_id": "[SIGNAL_ID]",
      "received_at": "2026-10-10T12:00:41Z"
    }

Signal format

POST /v1/signals with a JSON body. The secret goes in the body, because TradingView alerts can't set HTTP headers.

bot_idstring · requiredYour bot's id, for example bot_7Hq2.
secretstring · requiredThe secret shown when you created the bot, or after a rotation.
targetsobject · requiredSymbol → weight. { "BTC/USDT": 0.5 } means 50% of equity in BTC.
client_refstring · optionalYour own reference. Makes retries safe: a repeat returns the first signal.

Weights

  • Each weight is between 0 and 1. 0.5 means 50% of equity in that asset.
  • Weights add up to 1 or less. The rest stays in USDT.
  • Symbols you leave out keep their current target.
  • 0 closes the position fully.

Full example

curl -X POST https://api.marginbots.com/v1/signals \
  -H "Content-Type: application/json" \
  -d '{
    "bot_id": "bot_7Hq2",
    "secret": "YOUR_SECRET",
    "targets": { "BTC/USDT": 0.5, "ETH/USDT": 0.2 },
    "client_ref": "rebalance-2026-10-10-1200"
  }'

Responses and errors

202 Accepted returns the signal_id and our server's received_at, the time that counts. A rejected signal gets a 4xx response with one of these reasons, and the position doesn't change.

ReasonMeaning and what to do
unknown_symbolThe symbol is not in the trading universe.Use a pair from the trading universe, written like BTC/USDT.
over_allocatedTarget weights would add up to more than 1.Lower the weights so they add up to 1 or less. The rest stays in USDT.
invalid_weightA weight is outside 0 to 1.Send weights from 0 to 1. Shorts are not supported in v1.
rate_limitedMore than 60 signals in the last hour.Wait, or send fewer signals: the limit is 60 per hour per bot.
bad_secretThe bot id or secret is wrong.Check the bot id and secret. After a rotation, update your bot or alert.
duplicateSame client_ref as an earlier signal; the earlier one is returned.Nothing to fix. Use a new client_ref for a new signal.
invalid_requestThe body is not valid JSON, or a field is missing or malformed.Send a JSON object with bot_id, secret and targets. The detail says which part is wrong.
bot_retiredThe bot is retired and no longer accepts signals.A retired bot stays retired. Create a new bot to start a new record.

TradingView alerts

TradingView alerts work as they are, with no adapter. Each alert sends one signal.

  1. 1

    On your chart, open the alert dialog for your strategy or indicator.

  2. 2

    In Notifications, tick "Webhook URL" and paste https://api.marginbots.com/v1/signals.

    screenshot · TradingView alert dialog, Notifications tab, Webhook URL field
  3. 3

    In Message, paste the alert message below, with your bot_id, secret and targets.

    screenshot · TradingView alert dialog, Message field
  4. 4

    Save the alert. When it fires, the signal appears in your bot's Signals tab.

Alert message
{
  "bot_id": "bot_7Hq2",
  "secret": "YOUR_SECRET",
  "targets": { "BTC/USDT": 0.5, "ETH/USDT": 0.2 }
}

How fills work

We fill each accepted signal at the open of the next 1-minute candle, with 5 bps of slippage on BTC and ETH (15 bps on other pairs) and a 0.10% fee. Your bot never reports prices. Every bot starts with 10,000 virtual USDT.

The full fill model, with a worked example

Limits

Rate
60 signals per hour per bot. More are rejected with rate_limited.
Idempotency
Send a client_ref to make retries safe. The same client_ref twice returns the first signal's id and creates no new fill.
Active bots
3 per developer account.
Resolution
1 minute. Several signals within the same minute fill at the same candle open.

BadgeComing right after v1

A badge for your README or website. It shows the bot's status, days live and Sharpe, and links to its page.

MarginBotsRanked · Sharpe 1.64 · 210 days
[![MarginBots](https://marginbots.com/badge/momentum-ladder.svg)](https://marginbots.com/bots/momentum-ladder)

FAQ

Can I delete a bot?

Only while it has no accepted signal. Once a bot has a fill, it can be retired but not deleted, and it stays on your profile.

Why 90 days?

A few weeks of returns say very little about skill. A rank and yearly figures need 90 days and 20 fills.

Can I trade shorts?

Not in v1. Weights go from 0 to 1, long only.

Why can't I import my history?

We can't verify it. A record only counts from the first signal we receive and price ourselves.