Documentation hub

Everything you need to build on Ferogram.

This page is the map: install, log in, send your first message, and know where every other doc actually lives. Beginner-friendly top to bottom, with the deeper reference material just a click away.

Start here

Why docs are split up

The Ferogram ecosystem is deliberately spread across a few different pieces: a Rust core, Python bindings powered by that same core, and a separate voice/video calling stack (TgCalls) built on top of it. That's three quite different audiences, reading three quite different kinds of API, often at the same time.

A single docs site can't serve all of that well. Rust users want trait signatures and feature flags. Python users want a Pythonic API reference with type hints, not Rust internals. TgCalls users care about media pipelines and call signaling, not message parsing. So instead of flattening everything into one generic reference, each part of the ecosystem gets documentation shaped for how it's actually used, and this hub ties them together.

Think of docs.ferogram.dev as the front door: the guide for getting started, logging in, sending your first message, and understanding how the pieces fit, plus a map to every deeper reference.

Start here

Where the docs live

Three destinations, depending on what you're working with:

For the Rust core, every crate publishes its own docs.rs reference, generated straight from source comments. Most people only ever need the first one:

Rule of thumb
Writing Rust that talks directly to Telegram → docs.rs/ferogram. Writing Python → python.ferogram.dev. Streaming audio/video into a call → tgcalls.ferogram.dev. Everything else on this page applies to all three.
Start here

Which version to use

Use ferogram 0.6.5 or newer. Each release brings meaningful performance work on top of correctness and feature fixes, so staying current gets you the most out of the framework with no code changes required.

Recommended
ferogram = "0.6.5" (Rust) and pip install ferogram for the latest wheel (Python). Both track the same core version.

Development on GitHub moves faster than crates.io. If you need something from main, point Cargo at a specific commit instead of waiting on a release:

Cargo.toml
ferogram = { git = "https://github.com/ankit-chaubey/ferogram", rev = "COMMIT_SHA" }
Guide

Installation

Pick your language. Both sit on the exact same Rust core underneath, so networking, encryption, and MTProto behavior are identical either way.

Cargo.toml
[dependencies]
ferogram = "0.6.5"
tokio        = { version = "1", features = ["full"] }

ferogram re-exports everything you need for both user clients and bots - you don't add the other workspace crates yourself.

shell
pip install ferogram

Prebuilt wheels ship for Linux (x86_64, aarch64), macOS (x86_64, arm64), Windows (x86_64), and Android/Termux (aarch64, x86_64). pip grabs the right one automatically - no Rust toolchain required.

Building from source instead (contributing, or an unsupported platform):

shell
make dev      # editable install into .venv, builds the Rust extension
make build    # release wheel for this machine
make test     # run tests

On Termux: install rust clang python via pkg first.

Getting API credentials

Every Telegram API call needs an api_id (integer) and api_hash (hex string) tied to a registered application. Both languages need the same two values.

  1. Go to my.telegram.org and log in with your phone number
  2. Click API development tools
  3. Fill in any app name, short name, platform, and URL (URL can be blank)
  4. Click Create application
  5. Copy the App api_id and App api_hash

Bot token (bots only)

For bots, also get a token from @BotFather: open a chat with it, send /newbot, choose a display name and a username ending in bot, then copy the token it gives you - it looks like 1234567890:ABCdefGHIjklMNOpqrSTUvwxYZ.

Never hardcode credentials
Keep api_id, api_hash, and bot tokens out of source control. Read them from environment variables or a secrets file that's in .gitignore, especially in a public repo.
Guide

Your first message

The fastest path to a working client, in each language, for both a bot and a user account.

User account

Client::quick_connect handles the full interactive login - phone number, code, 2FA if enabled - in one call. On the first run it'll prompt you in the terminal; after that, the saved session skips login entirely.

main.rs
use ferogram::Client;

const API_ID: i32 = 0; // from https://my.telegram.org
const API_HASH: &str = "";

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let (client, _shutdown) = Client::quick_connect("my.session", API_ID, API_HASH).await?;

    client.send_message("me", "Hello from ferogram!").await?;
    client.save_session().await?;
    Ok(())
}

