Bot API · network

Bot networks: one key for all your bots

If you run dozens, hundreds or thousands of bots, you neither connect them one by one nor share their tokens. You get one API key for the whole network; each bot requests an ad and sends it to the user itself. A new bot starts working with its first request.

POST https://hiviews.net/api/partner/ad

Quick start

  1. Sign in to the dashboard

    Open the web dashboard with the Telegram account the network is registered to. “My bots” shows the card of your network.

  2. Get the key

    In the network card open “Integration” and press “New key”. The key is shown once — keep it on your server and never publish it. If it leaks, issue a new one in the same place: the old key stops working at once.

  3. Put the code into your bots

    Take the ready code below. It is the same for every bot of the network: the bot name is filled in automatically.

  4. Call it in two places

    On /start — a welcome impression; after the bot has delivered its result to the person — a regular one. See “When to call”.

  5. Check

    Go through the five-point check on one bot, then roll out to the rest.

There is no need to register bots in advance. A bot joins the network at its first request and gets ads right away: bots are not moderated one by one.

Request

POST https://hiviews.net/api/partner/ad with a JSON body.

Headers

HeaderValue
AuthorizationThe network key as is, without a Bearer prefix
Content-Typeapplication/json

Body fields

FieldTypeDescription
UserIdint, requiredTelegram id of the user the bot will show the ad to. Private chats only.
Botstring, requiredUsername of the bot the ad is requested for, without @ (case does not matter). It must be a real bot username: Latin letters, digits and underscores, ending in bot.
StartPlacebooltrue — a welcome impression (on /start); false — a regular impression after the result. Without the field the request counts as a regular impression.
UserFirstNamestringThe user's first name: replaces $name in the post text.
UsernamestringThe user's username without @, if any.
LanguagestringThe user's Telegram language (language_code). Campaigns are matched by it. If it is missing we try to work the language out ourselves (from earlier ads to this person and from the name), but sending it is more accurate.
HasPhotobooleanOptional. true if the user has a profile photo (getUserProfilePhotos → total_count > 0; cache the result). It matters for campaigns marked “Verified”: only people with a photo and an ordinary name get them. If you do not send it, we check those who have a username ourselves — so always send Username. Do not send true for everyone: such bots stand out by their clicks and lose paid impressions.

Example

curl -X POST https://hiviews.net/api/partner/ad \
  -H "Authorization: YOUR_NETWORK_KEY" \
  -H "Content-Type: application/json" \
  -d '{"UserId": 123456789, "UserFirstName": "Ivan", "Language": "en", "StartPlace": true, "Bot": "my_first_bot"}'

Response

The response is always JSON. With an ad — "ok": true and an ad object; without one — "ok": false and a reason (all values).

{
  "ok": true,
  "ad": {
    "text": "<a href=\"https://hiviews.net/api/go/v/…\">&#8203;</a>Ivan, …",
    "parse_mode": "HTML",
    "reply_markup": {"inline_keyboard": [[{"text": "Open", "url": "https://hiviews.net/api/go/…"}]]},
    "photo": "https://hiviews.net/api/go/m/…",
    "delete_after": 60
  }
}
ad fieldWhat to do with it
textThe post text in HTML markup. Send it unchanged. With media, it is the caption.
parse_modeAlways HTML — pass it to the send method.
reply_markupA keyboard with link buttons. Pass it as is.
photo / video / animationAt most one is present: a link to the file. Send it with sendPhoto, sendVideo or sendAnimation, passing the link as the file. Without it — sendMessage.
delete_afterIf present, delete the message after that many seconds. This marks external ads, which are shown briefly.

How to send the post so the impression is paid

We cannot see what a bot sends to a user, so Telegram itself confirms the impression. Every post starts with an invisible link: when the message is sent, Telegram's servers open it and the impression is counted.

  • Send the post exactly as you received it. Do not cut the beginning of the text, rebuild the markup, or change links in the text or the buttons.
  • Keep link previews on. Do not pass disable_web_page_preview or link_preview_options that disable them. Many libraries switch previews off globally in the bot defaults — switch them on for the ad message.
  • Send right away. A confirmation is accepted within 3 minutes of handing out the post. A post sent later is not paid.
  • One request — one message to one person. Do not send the post to other users, and do not request ads without a user action.

