Telegram error 400: Bad Request: can't parse entities
Last verified against Telegram Bot API reference
Bad Request: can't parse entities: <detail varies>What error 400 means
You set parse_mode (MarkdownV2, Markdown or HTML) and the text does not parse under those rules. The description always begins can't parse entities: and then continues with a parser-specific detail that varies by input, for example Can't find end of the entity starting at byte offset 42, Unsupported start tag "br" at byte offset 10, or Character '.' is reserved and must be escaped with the preceding '\'. Treat the prefix as the stable part and the remainder as diagnostic.
Nothing was sent. Telegram rejects the whole message rather than sending it unformatted. The byte offset refers to the UTF-8 byte position in the text (or caption) field you submitted, so multi-byte characters before the failure shift the number.
The MarkdownV2 escaping rules are documented exactly, and they are stricter than most developers expect. Outside of entities, all of these characters must be escaped with a preceding \: _ * [ ] ( ) ~ ` > # + - = | { } . ! — eighteen characters, including the period and the exclamation mark. Inside pre and code entities, only ` and \ must be escaped. Inside the (...) part of an inline link or custom emoji definition, only ) and \ must be escaped. Any character with code 1-126 may be escaped anywhere, so over-escaping ASCII is always safe. HTML mode instead requires &, < and > to be replaced with entities and accepts only the documented tag set (no <br>, <p> or <div>).
Libraries do not pre-validate for you: python-telegram-bot raises telegram.error.BadRequest and aiogram v3 raises TelegramBadRequest with the description; both ship escape helpers (telegram.helpers.escape_markdown(version=2), aiogram.utils formatting tools) that solve it at the source. This error is deterministic: the same input always fails, which is why it looks intermittent in production — it depends on what the user typed or what your database returned.
What it looks like
{"ok":false,"error_code":400,"description":"Bad Request: can't parse entities: Can't find end of the entity starting at byte offset 42"}Why it happens
- Unescaped user content inserted into a MarkdownV2 template (names with underscores, prices with dots, URLs with hyphens).
- HTML mode with unsupported tags (
<br>,<p>,<div>) or a bare&/<in prose. - Legacy
Markdownmode with nested or unbalanced*/_. - A truncated message that cut the text in the middle of an entity.
- LLM output containing Markdown that almost, but not exactly, matches MarkdownV2 rules.
How to fix Telegram error 400
- 1Log the full response body; the byte offset points at the exact failure.
- 2Escape every interpolated value with an escaper that matches the parse mode: for MarkdownV2 the full reserved set is
_ * [ ] ( ) ~ ` > # + - = | { } . !; for HTML replace&with&,<with<,>with>. - 3Remember the two context exceptions: inside
pre/codeescape only`and\; inside link URLs escape only)and\. - 4Prefer HTML mode or explicit
entitiesarrays over MarkdownV2 for dynamic content; HTML has three characters to escape instead of eighteen. - 5When truncating, cut on a paragraph or entity boundary, then re-validate.
How to stop it recurring
Never concatenate raw strings into a formatted template. Use a builder that escapes per parse mode, or send plain text with an entities array so there is nothing to parse. Add a unit test that sends representative user-like strings (underscores, dots, angle brackets, emoji) through your formatter and asserts a round-trip with the API's rules. Character budgets for the text being formatted are under text and caption limits; the production failure pattern is cause 7 in Telegram bot not responding.
Official reference: Telegram Bot API reference. See all Telegram error codes or the Telegram limits and quotas.
Related codes
Error 400 - quick answers
What does Telegram error 400 mean?
You set parse_mode ( MarkdownV2 , Markdown or HTML ) and the text does not parse under those rules. The description always begins can't parse entities: and then continues with a parser-specific detail that varies by input, for example Can't find end of the entity starting at byte offset 42 , Unsupported start tag "br" at byte offset 10 , or Character '.' is reserved and must be escaped with the preceding '\' .
How do I fix Telegram error 400?
1. Log the full response body; the byte offset points at the exact failure. 2. Escape every interpolated value with an escaper that matches the parse mode: for MarkdownV2 the full reserved set is _ * [ ] ( ) ~ ` > # + - = | { } . ! ; for HTML replace & with & , < with < , > with > . 3. Remember the two context exceptions: inside pre / code escape only ` and \ ; inside link URLs escape only ) and \ . 4. Prefer HTML mode or explicit entities arrays over MarkdownV2 for…
Should I retry after error 400?
No. Retrying the same request produces the same error; the condition has to be fixed first. Treat it as a permanent failure for that message and surface it, rather than looping.
Stop debugging Telegram by hand
Connect the channel through Conferbot: tokens, webhooks and retries are handled, failures show as readable status.