Free key
engineering

The channel whose username is an empty string

Ask for @durov and the username field comes back as "". Not null, not missing — the six real handles moved into an array, and only one of them belongs in a link.

16 Sept 2026

Ask the API for @durov and read the username field. It comes back as "".

Not null, not missing. An empty string, on the account of the person who founded Telegram. If your code does chat.username or chat.id and builds a link out of the result, you have just produced t.me/1006503122, which resolves to nothing for a human being.

Here is what is actually going on, and it is not a bug. Every response below is live output, captured on 16 September 2026.

A channel can hold more than one handle

Telegram lets a chat own several public handles. When it does, the primary username field goes empty and the handles move into a usernames array, each entry carrying its own flags.

curl 'https://telegram155.p.rapidapi.com/v1/usernames/durov' \
  --header 'x-rapidapi-key: YOUR_KEY' \
  --header 'x-rapidapi-host: telegram155.p.rapidapi.com'
{
  "chats": [
    {
      "id": 1006503122,
      "username": "",
      "usernames": [
        { "username": "durov", "active": true, "editable": true,  "flags": 3 },
        { "username": "rove",  "active": true, "editable": false, "flags": 2 },
        { "username": "paul",  "active": true, "editable": false, "flags": 2 },
        { "username": "snow",  "active": true, "editable": false, "flags": 2 },
        { "username": "feed",  "active": true, "editable": false, "flags": 2 },
        { "username": "lean",  "active": true, "editable": false, "flags": 2 }
      ]
    }
  ]
}

Six handles. All six active. Exactly one also editable, and that one is durov.

The flags number is the same information in bitfield form: 2 is the active bit alone, 3 is active plus editable. You never need to decode it, the booleans are already there — but seeing 3 against 2 tells you which entry is canonical at a glance.

So the rule is not "take the first active handle". It is "take the active and editable one". That is the handle the owner controls and the one that belongs in a link. The other five are aliases: they work, but they are not the name.

The opposite case, from the same endpoint

curl 'https://telegram155.p.rapidapi.com/v1/usernames/telegram' \
  --header 'x-rapidapi-key: YOUR_KEY' \
  --header 'x-rapidapi-host: telegram155.p.rapidapi.com'
{ "chats": [ { "id": 1005640892, "username": "telegram", "usernames": null } ] }

One handle, sitting in username, and usernames is null. Both shapes are normal and both arrive from the same route, so code that handles only one of them is wrong half the time.

Resolving it once, properly

def handle(chat):
    """The handle a link should use: active and editable, then any active, then the plain field."""
    names = chat.get("usernames") or []

    for u in names:
        if u.get("active") and u.get("editable"):
            return u["username"]

    for u in names:
        if u.get("active"):
            return u["username"]

    return chat.get("username") or None

Three things worth noticing in nine lines. or [] on the array, because usernames is null rather than empty for single-handle channels. The two loops in that order, because "editable" is the distinguishing bit and "active" alone is not. And None at the end rather than the numeric id, because a link built from an id is worse than no link: it looks valid, it renders, and it goes nowhere.

If you do want a fallback that always produces something, use the id for your own bookkeeping and keep it out of anything a person will click.

Where this bites in practice

Anywhere you turn stored data back into a link: a digest email listing channels, a dashboard row, a CSV export someone opens next month. The case that produces it is a channel that renamed itself and kept the old handles working — exactly when username empties out and nobody notices until the links ship.

The check is cheap: for every chat you store, assert that the handle you saved is non-empty and is not the id in string form. One line in a test, and a class of dead links never reaches anyone.

Resolving a handle is a single request, and the response carries whichever shape that channel has. The free plan is 2,500 calls a month with no card, so mapping a few hundred channels and getting this right costs nothing. Full endpoint reference: 19 routes, one API key, no phone number.

Run the same measurements yourself

Free tier, no card. Every figure above came from one endpoint on a weekly schedule.

Get a free API key
Reading

More notes