An unconfirmed post is not paid: neither the impression nor its clicks. If a bot takes posts and does not send more than half of them, it stops getting ads — the answer is unconfirmed_sends.

When to call

MomentStartPlaceFormat
The person pressed /starttrueA welcome impression. Call before your own greeting or right after it.
The bot delivered its result: found a track, downloaded a file, answered a requestfalseA regular impression. Call after the result has been sent, so the ad is the last message.
  • Call in both places — that is how bots earn on both formats.
  • Do not send StartPlace: true everywhere: a welcome impression goes to one person in one bot at most once a day, extra requests come back without an ad.
  • The request must not hold the bot up: use a 5-second timeout and catch errors. The ready code does.

Ready code

The code is the same for every bot of the network. The bot name for the Bot field is taken from the bot itself, so nothing changes when you launch a new bot.

Python, aiogram 3

# HiViews bot network: one key for all bots. The bot sends the ad itself.
# pip install aiogram aiohttp
import asyncio, aiohttp
from aiogram import Bot
from aiogram.types import InlineKeyboardMarkup

HIVIEWS_KEY = "YOUR_NETWORK_KEY"

async def show_ad(bot: Bot, user, start: bool = False) -> None:
    """Call with start=True on /start, and with start=False after your bot delivered its result."""
    me = await bot.me()  # the bot that is answering the user right now
    payload = {"UserId": user.id, "UserFirstName": user.first_name, "Username": user.username,
               "Language": user.language_code, "StartPlace": start, "Bot": me.username}
    try:
        async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=5)) as s:
            async with s.post("https://hiviews.net/api/partner/ad", json=payload,
                              headers={"Authorization": HIVIEWS_KEY}) as r:
                ad = (await r.json()).get("ad")
        if not ad:
            return  # no ad for this person right now
        kw = {"parse_mode": "HTML", "reply_markup": InlineKeyboardMarkup.model_validate(ad["reply_markup"])}
        if ad.get("photo"):
            msg = await bot.send_photo(user.id, ad["photo"], caption=ad["text"], **kw)
        elif ad.get("video"):
            msg = await bot.send_video(user.id, ad["video"], caption=ad["text"], **kw)
        elif ad.get("animation"):
            msg = await bot.send_animation(user.id, ad["animation"], caption=ad["text"], **kw)
        else:
            msg = await bot.send_message(user.id, ad["text"], **kw)  # do not disable link previews
        if ad.get("delete_after"):  # such a post is removed after a minute unless it was clicked
            asyncio.get_running_loop().call_later(
                ad["delete_after"], lambda: asyncio.ensure_future(_quiet_delete(bot, user.id, msg.message_id)))
    except Exception:
        pass  # ads must never break the bot

async def _quiet_delete(bot: Bot, chat_id: int, message_id: int) -> None:
    try:
        await bot.delete_message(chat_id, message_id)
    except Exception:
        pass

# Example:  await show_ad(bot, message.from_user, start=True)   # /start
#         await show_ad(bot, message.from_user)               # after the result is delivered

Node.js, Telegraf

// HiViews bot network: one key for all bots. Node.js 18+, Telegraf.
const HIVIEWS_KEY = process.env.HIVIEWS_KEY; // YOUR_NETWORK_KEY

