Telegram Login API: Session, OTP Code & 2FA Password

Published October 9, 2026

Every call in the Telegram Messaging API after the first one carries a sessionId. That ID represents a logged-in Telegram user account, and you obtain it through a short login flow: create a session, hand over the login code Telegram sends to the account, and, if the account has two-step verification, submit its cloud password. Once the status reads CONNECTED, the same sessionId authorizes messaging, chats, contacts and group operations.

This guide walks through the whole flow in curl and Python: phone login with an OTP code, the 2FA password step, QR login, and restoring a saved session without a new code. Log in only to accounts you own or are authorized to operate, and follow Telegram's Terms of Service.

How the login flow works

Three modes start the same way, with POST create-session, and all end at CONNECTED:

  1. Phone: you provide the phone number, Telegram delivers a login code to the account, and you submit that code.
  2. QR: you display a QR code and the account owner scans it from Telegram's Devices screen.
  3. Restore: you pass a previous sessionId and the saved login reconnects with no new code.

After every step you poll get-session and read its status. The values you will see are CREATED, WAITING_FOR_SCAN, WAITING_FOR_OTP, CONNECTING, CONNECTED, DISCONNECTED, LOGGED_OUT and FAILED. In phone mode you poll until the status is WAITING_FOR_OTP, submit the code, and keep polling until it reads CONNECTED. The code and password submissions are accepted asynchronously: the call returns right away and the final result shows up in the status.

Step 1: Get an API key

Sign up and copy an active key from your Dashboard. New accounts include free credits for testing the full flow.

Step 2: Create a phone session

create-session takes a JSON payload. For phone login it contains mode, phoneNumber in full international format with the leading +, and a deviceName label. The payload is sent as a string inside a body field:

curl -X POST 'https://api.opendata-api.com/api/project/telegram-messaging/create-session?key=YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"body":"{\"mode\":\"phone\",\"phoneNumber\":\"+15551234567\",\"deviceName\":\"My Bot\"}"}'

The reply is wrapped twice, as {"code": 200, "message": "success", "data": {"code": 201, "data": {...}}}: the outer layer comes from the platform and the inner one from the messaging service, which always answers with HTTP 200 and reports a failure in its own code, title and detail. The new session's identifier is the id field of the innermost payload, data.data.id in the raw JSON. Keep it, because every following call needs it as the sessionId query parameter. Then poll the session until its status reads WAITING_FOR_OTP and submit the login code.

Step 3: Wait until the session asks for the code

The call() helper unwraps both layers, treats the codes 200, 201, 202 and 204 as success, and raises with the full body on anything else. wait_for() polls get-session.

import json
import time
import requests

API_KEY = "YOUR_API_KEY"
BASE = "https://api.opendata-api.com/api/project/telegram-messaging"
SUCCESS_CODES = {200, 201, 202, 204}


def call(method, operation, payload=None, **params):
    """Call one operation and return the innermost payload (dict, list or None)."""
    params["key"] = API_KEY
    kwargs = {"params": params, "timeout": 60}
    if payload is not None:
        kwargs["json"] = {"body": json.dumps(payload)}
    resp = requests.request(method, f"{BASE}/{operation}", **kwargs)
    if not resp.ok:
        raise RuntimeError(f"{operation} failed: HTTP {resp.status_code} {resp.text}")
    body = resp.json()
    # Peel both envelopes: {"code": 200, "data": {"code": 201, "data": {...}}}
    while isinstance(body, dict) and "code" in body:
        if body["code"] not in SUCCESS_CODES:
            raise RuntimeError(f"{operation} failed: {body}")
        body = body.get("data")
    return body


def wait_for(session_id, wanted, timeout=120, stop_on_2fa=False):
    """Poll get-session until `status` is in `wanted`, or until 2FA is requested."""
    deadline = time.time() + timeout
    while time.time() < deadline:
        state = call("GET", "get-session", sessionId=session_id)
        status = state.get("status")
        if status in wanted or (stop_on_2fa and state.get("twoFactorRequired")):
            return state
        if status in ("FAILED", "LOGGED_OUT"):
            raise RuntimeError(f"login ended with status {status}")
        time.sleep(2)
    raise TimeoutError("session did not reach the expected status in time")

get-session also reports the login mode and phone number, and, while a login is pending, the qrImage or the twoFactorRequired flag. If the account is saved but offline, calling it reconnects the account for you.

Step 4: Submit the OTP code

The login code arrives inside the Telegram app of the account, not by SMS. Submit it with verify-otp while the status is WAITING_FOR_OTP; it is not valid in any other state.

curl -X POST 'https://api.opendata-api.com/api/project/telegram-messaging/verify-otp?key=YOUR_API_KEY&sessionId=YOUR_SESSION_ID' \
  -H 'Content-Type: application/json' \
  -d '{"body":"{\"code\":\"12345\"}"}'

Because the call is accepted asynchronously, do not treat the immediate response as a verdict. Poll get-session again: you will see either CONNECTED, or twoFactorRequired=true when the account is protected by a cloud password (the status itself stays WAITING_FOR_OTP in that case).

Step 5: Submit the 2FA password when required

