Fix Telegram's “Bad Request: can't parse entities” error

You call sendMessage with parse_mode: "MarkdownV2" and Telegram answers:

400 Bad Request: can't parse entities: Character '.' is reserved
and must be escaped with the preceding '\'

The message text contains a character that MarkdownV2 treats as syntax. One unescaped dot is enough — Telegram rejects the whole message. Here is every cause, most common first.

Cause 1: unescaped reserved characters

In MarkdownV2, these 18 characters are reserved and must be escaped with a backslash anywhere they appear as plain text:

_ * [ ] ( ) ~ ` > # + - = | { } . !

That includes characters inside ordinary sentences: the dot at the end of this one, the hyphen in “e-mail”, the exclamation mark in “Hi!”. A minimal escape function:

JavaScript

const escapeMdV2 = (text) =>
  text.replace(/[_*[\]()~`>#+\-=|{}.!]/g, "\\$&");

Python

import re

def escape_md_v2(text: str) -> str:
    return re.sub(r"([_*\[\]()~`>#+\-=|{}.!])", r"\\\1", text)

Note these functions escape everything, including characters you meant as formatting. Escape only the plain-text parts, or use a real converter (see below).

Cause 2: parse_mode mismatch

Legacy Markdown and MarkdownV2 have different rules. Text written for one fails under the other — for example __underline__ is MarkdownV2-only, and legacy Markdown does not require escaping dots. Check which mode your call actually sends; many libraries default to legacy Markdown.

Cause 3: unclosed entities

Every *bold*, _italic_ or backtick pair must close. A stray * from an LLM response or user input opens an entity that never ends:

can't parse entities: Can't find end of the entity starting at byte offset 42

This variant of the error is common in bots that forward AI-generated Markdown straight to Telegram.

Cause 4: special contexts have their own rules

  • Inside pre and code entities, escape only ` and \.
  • Inside link and custom-emoji URLs like [text](https://example.com), escape only ) and \.

The reliable fix: convert, don't hand-escape

Hand-escaping breaks the moment your text mixes real formatting with plain text. Two options that handle it correctly:

Alternatively, switch to parse_mode: "HTML" — HTML mode only requires escaping <, > and &, which is easier to get right.

Related guides

Skip the manual escaping

Paste your Markdown into the free md2tg converter and get valid Telegram MarkdownV2 out — every reserved character escaped for you.