async function showAd(ctx, start = false) {
  // Call showAd(ctx, true) on /start, and showAd(ctx, false) after your bot delivered its result.
  try {
    const u = ctx.from;
    const res = await fetch("https://hiviews.net/api/partner/ad", {
      method: "POST",
      headers: { Authorization: HIVIEWS_KEY, "Content-Type": "application/json" },
      body: JSON.stringify({ UserId: u.id, UserFirstName: u.first_name, Username: u.username,
                             Language: u.language_code, StartPlace: start,
                             Bot: ctx.botInfo.username }), // the bot that is answering the user right now
      signal: AbortSignal.timeout(5000),
    });
    const { ad } = await res.json();
    if (!ad) return; // no ad for this person right now
    const extra = { parse_mode: "HTML", reply_markup: ad.reply_markup };
    let msg;
    if (ad.photo) msg = await ctx.telegram.sendPhoto(u.id, ad.photo, { caption: ad.text, ...extra });
    else if (ad.video) msg = await ctx.telegram.sendVideo(u.id, ad.video, { caption: ad.text, ...extra });
    else if (ad.animation) msg = await ctx.telegram.sendAnimation(u.id, ad.animation, { caption: ad.text, ...extra });
    else msg = await ctx.telegram.sendMessage(u.id, ad.text, extra); // do not disable link previews
    if (ad.delete_after) { // such a post is removed after a minute unless it was clicked
      setTimeout(() => ctx.telegram.deleteMessage(u.id, msg.message_id).catch(() => {}), ad.delete_after * 1000);
    }
  } catch (e) { /* ads must never break the bot */ }
}

// Example:  bot.start(async (ctx) => { await showAd(ctx, true); /* ... */ });

PHP

<?php
// HiViews bot network: one key for all bots.
const HIVIEWS_KEY = 'YOUR_NETWORK_KEY';

/** $user is the "from" array of the Telegram update, $botUsername is the bot's username without @. On /start: $start = true; after your result: false.
 *  Returns [message_id, delete_after] — if delete_after is not null, delete the message after that many seconds. */
function hiviews_show_ad(string $botToken, string $botUsername, array $user, bool $start = false): ?array
{
    $ch = curl_init('https://hiviews.net/api/partner/ad');
    curl_setopt_array($ch, [
        CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5,
        CURLOPT_HTTPHEADER => ['Authorization: ' . HIVIEWS_KEY, 'Content-Type: application/json'],
        CURLOPT_POSTFIELDS => json_encode([
            'UserId' => $user['id'], 'UserFirstName' => $user['first_name'] ?? '',
            'Username' => $user['username'] ?? null, 'Language' => $user['language_code'] ?? null,
            'StartPlace' => $start, 'Bot' => $botUsername, // the bot that is answering the user right now
        ]),
    ]);
    $ad = json_decode((string) curl_exec($ch), true)['ad'] ?? null;
    curl_close($ch);
    if (!$ad) {
        return null; // no ad for this person right now
    }
    $params = ['chat_id' => $user['id'], 'parse_mode' => 'HTML', 'reply_markup' => json_encode($ad['reply_markup'])];
    if (!empty($ad['photo'])) {
        $method = 'sendPhoto'; $params += ['photo' => $ad['photo'], 'caption' => $ad['text']];
    } elseif (!empty($ad['video'])) {
        $method = 'sendVideo'; $params += ['video' => $ad['video'], 'caption' => $ad['text']];
    } elseif (!empty($ad['animation'])) {
        $method = 'sendAnimation'; $params += ['animation' => $ad['animation'], 'caption' => $ad['text']];
    } else {
        $method = 'sendMessage'; $params += ['text' => $ad['text']]; // do not disable link previews
    }
    $ch = curl_init('https://api.telegram.org/bot' . $botToken . '/' . $method);
    curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10,
                            CURLOPT_POSTFIELDS => $params]);
    $sent = json_decode((string) curl_exec($ch), true);
    curl_close($ch);
    return [$sent['result']['message_id'] ?? null, $ad['delete_after'] ?? null];
}

// Example:  hiviews_show_ad($botToken, 'my_first_bot', $update['message']['from'], true);   // /start

No ad: the reason values

With "ok": false there is nothing to send — the bot simply carries on. The reason field tells you why.

HTTPreasonWhat it means and what to do
200no_adsNo fitting ad for this person right now. Normal.
200cap_viewer_window, cap_hello_window, cap_daily_cap, cap_externalThis person saw an ad recently. The caps protect users: at most one ad an hour and six a day across the network, a welcome ad once a day in one bot.
200placement_offThis format is switched off in the network settings in the dashboard.
200parallelAnother request for the same person arrived at the same moment. Nothing to do.
200rate_limitedToo many requests per minute (see limits). Requests above the limit just get no ad.
200unconfirmed_sendsTelegram did not confirm more than half of this bot's recent posts. Check that the post is sent unchanged and with link previews. Serving resumes by itself once confirmations recover.
400bad_requestThe body is not JSON, or UserId is missing or not a positive number.
400bad_keyNo Authorization header, or the key is wrong.
400bad_botThe Bot field is missing or does not look like a bot username.
400bot_takenA bot with this username is already connected to HiViews on its own (with or without a token). To run it through the network, delete its own connection in the dashboard.
400too_many_botsThe network has more than 50,000 bots — contact support.
403bot_pausedThe network is paused in the dashboard.
403bot_moderation, bot_bannedThe network is under review or blocked — contact support.