If two-step verification is enabled on the account, send the password with submit-password. It is only valid while the session shows twoFactorRequired=true (the status stays WAITING_FOR_OTP meanwhile), which can happen after the OTP step (phone mode) or after the QR scan (QR mode).

import getpass

session = call("POST", "create-session",
               {"mode": "phone", "phoneNumber": "+15551234567", "deviceName": "My Bot"})
# The identifier is the "id" field; "sessionId" is only the query-parameter name.
session_id = (session or {}).get("id")
if not session_id:
    raise RuntimeError(f"no session id in the response: {session}")

wait_for(session_id, {"WAITING_FOR_OTP"})
code = input("Login code from the Telegram app: ").strip()
call("POST", "verify-otp", {"code": code}, sessionId=session_id)

state = wait_for(session_id, {"CONNECTED"}, stop_on_2fa=True)
if state.get("status") != "CONNECTED":
    password = getpass.getpass("Telegram cloud password: ")
    call("POST", "submit-password", {"password": password}, sessionId=session_id)
    state = wait_for(session_id, {"CONNECTED"}, timeout=60)

print("status:", state.get("status"))
print("session id to keep for restore:", state.get("id", session_id))
print("account:", call("GET", "get-account", sessionId=session_id))

The script reads the code and the password interactively and never stores them. In a real product, collect them in your own UI over HTTPS, forward them once and do not write them to logs. The final get-account call confirms exactly which account the session is connected as.

QR login

If the owner prefers not to receive a code, create a session in qr mode instead:

curl -X POST 'https://api.opendata-api.com/api/project/telegram-messaging/create-session?key=YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"body":"{\"mode\":\"qr\",\"deviceName\":\"My Bot\"}"}'

The create response returns a qrPageUrl you can open to watch the login live. If you want to draw the QR code in your own interface, poll get-session and render the qrImage it returns, a base64 data-URI. On the phone, the owner opens Telegram, goes to Settings, then Devices, then Link Desktop Device, and scans the code. If the account has a cloud password, finish with submit-password exactly as above.

Restore a saved session

You do not repeat the login after a restart. Pass the previous ID to create-session in restore mode and the saved login reconnects without a new code:

curl -X POST 'https://api.opendata-api.com/api/project/telegram-messaging/create-session?key=YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"body":"{\"mode\":\"restore\",\"sessionId\":\"PREVIOUS_SESSION_ID\"}"}'

Once connected, the session ID becomes a stable ID derived from the account, and both the original and the stable ID keep working. Read the id that get-session reports after login and store it for future restore calls. In restore mode the payload field is called sessionId, as shown above.

Log out or disconnect

disconnect-session is a DELETE call. By default it only drops the live connection and keeps the stored login, so you can restore later. Add logout=true to also log out of Telegram remotely and delete the stored login; after that the account has to sign in again with a code or QR scan.

curl -X DELETE 'https://api.opendata-api.com/api/project/telegram-messaging/disconnect-session?key=YOUR_API_KEY&sessionId=YOUR_SESSION_ID&logout=false'

Troubleshooting

  • Stuck in WAITING_FOR_OTP. Check the Telegram app of the account, not SMS. The code is only accepted while the status is WAITING_FOR_OTP.
  • verify-otp is rejected. It is only valid while the status is WAITING_FOR_OTP, so the session is probably in another state; read get-session first.
  • twoFactorRequired=true after the code. This is expected for accounts with a cloud password; continue with submit-password.
  • Status FAILED, LOGGED_OUT or DISCONNECTED. Calling get-session reconnects an account that is saved but offline. If the stored login is gone, for example after disconnect-session with logout=true, create a new session and log in again.

Security and compliance

The login code, the cloud password and the sessionId are the account owner's secrets. Collect them only from the owner, never log them, keep the sessionId in a secret store and rotate your API key if it leaks. Operate only accounts you own or have explicit permission to use. For the next step after login, follow sending and reading Telegram messages in Python; for a session-free way to check whether a handle or number exists on Telegram, see the Telegram number checker; and for the equivalent QR and pairing-code flow on WhatsApp, read how to send WhatsApp messages via API.

FAQ

Does Telegram send the login code by SMS? In this flow the code is delivered to the account inside the Telegram app. Open Telegram on a device where the account is already signed in, read the code from the message and submit it with verify-otp while the session status is WAITING_FOR_OTP.

What if the account has two-step verification enabled? After the code (or the QR scan) the session shows twoFactorRequired=true. Send the cloud password with submit-password, then poll get-session until the status reads CONNECTED.

Can I log in without a phone code? Yes, in two ways. QR mode lets the owner scan a code from Telegram's Devices screen, and restore mode reconnects a previously logged-in account from its saved sessionId without any new code.

How long does a session last? A normal disconnect-session keeps the stored login so you can restore it later, while logout=true deletes it and the account then signs in again with a code or QR scan. After a restart you restore the session with create-session in restore mode, though the inbox and chat list records are cleared.

Why does verify-otp return immediately without a result? Code and password submissions are processed asynchronously. The immediate response only means the request was accepted; poll get-session until the status becomes CONNECTED, shows twoFactorRequired=true, or reports a failure.

Try it

Run the flow in the browser from the Telegram Messaging page, and read the create-session, verify-otp, submit-password and get-session references. Check pricing for per-request costs.