AniBTWiki
Open API

Integration examples

Executable conditional RSS reads and bounded deletion polling.

Public RSS returns XML with .torrent enclosures. Combine anime and group filters; use separate category feeds for other releases.

RSS URLs
https://anibt.net/rss/anime.xml?bgmId=543360&groupSlug=kirara-fantasia
https://anibt.net/rss/anime.xml?bgmId=543360&resolution=1080p&language=CHS
https://anibt.net/rss/group/kirara-fantasia.xml
https://anibt.net/rss/other/music.xml

These scripts use the Python 3 standard library. Save the body before its ETag; a 304 only reuses an existing local body.

conditional_rss_poll.py
import os
from pathlib import Path
from urllib.error import HTTPError
from urllib.request import Request, urlopen

url = os.environ.get("FEED_URL", "https://anibt.net/rss/anime.xml?bgmId=543360")
feed_file = Path("feed.xml")
etag_file = Path("feed.etag")
headers = {"User-Agent": "AniBT-RSS-example/1.0"}
if feed_file.exists() and etag_file.exists():
    headers["If-None-Match"] = etag_file.read_text().strip()
try:
    with urlopen(Request(url, headers=headers), timeout=30) as response:
        body = response.read()
        etag = response.headers.get("ETag", "")
        status = response.status
except HTTPError as error:
    if error.code == 304:
        print("Unchanged; reuse feed.xml")
        raise SystemExit(0)
    raise SystemExit(f"HTTP {error.code}; Retry-After={error.headers.get('Retry-After', 'unknown')}")
if status != 200:
    raise SystemExit(f"Unexpected HTTP {status}")
# Commit the body before its validator; never mark an undownloaded body current.
feed_file.with_suffix(".tmp").write_bytes(body)
feed_file.with_suffix(".tmp").replace(feed_file)
etag_file.write_text(etag)
print("Saved feed.xml")

The deletion example reads RELEASE_ID and ANIBT_API_KEY from environment variables and polls for at most 180 seconds. Only completed means success. HTTP errors, failed receipts, and malformed bodies stop the script.

delete_and_wait.py
import json
import os
import time
from urllib.error import HTTPError
from urllib.request import Request, urlopen

base = os.environ.get("ANIBT_BASE_URL", "https://anibt.net").rstrip("/")
release_id = os.environ["RELEASE_ID"]
key = os.environ["ANIBT_API_KEY"]
deadline = time.monotonic() + 180

def call(method, path):
    request = Request(base + path, method=method, headers={"Authorization": f"Bearer {key}"})
    try:
        response = urlopen(request, timeout=20)
    except HTTPError as error:
        response = error
    with response:
        raw = response.read()
        status = response.status
        retry_after = response.headers.get("Retry-After")
        request_id = response.headers.get("X-Request-ID", "unknown")
    if status == 429:
        wait = int(retry_after) if retry_after and retry_after.isdigit() else 5
        return None, max(1, wait)
    try:
        data = json.loads(raw)
    except ValueError:
        raise SystemExit(f"Non-JSON response: HTTP {status}, requestId={request_id}")
    if status not in (200, 202) or data.get("ok") is not True:
        code = data.get("error", {}).get("code", "unknown")
        raise SystemExit(f"HTTP {status}: {code}, requestId={request_id}")
    result = data.get("result")
    if not isinstance(result, dict) or result.get("state") not in ("pending", "completed", "failed"):
        raise SystemExit("Invalid deletion receipt")
    wait = int(retry_after) if retry_after and retry_after.isdigit() else 2
    return result, max(1, wait)

receipt, wait = call("DELETE", f"/api/releases/{release_id}")
if receipt is None:
    raise SystemExit(f"DELETE rate limited; retry after {wait} seconds")
while True:
    if receipt is not None and receipt["state"] == "completed":
        print("Deletion completed")
        break
    if receipt is not None and receipt["state"] == "failed":
        raise SystemExit(f"Deletion failed: {receipt.get('failure', {}).get('code', 'unknown')}")
    remaining = deadline - time.monotonic()
    if remaining <= wait:
        raise SystemExit("Polling timed out; keep the releaseId and check its status later")
    time.sleep(wait)
    receipt, wait = call("GET", f"/api/releases/{release_id}/deletion")