📺 NotifyBot API  Real-time OTT episode alerts
Base URL https://notification.ottdlbot.online

Episode alerts for your own apps

The same instant feed that powers the NotifyBot Telegram bot — every new serial episode, preview, trailer and movie across 12 OTT platforms — delivered to your server as JSON.

⚡ Real-timeEpisodes land seconds after they go live on the platform.
🪝 Push or pullSigned webhooks, or poll /v1/alerts with a cursor.
🖼 Rich dataShow, episode number, title, language, poster and watch link.

Quick start

  1. Buy an API plan from an admin, then open the bot and send /api. Your API key arrives in a private message — it is shown only once.
  2. Call the API from your server. The first successful call locks the key to that server's IP (see IP lock).
  3. Poll for new episodes, or register a webhook with /api webhook add https://your-server/hook.
curl -H "Authorization: Bearer nb_YOUR_KEY" \
  "https://notification.ottdlbot.online/v1/alerts?since=1790500000&limit=50"

Authentication

Send your key with every request, in either header:

Authorization: Bearer nb_YOUR_KEY
X-Api-Key: nb_YOUR_KEY

Keys start with nb_. Lost or leaked a key? Tap 🔑 New API key in /api — the old key stops working immediately.

Server IP lock

Each personal key works from one server IP. The first server that calls the API is registered automatically; requests from any other IP are refused with 403 ip_not_allowed.

  • Moving servers? Send /api ip 203.0.113.10 in the bot. The IP can be changed once every 7 days.
  • Webhooks must point to that same server — the webhook host has to resolve to your registered IP.
  • Calling from behind a proxy or NAT? Register the public IP the API sees (it is shown in the 403 error message).

Rate limits

A burst of 30 requests, refilling at 1 request per second per key. Over the limit you get 429 with {"error":"rate_limited","retry_after":1}. Polling /v1/alerts every 5–10 seconds is well within the limit; webhooks need no polling at all.

Endpoints

GET/v1/alerts

New episodes in the order they were detected. The main endpoint for polling.

QueryTypeDescription
sincenumberUnix time (seconds). Returns alerts detected after it. Default: last hour.
platformstringOnly one platform, e.g. ZEE5. See platforms.
limitint1–200, default 50.

Response: {"alerts": [Episode…], "next_since": 1790500433.48}. Pass next_since back as since to page forward; it is null when you are caught up.

{
  "alerts": [
    {
      "platform": "ZEE5",
      "show_id": "0-6-4z5723311",
      "serial": "Manetana",
      "season": "1",
      "epnum": 560,
      "epname": "Manetana - September 27, 2026",
      "ctype": "Serials",
      "language": "Kannada",
      "release_date": "27-September-2026",
      "release_ts": 1790447400.0,
      "created_at": 1790500433.48,
      "url": "https://notification.ottdlbot.online/w/…",
      "image": "https://akamaividz2.zee5.com/image/upload/…jpg",
      "key": "0-1-6z51082258",
      "spoiler": false
    }
  ],
  "next_since": null
}
GET/v1/shows

Search the catalogue of tracked shows.

QueryTypeDescription
qstringTitle search (case-insensitive).
platformstringFilter by platform.
languagestringFilter by language, e.g. Marathi.
activebool1 = currently airing only, 0 = not airing.
limit / cursorintPage size (1–200, default 50) and offset. Use next_cursor from the response.

Response: {"shows":[{"show_id","platform","serial","language","last_ep","episode_count","active","poster","last_episode_at"}…], "total": 1834, "next_cursor": 50}

GET/v1/shows/{platform}/{show_id}/episodes

Episode history of one show, newest first.

QueryTypeDescription
beforenumberOnly episodes detected before this Unix time. Use next_before to page back.
limitint1–200, default 50.
curl -H "X-Api-Key: nb_YOUR_KEY" \
  "https://notification.ottdlbot.online/v1/shows/SonyLIV/1700000195/episodes?limit=5"
GET/v1/health

No key needed. Returns {"ok": true, "service": "notifybot-api"} — use it for uptime checks.

Episode object

FieldTypeDescription
platformstringOTT platform, e.g. JioHotstar.
show_idstringPlatform's show id.
serialstringShow / movie title.
season, epnumstring, intSeason and episode number (empty for movies). Batch cards use a range like "11-14".
epnamestringEpisode title.
ctypestringSerials, Previews, Trailers or Movies.
language, languagesstring, string[]Primary language and all audio languages when known.
release_ts, release_datenumber, stringPlatform's release time (Unix seconds) and date label.
created_atnumberWhen we detected it (Unix seconds). This is the since cursor.
urlstringWatch link. Opens the episode on the OTT platform.
imagestringPoster / thumbnail URL.
keystringPlatform's content id — unique per episode. Use it to de-duplicate.
spoilerbooltrue for previews / promos.

Errors

StatuserrorMeaning
401unauthorizedMissing or invalid API key.
403ip_not_allowedRequest came from an IP other than your registered server.
403subscription_expiredYour API plan has ended — renew via an admin, then check /api.
403forbiddenKey paused or revoked.
429rate_limitedSlow down; retry after retry_after seconds.
500internalOur side — retry with backoff.

Webhooks