What is paid and how

  • Impressions. Every impression of a paid campaign whose sending Telegram confirmed is paid — welcome and regular. The rate per impression without a token is lower than for bots connected with a token.
  • Clicks. On pay-per-click campaigns — $0.04 per click. The first click of an impression is paid, at most one per address per hour.
  • External ads. Shown when no campaign of the network fits, so the bot is never idle. Paid per click only, the price depends on country and audience, the income is credited the next day.
  • Withdrawals. The income of all bots of the network goes to one balance. It becomes withdrawable after 7 days (anti-fraud check); withdrawals from $50 in USDT (TRC20), withdrawal fee: $3. See the finance guide.

The statistics in the network card show impressions, clicks and income of all bots together.

Quality control

Every link in an ad goes through our tracking, and we know how often the same campaigns are clicked in bots where we deliver the ads ourselves. So for every bot and for the network as a whole we know how many clicks such a number of impressions should bring.

  • Many impressions with almost no clicks, or with implausibly many, switch paid impressions off automatically — for that bot, or for the whole network if the mismatch shows across it. Pay-per-click stays.
  • Clicks are counted by origin: many links opened from one address are one click.
  • Income from fake impressions is returned to advertisers. Do not request ads for made-up UserIds and do not send posts to service chats.

Limits

WhatValue
Requests per minute for one bot1,200
Requests per minute for the whole network120,000
Bots in a networkup to 50,000
Time to send a post after receiving it3 minutes
Recommended request timeout5 seconds

Need more? Contact support — the limits can be raised.

Checking the integration

  1. The request goes through

    Send the example request with your key and the username of one of your bots. "ok": true with an ad object, or "ok": false with no_ads, both mean the key and the bot name are accepted.

  2. The post arrives whole

    Press /start in the bot from your own account. The ad should arrive with its button and, if the post has a picture or a video, with it.

  3. The button goes through hiviews.net

    The button link starts with https://hiviews.net/api/go/. If it does not, the code changes links and clicks will not be counted.

  4. The impression shows up in the statistics

    A minute later open the network card in the dashboard: the impression should be in the statistics. If it is not, the post was changed or link previews are off.

  5. The regular impression comes after the result

    Make a request in the bot and make sure the ad arrives after the result, not before it.

Network settings

Settings are made once in the network card in the dashboard and apply to all its bots.

  • Formats: the welcome or the regular impression can be switched off.
  • Ad categories: you can refuse any — betting, VPN, dating and others.
  • External ads and pay-per-click ads are switched on and off separately.
  • Pause stops ads in all bots of the network at once.
  • The network's topics are set by support when the network is created; to change them, contact support.

FAQ

Do I have to share bot tokens?

No. The bots send the ads themselves, with their own tokens, which stay with you. We only need the network key in the request header.

How do I add a new bot to the network?

You do not add it separately: start sending requests with its username in the Bot field. The bot registers on its first request and gets ads right away.

Can I use the network key for a bot that is already connected on its own?

No, the request returns bot_taken. Delete the bot's own connection in the dashboard — after that it works through the network. Keep in mind that a bot connected with a token has a higher rate per impression.

Why are there impressions but no payment for them?

Impressions confirmed by Telegram are paid. Check that the post is sent unchanged, with link previews on, and within 3 minutes of receiving it.

What happens if one bot of the network breaks the rules?

Paid impressions are switched off for that bot; the others keep working. If the mismatch between impressions and clicks shows across the network, paid impressions are switched off for the whole network.

Need a network key or have questions? A network is set up on request: tell support how many bots you have and what they are about.