That sends "Hello from ferogram!" straight to your own Saved Messages.

Bot

main.rs
use ferogram::Client;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let (client, _shutdown) = Client::builder()
        .api_id(API_ID)
        .api_hash(API_HASH)
        .bot_token("1234567890:ABCdef...")
        .session("mybot.session")
        .connect().await?.await?;

    client.save_session().await?;
    Ok(())
}

User account

main.py
import asyncio
from ferogram import Client

app = Client("myaccount", api_id=0, api_hash="", phone="+1234567890")

async def main():
    async with app as client:
        await client.send_message("me", "logged in")

asyncio.run(main())

The first run prompts for your phone number, login code, and 2FA password (if set) right in the terminal. After that, the saved session file skips it. Credentials also work from env vars: API_ID, API_HASH, BOT_TOKEN.

Bot

main.py
from ferogram import Client, filters

app = Client("mybot", api_id=0, api_hash="", bot_token="123:TOKEN")

@app.on_message(filters.command("start"))
async def start(client, message):
    await message.reply("Hello!")

app.run()
Guide

Authentication, step by step

quick_connect (Rust) and Client(...).start() (Python) cover login interactively in one call for most people. This section is for when you need explicit control - a custom prompt, a headless server, or a non-interactive setup.

User login (phone + code + 2FA)

Three steps under the hood:

  1. Request a code - sent via SMS or the Telegram app itself
  2. Submit the code - returns your account name, or asks for a 2FA password if one is set
  3. Submit the 2FA password (if required) - verified via SRP; your password is never sent in plain text, only a cryptographic proof
Rust
let token = client.request_login_code("+1234567890").await?;

match client.sign_in(&token, "12345").await {
    Ok(name) => println!("Signed in as {name}"),
    Err(SignInError::PasswordRequired(pw_token)) => {
        client.check_password(pw_token, "my_2fa_password").await?;
    }
    Err(e) => return Err(e.into()),
}
client.save_session().await?;

Bot login

Simpler - one call with the token from BotFather:

Rust
if !client.is_authorized().await? {
    client.bot_sign_in(BOT_TOKEN).await?;
    client.save_session().await?;
}

QR code login (Rust)

For desktop-style "scan to log in" flows. Export a login token, render it as a QR code pointing at tg://login?token=..., then poll until the user scans it from another logged-in device:

Rust
let (token_bytes, expires) = client.export_login_token().await?;
// base64url-encode token_bytes into a tg://login?token=... QR code

// poll after the user scans it
if let Some(name) = client.check_qr_login(token_bytes).await? {
    println!("Signed in as {name}");
}

Save your session

Whichever path you used, always persist it - otherwise every run logs in again from scratch:

client.save_session().await?;
Guide

Enabling features

Ferogram ships a minimal default build in Rust; opt-in Cargo feature flags pull in exactly what you need and nothing else.

FlagWhat it does
sqlite-sessionPersist sessions to a local SQLite database instead of the binary file - better crash resilience for long-running bots.
libsql-sessionPersist sessions via libSQL, local file or embedded replica.
libsql-remote-sessionPersist sessions to a remote libSQL/Turso server, with replication.
parsers (alias: html)HTML/Markdown message parsing for rich text - ParseMode and entity building.
html5everSwaps in the html5ever tokenizer for stricter, spec-compliant HTML parsing.
fsmEnables the finite-state-machine dispatcher helpers for multi-step conversations.
deriveEnables #[derive(FsmState)] and related proc-macros.
socks5SOCKS5 proxy support. Off by default; not needed for direct connections to Telegram.
resilient-connectDNS-over-HTTPS and special-config fallback for censored networks. Pulls in reqwest.
experimentalExperimental transfer APIs, including resumable transfers.
metricsRPC/connection instrumentation (counters, histograms, gauges) via the metrics crate.
serdeDerives Serialize/Deserialize on session types, for custom storage backends.
Cargo.toml
ferogram = { version = "0.6.5", features = ["sqlite-session", "parsers", "fsm"] }
Python
The published wheel bundles sqlite-session and socks5 support already - there's nothing to enable, they just work. Everything else in the table is Rust-only, part of the compiled extension.
Guide