Instead of polling, let us POST each new episode to your server the moment it is detected.

  • Add up to 2 URLs in the bot: /api webhook add https://your-server/hook (list with /api webhook, remove with /api webhook del 1).
  • The URL's host must resolve to your registered server IP.
  • Reply with any 2xx within 8 seconds. Failed deliveries are retried after 5 s, 15 s, 45 s, 2 min and 5 min.
  • A delivery can arrive more than once — de-duplicate on delivery_id (or the episode key).
POST /hook HTTP/1.1
Content-Type: application/json
User-Agent: NotifyBot-Push/1.0
X-NB-Timestamp: 1790500434
X-NB-Signature: sha256=5f0c…e91a

{"type": "episode.released", "delivery_id": "alert:…:9f3a", "episode": { …Episode object… }}

Verify signatures

Get your signing secret from 🔏 Webhook secret in /api. Compute HMAC-SHA256(secret, timestamp + "." + raw_body) and compare it with X-NB-Signature. Reject requests whose timestamp is more than 5 minutes old.

import hmac, hashlib, time

def verified(secret: str, ts: str, signature: str, raw_body: bytes) -> bool:
    if abs(time.time() - int(ts)) > 300:
        return False
    mac = hmac.new(secret.encode(), ts.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest("sha256=" + mac, signature)
const crypto = require("crypto");

function verified(secret, ts, signature, rawBody) {
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const mac = crypto.createHmac("sha256", secret).update(ts + ".").update(rawBody).digest("hex");
  const a = Buffer.from("sha256=" + mac), b = Buffer.from(signature);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
function verified($secret, $ts, $signature, $rawBody) {
    if (abs(time() - intval($ts)) > 300) return false;
    $mac = hash_hmac('sha256', $ts . '.' . $rawBody, $secret);
    return hash_equals('sha256=' . $mac, $signature);
}

Code examples

Poll for new episodes

import time, requests

API = "https://notification.ottdlbot.online"
HEADERS = {"Authorization": "Bearer nb_YOUR_KEY"}
since = time.time() - 3600
seen = set()

while True:
    r = requests.get(f"{API}/v1/alerts", headers=HEADERS,
                     params={"since": since, "limit": 100}, timeout=15)
    if r.status_code == 429:
        time.sleep(r.json().get("retry_after", 1)); continue
    r.raise_for_status()
    data = r.json()
    for ep in data["alerts"]:
        if ep["key"] in seen:
            continue
        seen.add(ep["key"])
        print(f'{ep["platform"]} · {ep["serial"]} E{ep["epnum"]} · {ep["language"]} → {ep["url"]}')
        since = max(since, ep["created_at"])
    if data["next_since"] is None:
        time.sleep(10)
const API = "https://notification.ottdlbot.online";
const headers = { Authorization: "Bearer nb_YOUR_KEY" };
let since = Date.now() / 1000 - 3600;

async function poll() {
  const res = await fetch(`${API}/v1/alerts?since=${since}&limit=100`, { headers });
  if (res.status === 429) return setTimeout(poll, 1000);
  const data = await res.json();
  for (const ep of data.alerts) {
    console.log(`${ep.platform} · ${ep.serial} E${ep.epnum} → ${ep.url}`);
    since = Math.max(since, ep.created_at);
  }
  setTimeout(poll, data.next_since === null ? 10000 : 0);
}
poll();
curl -s -H "X-Api-Key: nb_YOUR_KEY" \
  "https://notification.ottdlbot.online/v1/alerts?since=$(( $(date +%s) - 3600 ))&platform=SonyLIV" | jq '.alerts[] | {serial, epnum, url}'

Receive webhooks (Python / Flask)

from flask import Flask, request, abort
app = Flask(__name__)
SECRET = "ws_YOUR_SIGNING_SECRET"

@app.post("/hook")
def hook():
    if not verified(SECRET, request.headers.get("X-NB-Timestamp", "0"),
                    request.headers.get("X-NB-Signature", ""), request.get_data()):
        abort(401)
    ep = request.get_json()["episode"]
    print("New:", ep["serial"], ep["epnum"], ep["url"])
    return "", 204

Platforms & values

Platforms (use exactly as written): ZEE5 JioHotstar SonyLIV SunNXT DangalPlay ManoramaMAX TarangPlus PrimeVideo Aha Netflix ETVWin Stage

Content types: Serials Previews Trailers Movies

Languages: full English names — Hindi Bengali Tamil Telugu Kannada Malayalam Marathi Gujarati Odia Punjabi Bhojpuri Haryanvi Rajasthani English and international languages.

Fair use & monitoring

Personal use only. An API plan is for one person's own apps on one server. You may not resell it, share the key, run it on multiple servers, or use it to operate a public bot or channel that redistributes alerts or sells subscriptions.

To protect the service, we record how every key is used: request times, IP addresses and user agents, webhook destinations, and opens of the watch links included in API data (links go through notification.ottdlbot.online/w/… and redirect to the OTT platform). These records are used only for security, abuse prevention and support, and are kept for 90 days.

Keys used from other servers, shared, or used to redistribute alerts are revoked without a refund.

Support

Questions, a new plan or a key reset: message the admin through the NotifyBot Telegram bot. Manage your key, server IP and webhooks anytime with /api.