Additional settings

Proxy support

SOCKS5, with optional authentication, for routing traffic through a proxy of your choice. Enable with the socks5 feature in Rust; built in for Python.

MTProxy

Native support for Telegram's own MTProxy transports - Classic, DD, and FakeTLS - configured either from a t.me/proxy?... link or manually.

Transport probing

When multiple transports are viable, Ferogram races them and connects over whichever responds first, instead of guessing or trying them one at a time.

Resilient connect

On networks that block or interfere with direct TCP to Telegram's data centers, the resilient-connect feature adds DNS-over-HTTPS and a special-config fallback so the client can still find its way in.

Session backends

Binary file by default, with SQLite, LibSQL/Turso, in-memory, and portable base64 string sessions available - see session_backend on docs.rs for the full trait and every built-in backend.

Responsible use

Session security

A session file (or session string) is not a low-stakes artifact. Whoever holds it has full API access to the account it belongs to - reading messages, sending as you, joining or leaving chats, all of it. Treat it like a password, not a config file.

Never share your session file
Not in a bug report, not in a support chat, not in a public repo. A leaked .session file or exported session string is equivalent to handing someone your logged-in account.
  • Add session files to .gitignore: *.session, *.session.db
  • Set restrictive file permissions: chmod 600 my.session
  • Never log or print session file contents, or a session string, anywhere
  • If a session is ever compromised, revoke it immediately from Telegram → Settings → Devices → Terminate session
  • Prefer environment variables or a secrets manager over hardcoding api_id / api_hash / bot tokens in source
Responsible use

Terms of service & your responsibility

Ferogram gives you direct, low-level access to Telegram's own API - the same access Telegram's official apps use. That's powerful, and it comes with responsibility. Usage of this library must comply with Telegram's API Terms of Service.

Ferogram is a framework, not an actor. It sends the requests you tell it to send. Whatever you build with it - a bot, a userbot, an automation script - you are responsible for what it does: rate limits, spam prevention, user consent, data handling, and staying within Telegram's rules for automated and user-account behavior alike. Neither the library nor its maintainer is responsible for how an application built with it is used.

If you're building something that acts on a real user account (a "userbot"), be especially deliberate: user accounts are held to Telegram's normal user-facing terms, not bot-specific allowances, and abuse there tends to get accounts limited or banned.

Project

Voice & video calls

Group calls, P2P calls, screen share, and conference calls live in their own crate, tgcalls, built on top of ferogram. Joining a group call and playing a file takes a few lines:

Rust
let calls = Calls::new(client);
calls.play(chat_id, "song.mp3").await?;

Full guide, media pipeline details, and the P2P signaling walkthrough live at tgcalls.ferogram.dev. Requires FFmpeg on your PATH for media decoding.

Project

About the maintainer

Ferogram, ferogram-py, and TgCalls are built and maintained by Ankit Chaubey. The project started from a straightforward frustration: existing MTProto libraries kept getting in the way for things that should have been simple, and asking them to behave differently wasn't always possible. So the whole stack, the .tl schema parser, the code generator, AES-IGE crypto, the DH key exchange, MTProto framing, the session layer, and the client on top, was written from scratch, one crate at a time.

Ankit Chaubey
Ankit Chaubey
Creator & maintainer - Ferogram, ferogram-py, TgCalls

Group audio calls are stable and already in production use; group video delivers full HD, high-quality video and high-quality audio with no codec issues; P2P calls are still actively evolving. Secret chats (end-to-end encryption) are fully implemented in the core but not yet published to crates.io, pending enough community demand to prioritize it.

Project

Community

Questions, discussions, bug reports, and feedback are always welcome.

Licensed under MIT OR Apache-2.0. Usage must comply with Telegram's API Terms of Service.