diff --git a/src/errors.rs b/src/errors.rs
index 999692be..da2666b2 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -8,9 +8,11 @@ use thiserror::Error;
 /// An error caused by downloading a file.
 #[derive(Debug, Error, From)]
 pub enum DownloadError {
+    /// Network error while downloading file from telegram.
     #[error("A network error: {0}")]
     NetworkError(#[source] reqwest::Error),
 
+    /// I/O error while writing file to destination.
     #[error("An I/O error: {0}")]
     Io(#[source] std::io::Error),
 }
@@ -18,8 +20,14 @@ pub enum DownloadError {
 /// An error caused by sending a request to Telegram.
 #[derive(Debug, Error)]
 pub enum RequestError {
+    /// Telegram API error
     #[error("A Telegram's error #{status_code}: {kind:?}")]
-    ApiError { status_code: StatusCode, kind: ApiErrorKind },
+    ApiError {
+        /// Kind of api error
+        kind: ApiError,
+        /// HTTP code returned by telegram, not very usefull in practice.
+        status_code: StatusCode,
+    },
 
     /// The group has been migrated to a supergroup with the specified
     /// identifier.
@@ -31,9 +39,16 @@ pub enum RequestError {
     #[error("Retry after {0} seconds")]
     RetryAfter(i32),
 
+    /// Network error while sending request to telegram.
     #[error("A network error: {0}")]
     NetworkError(#[source] reqwest::Error),
 
+    /// Error while parsing response from telegram.
+    ///
+    /// If you've received this error, please, [open an issue] with the
+    /// description of the error.
+    ///
+    /// [open an issue]: https://github.com/teloxide/teloxide/issues/new
     #[error("An error while parsing JSON: {0}")]
     InvalidJson(#[source] serde_json::Error),
 
@@ -42,480 +57,480 @@ pub enum RequestError {
     Io(#[source] io::Error),
 }
 
-/// A kind of an API error.
-///
-/// If you receive [`ApiErrorKind::Unknown`], please [open an issue] with
-/// the description of the error.
-///
-/// [`ApiErrorKind::Unknown`]: crate::ApiErrorKind::Unknown
-/// [open an issue]: https://github.com/teloxide/teloxide/issues/new
-#[derive(Debug, Deserialize, PartialEq, Hash, Eq, Clone)]
-#[serde(untagged)]
-pub enum ApiErrorKind {
-    Known(KnownApiErrorKind),
-    Unknown(String),
-}
-
-/// A kind of a known API error.
-#[derive(Debug, Deserialize, PartialEq, Copy, Hash, Eq, Clone)]
-pub enum KnownApiErrorKind {
-    /// Occurs when the bot tries to send message to user who blocked the bot.
-    #[serde(rename = "Forbidden: bot was blocked by the user")]
-    BotBlocked,
-
-    /// Occurs when bot tries to modify a message without modification content.
-    ///
-    /// May happen in methods:
-    /// 1. [`EditMessageText`]
-    ///
-    /// [`EditMessageText`]: crate::requests::EditMessageText
-    #[serde(rename = "Bad Request: message is not modified: specified new message content and \
-                      reply markup are exactly the same as a current content and reply markup \
-                      of the message")]
-    MessageNotModified,
-
-    /// Occurs when bot tries to forward or delete a message which was deleted.
-    ///
-    /// May happen in methods:
-    /// 1. [`ForwardMessage`]
-    /// 2. [`DeleteMessage`]
-    ///
-    /// [`ForwardMessage`]: crate::requests::ForwardMessage
-    /// [`DeleteMessage`]: crate::requests::DeleteMessage
-    #[serde(rename = "Bad Request: MESSAGE_ID_INVALID")]
-    MessageIdInvalid,
-
-    /// Occurs when bot tries to forward a message which does not exists.
-    ///
-    /// May happen in methods:
-    /// 1. [`ForwardMessage`]
-    ///
-    /// [`ForwardMessage`]: crate::requests::ForwardMessage
-    #[serde(rename = "Bad Request: message to forward not found")]
-    MessageToForwardNotFound,
-
-    /// Occurs when bot tries to delete a message which does not exists.
-    ///
-    /// May happen in methods:
-    /// 1. [`DeleteMessage`]
-    ///
-    /// [`DeleteMessage`]: crate::requests::DeleteMessage
-    #[serde(rename = "Bad Request: message to delete not found")]
-    MessageToDeleteNotFound,
-
-    /// Occurs when bot tries to send a text message without text.
-    ///
-    /// May happen in methods:
-    /// 1. [`SendMessage`]
-    ///
-    /// [`SendMessage`]: crate::requests::SendMessage
-    #[serde(rename = "Bad Request: message text is empty")]
-    MessageTextIsEmpty,
-
-    /// Occurs when bot tries to edit a message after long time.
-    ///
-    /// May happen in methods:
-    /// 1. [`EditMessageText`]
-    ///
-    /// [`EditMessageText`]: crate::requests::EditMessageText
-    #[serde(rename = "Bad Request: message can't be edited")]
-    MessageCantBeEdited,
-
-    /// Occurs when bot tries to delete a someone else's message in group where
-    /// it does not have enough rights.
-    ///
-    /// May happen in methods:
-    /// 1. [`DeleteMessage`]
-    ///
-    /// [`DeleteMessage`]: crate::requests::DeleteMessage
-    #[serde(rename = "Bad Request: message can't be deleted")]
-    MessageCantBeDeleted,
-
-    /// Occurs when bot tries to edit a message which does not exists.
-    ///
-    /// May happen in methods:
-    /// 1. [`EditMessageText`]
-    ///
-    /// [`EditMessageText`]: crate::requests::EditMessageText
-    #[serde(rename = "Bad Request: message to edit not found")]
-    MessageToEditNotFound,
-
-    /// Occurs when bot tries to reply to a message which does not exists.
-    ///
-    /// May happen in methods:
-    /// 1. [`SendMessage`]
-    ///
-    /// [`SendMessage`]: crate::requests::SendMessage
-    #[serde(rename = "Bad Request: reply message not found")]
-    MessageToReplyNotFound,
-
-    /// Occurs when bot tries to
-    #[serde(rename = "Bad Request: message identifier is not specified")]
-    MessageIdentifierNotSpecified,
-
-    /// Occurs when bot tries to send a message with text size greater then
-    /// 4096 symbols.
-    ///
-    /// May happen in methods:
-    /// 1. [`SendMessage`]
-    ///
-    /// [`SendMessage`]: crate::requests::SendMessage
-    #[serde(rename = "Bad Request: message is too long")]
-    MessageIsTooLong,
-
-    /// Occurs when bot tries to send media group with more than 10 items.
-    ///
-    /// May happen in methods:
-    /// 1. [`SendMediaGroup`]
-    ///
-    /// [`SendMediaGroup`]: crate::requests::SendMediaGroup
-    #[serde(rename = "Bad Request: Too much messages to send as an album")]
-    ToMuchMessages,
-
-    /// Occurs when bot tries to stop poll that has already been stopped.
-    ///
-    /// May happen in methods:
-    /// 1. [`SendPoll`]
-    ///
-    /// [`SendPoll`]: crate::requests::SendPoll
-    #[serde(rename = "Bad Request: poll has already been closed")]
-    PollHasAlreadyClosed,
-
-    /// Occurs when bot tries to send poll with less than 2 options.
-    ///
-    /// May happen in methods:
-    /// 1. [`SendPoll`]
-    ///
-    /// [`SendPoll`]: crate::requests::SendPoll
-    #[serde(rename = "Bad Request: poll must have at least 2 option")]
-    PollMustHaveMoreOptions,
-
-    /// Occurs when bot tries to send poll with more than 10 options.
-    ///
-    /// May happen in methods:
-    /// 1. [`SendPoll`]
-    ///
-    /// [`SendPoll`]: crate::requests::SendPoll
-    #[serde(rename = "Bad Request: poll can't have more than 10 options")]
-    PollCantHaveMoreOptions,
-
-    /// Occurs when bot tries to send poll with empty option (without text).
-    ///
-    /// May happen in methods:
-    /// 1. [`SendPoll`]
-    ///
-    /// [`SendPoll`]: crate::requests::SendPoll
-    #[serde(rename = "Bad Request: poll options must be non-empty")]
-    PollOptionsMustBeNonEmpty,
-
-    /// Occurs when bot tries to send poll with empty question (without text).
-    ///
-    /// May happen in methods:
-    /// 1. [`SendPoll`]
-    ///
-    /// [`SendPoll`]: crate::requests::SendPoll
-    #[serde(rename = "Bad Request: poll question must be non-empty")]
-    PollQuestionMustBeNonEmpty,
-
-    /// Occurs when bot tries to send poll with total size of options more than
-    /// 100 symbols.
-    ///
-    /// May happen in methods:
-    /// 1. [`SendPoll`]
-    ///
-    /// [`SendPoll`]: crate::requests::SendPoll
-    #[serde(rename = "Bad Request: poll options length must not exceed 100")]
-    PollOptionsLengthTooLong,
-
-    /// Occurs when bot tries to send poll with question size more than 255
-    /// symbols.
-    ///
-    /// May happen in methods:
-    /// 1. [`SendPoll`]
-    ///
-    /// [`SendPoll`]: crate::requests::SendPoll
-    #[serde(rename = "Bad Request: poll question length must not exceed 255")]
-    PollQuestionLengthTooLong,
-
-    /// Occurs when bot tries to stop poll with message without poll.
-    ///
-    /// May happen in methods:
-    /// 1. [`StopPoll`]
-    ///
-    /// [`StopPoll`]: crate::requests::StopPoll
-    #[serde(rename = "Bad Request: message with poll to stop not found")]
-    MessageWithPollNotFound,
-
-    /// Occurs when bot tries to stop poll with message without poll.
-    ///
-    /// May happen in methods:
-    /// 1. [`StopPoll`]
-    ///
-    /// [`StopPoll`]: crate::requests::StopPoll
-    #[serde(rename = "Bad Request: message is not a poll")]
-    MessageIsNotAPoll,
-
-    /// Occurs when bot tries to send a message to chat in which it is not a
-    /// member.
-    ///
-    /// May happen in methods:
-    /// 1. [`SendMessage`]
-    ///
-    /// [`SendMessage`]: crate::requests::SendMessage
-    #[serde(rename = "Bad Request: chat not found")]
-    ChatNotFound,
-
-    /// Occurs when bot tries to send method with unknown user_id.
-    ///
-    /// May happen in methods:
-    /// 1. [`getUserProfilePhotos`]
-    ///
-    /// [`getUserProfilePhotos`]:
-    /// crate::requests::GetUserProfilePhotos
-    #[serde(rename = "Bad Request: user not found")]
-    UserNotFound,
-
-    /// Occurs when bot tries to send [`SetChatDescription`] with same text as
-    /// in the current description.
-    ///
-    /// May happen in methods:
-    /// 1. [`SetChatDescription`]
-    ///
-    /// [`SetChatDescription`]: crate::requests::SetChatDescription
-    #[serde(rename = "Bad Request: chat description is not modified")]
-    ChatDescriptionIsNotModified,
-
-    /// Occurs when bot tries to answer to query after timeout expire.
-    ///
-    /// May happen in methods:
-    /// 1. [`AnswerCallbackQuery`]
-    ///
-    /// [`AnswerCallbackQuery`]: crate::requests::AnswerCallbackQuery
-    #[serde(rename = "Bad Request: query is too old and response timeout expired or query id is \
-                      invalid")]
-    InvalidQueryID,
-
-    /// Occurs when bot tries to send InlineKeyboardMarkup with invalid button
-    /// url.
-    ///
-    /// May happen in methods:
-    /// 1. [`SendMessage`]
-    ///
-    /// [`SendMessage`]: crate::requests::SendMessage
-    #[serde(rename = "Bad Request: BUTTON_URL_INVALID")]
-    ButtonURLInvalid,
-
-    /// Occurs when bot tries to send button with data size more than 64 bytes.
-    ///
-    /// May happen in methods:
-    /// 1. [`SendMessage`]
-    ///
-    /// [`SendMessage`]: crate::requests::SendMessage
-    #[serde(rename = "Bad Request: BUTTON_DATA_INVALID")]
-    ButtonDataInvalid,
-
-    /// Occurs when bot tries to send button with data size == 0.
-    ///
-    /// May happen in methods:
-    /// 1. [`SendMessage`]
-    ///
-    /// [`SendMessage`]: crate::requests::SendMessage
-    #[serde(rename = "Bad Request: can't parse inline keyboard button: Text buttons are \
-                      unallowed in the inline keyboard")]
-    TextButtonsAreUnallowed,
-
-    /// Occurs when bot tries to get file by wrong file id.
-    ///
-    /// May happen in methods:
-    /// 1. [`GetFile`]
-    ///
-    /// [`GetFile`]: crate::requests::GetFile
-    #[serde(rename = "Bad Request: wrong file id")]
-    WrongFileID,
-
-    /// Occurs when bot tries to do some with group which was deactivated.
-    #[serde(rename = "Bad Request: group is deactivated")]
-    GroupDeactivated,
-
-    /// Occurs when bot tries to set chat photo from file ID
-    ///
-    /// May happen in methods:
-    /// 1. [`SetChatPhoto`]
-    ///
-    /// [`SetChatPhoto`]: crate::requests::SetChatPhoto
-    #[serde(rename = "Bad Request: Photo should be uploaded as an InputFile")]
-    PhotoAsInputFileRequired,
-
-    /// Occurs when bot tries to add sticker to stickerset by invalid name.
-    ///
-    /// May happen in methods:
-    /// 1. [`AddStickerToSet`]
-    ///
-    /// [`AddStickerToSet`]: crate::requests::AddStickerToSet
-    #[serde(rename = "Bad Request: STICKERSET_INVALID")]
-    InvalidStickersSet,
-
-    /// Occurs when bot tries to pin a message without rights to pin in this
-    /// chat.
-    ///
-    /// May happen in methods:
-    /// 1. [`PinChatMessage`]
-    ///
-    /// [`PinChatMessage`]: crate::requests::PinChatMessage
-    #[serde(rename = "Bad Request: not enough rights to pin a message")]
-    NotEnoughRightsToPinMessage,
-
-    /// Occurs when bot tries to use method in group which is allowed only in a
-    /// supergroup or channel.
-    #[serde(rename = "Bad Request: method is available only for supergroups and channel")]
-    MethodNotAvailableInPrivateChats,
-
-    /// Occurs when bot tries to demote chat creator.
-    ///
-    /// May happen in methods:
-    /// 1. [`PromoteChatMember`]
-    ///
-    /// [`PromoteChatMember`]: crate::requests::PromoteChatMember
-    #[serde(rename = "Bad Request: can't demote chat creator")]
-    CantDemoteChatCreator,
-
-    /// Occurs when bot tries to restrict self in group chats.
-    ///
-    /// May happen in methods:
-    /// 1. [`RestrictChatMember`]
-    ///
-    /// [`RestrictChatMember`]: crate::requests::RestrictChatMember
-    #[serde(rename = "Bad Request: can't restrict self")]
-    CantRestrictSelf,
-
-    /// Occurs when bot tries to restrict chat member without rights to
-    /// restrict in this chat.
-    ///
-    /// May happen in methods:
-    /// 1. [`RestrictChatMember`]
-    ///
-    /// [`RestrictChatMember`]: crate::requests::RestrictChatMember
-    #[serde(rename = "Bad Request: not enough rights to restrict/unrestrict chat member")]
-    NotEnoughRightsToRestrict,
-
-    /// Occurs when bot tries set webhook to protocol other than HTTPS.
-    ///
-    /// May happen in methods:
-    /// 1. [`SetWebhook`]
-    ///
-    /// [`SetWebhook`]: crate::requests::SetWebhook
-    #[serde(rename = "Bad Request: bad webhook: HTTPS url must be provided for webhook")]
-    WebhookRequireHTTPS,
-
-    /// Occurs when bot tries to set webhook to port other than 80, 88, 443 or
-    /// 8443.
-    ///
-    /// May happen in methods:
-    /// 1. [`SetWebhook`]
-    ///
-    /// [`SetWebhook`]: crate::requests::SetWebhook
-    #[serde(rename = "Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 \
-                      or 8443")]
-    BadWebhookPort,
-
-    /// Occurs when bot tries to set webhook to unknown host.
-    ///
-    /// May happen in methods:
-    /// 1. [`SetWebhook`]
-    ///
-    /// [`SetWebhook`]: crate::requests::SetWebhook
-    #[serde(rename = "Bad Request: bad webhook: Failed to resolve host: Name or service not known")]
-    UnknownHost,
-
-    /// Occurs when bot tries to set webhook to invalid URL.
-    ///
-    /// May happen in methods:
-    /// 1. [`SetWebhook`]
-    ///
-    /// [`SetWebhook`]: crate::requests::SetWebhook
-    #[serde(rename = "Bad Request: can't parse URL")]
-    CantParseUrl,
-
-    /// Occurs when bot tries to send message with unfinished entities.
-    ///
-    /// May happen in methods:
-    /// 1. [`SendMessage`]
-    ///
-    /// [`SendMessage`]: crate::requests::SendMessage
-    #[serde(rename = "Bad Request: can't parse entities")]
-    CantParseEntities,
-
-    /// Occurs when bot tries to use getUpdates while webhook is active.
-    ///
-    /// May happen in methods:
-    /// 1. [`GetUpdates`]
-    ///
-    /// [`GetUpdates`]: crate::requests::GetUpdates
-    #[serde(rename = "can't use getUpdates method while webhook is active")]
-    CantGetUpdates,
-
-    /// Occurs when bot tries to do some in group where bot was kicked.
-    ///
-    /// May happen in methods:
-    /// 1. [`SendMessage`]
-    ///
-    /// [`SendMessage`]: crate::requests::SendMessage
-    #[serde(rename = "Unauthorized: bot was kicked from a chat")]
-    BotKicked,
-
-    /// Occurs when bot tries to send message to deactivated user.
-    ///
-    /// May happen in methods:
-    /// 1. [`SendMessage`]
-    ///
-    /// [`SendMessage`]: crate::requests::SendMessage
-    #[serde(rename = "Unauthorized: user is deactivated")]
-    UserDeactivated,
-
-    /// Occurs when you tries to initiate conversation with a user.
-    ///
-    /// May happen in methods:
-    /// 1. [`SendMessage`]
-    ///
-    /// [`SendMessage`]: crate::requests::SendMessage
-    #[serde(rename = "Unauthorized: bot can't initiate conversation with a user")]
-    CantInitiateConversation,
-
-    /// Occurs when you tries to send message to bot.
-    ///
-    /// May happen in methods:
-    /// 1. [`SendMessage`]
-    ///
-    /// [`SendMessage`]: crate::requests::SendMessage
-    #[serde(rename = "Unauthorized: bot can't send messages to bots")]
-    CantTalkWithBots,
-
-    /// Occurs when bot tries to send button with invalid http url.
-    ///
-    /// May happen in methods:
-    /// 1. [`SendMessage`]
-    ///
-    /// [`SendMessage`]: crate::requests::SendMessage
-    #[serde(rename = "Bad Request: wrong HTTP URL")]
-    WrongHTTPurl,
-
-    /// Occurs when bot tries GetUpdate before the timeout. Make sure that only
-    /// one Updater is running.
-    ///
-    /// May happen in methods:
-    /// 1. [`GetUpdates`]
-    ///
-    /// [`GetUpdates`]: crate::requests::GetUpdates
-    #[serde(rename = "Conflict: terminated by other getUpdates request; make sure that only one \
-                      bot instance is running")]
-    TerminatedByOtherGetUpdates,
-
-    /// Occurs when bot tries to get file by invalid file id.
-    ///
-    /// May happen in methods:
-    /// 1. [`GetFile`]
-    ///
-    /// [`GetFile`]: crate::requests::GetFile
-    #[serde(rename = "Bad Request: invalid file id")]
-    FileIdInvalid,
+serde_or_unknown! {
+    #[unknown_mod = api_error_internals]
+    #[unknown_kind = Kind]
+    #[unknown_path = "api_error_internals::Kind"]
+
+    /// A kind of an API error.
+    #[derive(Debug, Deserialize, PartialEq, Hash, Eq, Clone)]
+    pub enum ApiError {
+        #[unknown]
+        /// Error which is not known to `teloxide`.
+        ///
+        /// If you've received this error, please [open an issue] with the description of the error.
+        ///
+        /// [open an issue]: https://github.com/teloxide/teloxide/issues/new
+        Unknown(String),
+
+        /// Occurs when the bot tries to send message to user who blocked the bot.
+        #[serde(rename = "Forbidden: bot was blocked by the user")]
+        BotBlocked,
+
+        /// Occurs when bot tries to modify a message without modification content.
+        ///
+        /// May happen in methods:
+        /// 1. [`EditMessageText`]
+        ///
+        /// [`EditMessageText`]: crate::requests::EditMessageText
+        #[serde(rename = "Bad Request: message is not modified: specified new message content and \
+                        reply markup are exactly the same as a current content and reply markup \
+                        of the message")]
+        MessageNotModified,
+
+        /// Occurs when bot tries to forward or delete a message which was deleted.
+        ///
+        /// May happen in methods:
+        /// 1. [`ForwardMessage`]
+        /// 2. [`DeleteMessage`]
+        ///
+        /// [`ForwardMessage`]: crate::requests::ForwardMessage
+        /// [`DeleteMessage`]: crate::requests::DeleteMessage
+        #[serde(rename = "Bad Request: MESSAGE_ID_INVALID")]
+        MessageIdInvalid,
+
+        /// Occurs when bot tries to forward a message which does not exists.
+        ///
+        /// May happen in methods:
+        /// 1. [`ForwardMessage`]
+        ///
+        /// [`ForwardMessage`]: crate::requests::ForwardMessage
+        #[serde(rename = "Bad Request: message to forward not found")]
+        MessageToForwardNotFound,
+
+        /// Occurs when bot tries to delete a message which does not exists.
+        ///
+        /// May happen in methods:
+        /// 1. [`DeleteMessage`]
+        ///
+        /// [`DeleteMessage`]: crate::requests::DeleteMessage
+        #[serde(rename = "Bad Request: message to delete not found")]
+        MessageToDeleteNotFound,
+
+        /// Occurs when bot tries to send a text message without text.
+        ///
+        /// May happen in methods:
+        /// 1. [`SendMessage`]
+        ///
+        /// [`SendMessage`]: crate::requests::SendMessage
+        #[serde(rename = "Bad Request: message text is empty")]
+        MessageTextIsEmpty,
+
+        /// Occurs when bot tries to edit a message after long time.
+        ///
+        /// May happen in methods:
+        /// 1. [`EditMessageText`]
+        ///
+        /// [`EditMessageText`]: crate::requests::EditMessageText
+        #[serde(rename = "Bad Request: message can't be edited")]
+        MessageCantBeEdited,
+
+        /// Occurs when bot tries to delete a someone else's message in group where
+        /// it does not have enough rights.
+        ///
+        /// May happen in methods:
+        /// 1. [`DeleteMessage`]
+        ///
+        /// [`DeleteMessage`]: crate::requests::DeleteMessage
+        #[serde(rename = "Bad Request: message can't be deleted")]
+        MessageCantBeDeleted,
+
+        /// Occurs when bot tries to edit a message which does not exists.
+        ///
+        /// May happen in methods:
+        /// 1. [`EditMessageText`]
+        ///
+        /// [`EditMessageText`]: crate::requests::EditMessageText
+        #[serde(rename = "Bad Request: message to edit not found")]
+        MessageToEditNotFound,
+
+        /// Occurs when bot tries to reply to a message which does not exists.
+        ///
+        /// May happen in methods:
+        /// 1. [`SendMessage`]
+        ///
+        /// [`SendMessage`]: crate::requests::SendMessage
+        #[serde(rename = "Bad Request: reply message not found")]
+        MessageToReplyNotFound,
+
+        /// Occurs when bot tries to
+        #[serde(rename = "Bad Request: message identifier is not specified")]
+        MessageIdentifierNotSpecified,
+
+        /// Occurs when bot tries to send a message with text size greater then
+        /// 4096 symbols.
+        ///
+        /// May happen in methods:
+        /// 1. [`SendMessage`]
+        ///
+        /// [`SendMessage`]: crate::requests::SendMessage
+        #[serde(rename = "Bad Request: message is too long")]
+        MessageIsTooLong,
+
+        /// Occurs when bot tries to send media group with more than 10 items.
+        ///
+        /// May happen in methods:
+        /// 1. [`SendMediaGroup`]
+        ///
+        /// [`SendMediaGroup`]: crate::requests::SendMediaGroup
+        #[serde(rename = "Bad Request: Too much messages to send as an album")]
+        ToMuchMessages,
+
+        /// Occurs when bot tries to stop poll that has already been stopped.
+        ///
+        /// May happen in methods:
+        /// 1. [`SendPoll`]
+        ///
+        /// [`SendPoll`]: crate::requests::SendPoll
+        #[serde(rename = "Bad Request: poll has already been closed")]
+        PollHasAlreadyClosed,
+
+        /// Occurs when bot tries to send poll with less than 2 options.
+        ///
+        /// May happen in methods:
+        /// 1. [`SendPoll`]
+        ///
+        /// [`SendPoll`]: crate::requests::SendPoll
+        #[serde(rename = "Bad Request: poll must have at least 2 option")]
+        PollMustHaveMoreOptions,
+
+        /// Occurs when bot tries to send poll with more than 10 options.
+        ///
+        /// May happen in methods:
+        /// 1. [`SendPoll`]
+        ///
+        /// [`SendPoll`]: crate::requests::SendPoll
+        #[serde(rename = "Bad Request: poll can't have more than 10 options")]
+        PollCantHaveMoreOptions,
+
+        /// Occurs when bot tries to send poll with empty option (without text).
+        ///
+        /// May happen in methods:
+        /// 1. [`SendPoll`]
+        ///
+        /// [`SendPoll`]: crate::requests::SendPoll
+        #[serde(rename = "Bad Request: poll options must be non-empty")]
+        PollOptionsMustBeNonEmpty,
+
+        /// Occurs when bot tries to send poll with empty question (without text).
+        ///
+        /// May happen in methods:
+        /// 1. [`SendPoll`]
+        ///
+        /// [`SendPoll`]: crate::requests::SendPoll
+        #[serde(rename = "Bad Request: poll question must be non-empty")]
+        PollQuestionMustBeNonEmpty,
+
+        /// Occurs when bot tries to send poll with total size of options more than
+        /// 100 symbols.
+        ///
+        /// May happen in methods:
+        /// 1. [`SendPoll`]
+        ///
+        /// [`SendPoll`]: crate::requests::SendPoll
+        #[serde(rename = "Bad Request: poll options length must not exceed 100")]
+        PollOptionsLengthTooLong,
+
+        /// Occurs when bot tries to send poll with question size more than 255
+        /// symbols.
+        ///
+        /// May happen in methods:
+        /// 1. [`SendPoll`]
+        ///
+        /// [`SendPoll`]: crate::requests::SendPoll
+        #[serde(rename = "Bad Request: poll question length must not exceed 255")]
+        PollQuestionLengthTooLong,
+
+        /// Occurs when bot tries to stop poll with message without poll.
+        ///
+        /// May happen in methods:
+        /// 1. [`StopPoll`]
+        ///
+        /// [`StopPoll`]: crate::requests::StopPoll
+        #[serde(rename = "Bad Request: message with poll to stop not found")]
+        MessageWithPollNotFound,
+
+        /// Occurs when bot tries to stop poll with message without poll.
+        ///
+        /// May happen in methods:
+        /// 1. [`StopPoll`]
+        ///
+        /// [`StopPoll`]: crate::requests::StopPoll
+        #[serde(rename = "Bad Request: message is not a poll")]
+        MessageIsNotAPoll,
+
+        /// Occurs when bot tries to send a message to chat in which it is not a
+        /// member.
+        ///
+        /// May happen in methods:
+        /// 1. [`SendMessage`]
+        ///
+        /// [`SendMessage`]: crate::requests::SendMessage
+        #[serde(rename = "Bad Request: chat not found")]
+        ChatNotFound,
+
+        /// Occurs when bot tries to send method with unknown user_id.
+        ///
+        /// May happen in methods:
+        /// 1. [`getUserProfilePhotos`]
+        ///
+        /// [`getUserProfilePhotos`]:
+        /// crate::requests::GetUserProfilePhotos
+        #[serde(rename = "Bad Request: user not found")]
+        UserNotFound,
+
+        /// Occurs when bot tries to send [`SetChatDescription`] with same text as
+        /// in the current description.
+        ///
+        /// May happen in methods:
+        /// 1. [`SetChatDescription`]
+        ///
+        /// [`SetChatDescription`]: crate::requests::SetChatDescription
+        #[serde(rename = "Bad Request: chat description is not modified")]
+        ChatDescriptionIsNotModified,
+
+        /// Occurs when bot tries to answer to query after timeout expire.
+        ///
+        /// May happen in methods:
+        /// 1. [`AnswerCallbackQuery`]
+        ///
+        /// [`AnswerCallbackQuery`]: crate::requests::AnswerCallbackQuery
+        #[serde(rename = "Bad Request: query is too old and response timeout expired or query id is \
+                        invalid")]
+        InvalidQueryID,
+
+        /// Occurs when bot tries to send InlineKeyboardMarkup with invalid button
+        /// url.
+        ///
+        /// May happen in methods:
+        /// 1. [`SendMessage`]
+        ///
+        /// [`SendMessage`]: crate::requests::SendMessage
+        #[serde(rename = "Bad Request: BUTTON_URL_INVALID")]
+        ButtonURLInvalid,
+
+        /// Occurs when bot tries to send button with data size more than 64 bytes.
+        ///
+        /// May happen in methods:
+        /// 1. [`SendMessage`]
+        ///
+        /// [`SendMessage`]: crate::requests::SendMessage
+        #[serde(rename = "Bad Request: BUTTON_DATA_INVALID")]
+        ButtonDataInvalid,
+
+        /// Occurs when bot tries to send button with data size == 0.
+        ///
+        /// May happen in methods:
+        /// 1. [`SendMessage`]
+        ///
+        /// [`SendMessage`]: crate::requests::SendMessage
+        #[serde(rename = "Bad Request: can't parse inline keyboard button: Text buttons are \
+                        unallowed in the inline keyboard")]
+        TextButtonsAreUnallowed,
+
+        /// Occurs when bot tries to get file by wrong file id.
+        ///
+        /// May happen in methods:
+        /// 1. [`GetFile`]
+        ///
+        /// [`GetFile`]: crate::requests::GetFile
+        #[serde(rename = "Bad Request: wrong file id")]
+        WrongFileID,
+
+        /// Occurs when bot tries to do some with group which was deactivated.
+        #[serde(rename = "Bad Request: group is deactivated")]
+        GroupDeactivated,
+
+        /// Occurs when bot tries to set chat photo from file ID
+        ///
+        /// May happen in methods:
+        /// 1. [`SetChatPhoto`]
+        ///
+        /// [`SetChatPhoto`]: crate::requests::SetChatPhoto
+        #[serde(rename = "Bad Request: Photo should be uploaded as an InputFile")]
+        PhotoAsInputFileRequired,
+
+        /// Occurs when bot tries to add sticker to stickerset by invalid name.
+        ///
+        /// May happen in methods:
+        /// 1. [`AddStickerToSet`]
+        ///
+        /// [`AddStickerToSet`]: crate::requests::AddStickerToSet
+        #[serde(rename = "Bad Request: STICKERSET_INVALID")]
+        InvalidStickersSet,
+
+        /// Occurs when bot tries to pin a message without rights to pin in this
+        /// chat.
+        ///
+        /// May happen in methods:
+        /// 1. [`PinChatMessage`]
+        ///
+        /// [`PinChatMessage`]: crate::requests::PinChatMessage
+        #[serde(rename = "Bad Request: not enough rights to pin a message")]
+        NotEnoughRightsToPinMessage,
+
+        /// Occurs when bot tries to use method in group which is allowed only in a
+        /// supergroup or channel.
+        #[serde(rename = "Bad Request: method is available only for supergroups and channel")]
+        MethodNotAvailableInPrivateChats,
+
+        /// Occurs when bot tries to demote chat creator.
+        ///
+        /// May happen in methods:
+        /// 1. [`PromoteChatMember`]
+        ///
+        /// [`PromoteChatMember`]: crate::requests::PromoteChatMember
+        #[serde(rename = "Bad Request: can't demote chat creator")]
+        CantDemoteChatCreator,
+
+        /// Occurs when bot tries to restrict self in group chats.
+        ///
+        /// May happen in methods:
+        /// 1. [`RestrictChatMember`]
+        ///
+        /// [`RestrictChatMember`]: crate::requests::RestrictChatMember
+        #[serde(rename = "Bad Request: can't restrict self")]
+        CantRestrictSelf,
+
+        /// Occurs when bot tries to restrict chat member without rights to
+        /// restrict in this chat.
+        ///
+        /// May happen in methods:
+        /// 1. [`RestrictChatMember`]
+        ///
+        /// [`RestrictChatMember`]: crate::requests::RestrictChatMember
+        #[serde(rename = "Bad Request: not enough rights to restrict/unrestrict chat member")]
+        NotEnoughRightsToRestrict,
+
+        /// Occurs when bot tries set webhook to protocol other than HTTPS.
+        ///
+        /// May happen in methods:
+        /// 1. [`SetWebhook`]
+        ///
+        /// [`SetWebhook`]: crate::requests::SetWebhook
+        #[serde(rename = "Bad Request: bad webhook: HTTPS url must be provided for webhook")]
+        WebhookRequireHTTPS,
+
+        /// Occurs when bot tries to set webhook to port other than 80, 88, 443 or
+        /// 8443.
+        ///
+        /// May happen in methods:
+        /// 1. [`SetWebhook`]
+        ///
+        /// [`SetWebhook`]: crate::requests::SetWebhook
+        #[serde(rename = "Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 \
+                        or 8443")]
+        BadWebhookPort,
+
+        /// Occurs when bot tries to set webhook to unknown host.
+        ///
+        /// May happen in methods:
+        /// 1. [`SetWebhook`]
+        ///
+        /// [`SetWebhook`]: crate::requests::SetWebhook
+        #[serde(rename = "Bad Request: bad webhook: Failed to resolve host: Name or service not known")]
+        UnknownHost,
+
+        /// Occurs when bot tries to set webhook to invalid URL.
+        ///
+        /// May happen in methods:
+        /// 1. [`SetWebhook`]
+        ///
+        /// [`SetWebhook`]: crate::requests::SetWebhook
+        #[serde(rename = "Bad Request: can't parse URL")]
+        CantParseUrl,
+
+        /// Occurs when bot tries to send message with unfinished entities.
+        ///
+        /// May happen in methods:
+        /// 1. [`SendMessage`]
+        ///
+        /// [`SendMessage`]: crate::requests::SendMessage
+        #[serde(rename = "Bad Request: can't parse entities")]
+        CantParseEntities,
+
+        /// Occurs when bot tries to use getUpdates while webhook is active.
+        ///
+        /// May happen in methods:
+        /// 1. [`GetUpdates`]
+        ///
+        /// [`GetUpdates`]: crate::requests::GetUpdates
+        #[serde(rename = "can't use getUpdates method while webhook is active")]
+        CantGetUpdates,
+
+        /// Occurs when bot tries to do some in group where bot was kicked.
+        ///
+        /// May happen in methods:
+        /// 1. [`SendMessage`]
+        ///
+        /// [`SendMessage`]: crate::requests::SendMessage
+        #[serde(rename = "Unauthorized: bot was kicked from a chat")]
+        BotKicked,
+
+        /// Occurs when bot tries to send message to deactivated user.
+        ///
+        /// May happen in methods:
+        /// 1. [`SendMessage`]
+        ///
+        /// [`SendMessage`]: crate::requests::SendMessage
+        #[serde(rename = "Unauthorized: user is deactivated")]
+        UserDeactivated,
+
+        /// Occurs when you tries to initiate conversation with a user.
+        ///
+        /// May happen in methods:
+        /// 1. [`SendMessage`]
+        ///
+        /// [`SendMessage`]: crate::requests::SendMessage
+        #[serde(rename = "Unauthorized: bot can't initiate conversation with a user")]
+        CantInitiateConversation,
+
+        /// Occurs when you tries to send message to bot.
+        ///
+        /// May happen in methods:
+        /// 1. [`SendMessage`]
+        ///
+        /// [`SendMessage`]: crate::requests::SendMessage
+        #[serde(rename = "Unauthorized: bot can't send messages to bots")]
+        CantTalkWithBots,
+
+        /// Occurs when bot tries to send button with invalid http url.
+        ///
+        /// May happen in methods:
+        /// 1. [`SendMessage`]
+        ///
+        /// [`SendMessage`]: crate::requests::SendMessage
+        #[serde(rename = "Bad Request: wrong HTTP URL")]
+        WrongHTTPurl,
+
+        /// Occurs when bot tries GetUpdate before the timeout. Make sure that only
+        /// one Updater is running.
+        ///
+        /// May happen in methods:
+        /// 1. [`GetUpdates`]
+        ///
+        /// [`GetUpdates`]: crate::requests::GetUpdates
+        #[serde(rename = "Conflict: terminated by other getUpdates request; make sure that only one \
+                        bot instance is running")]
+        TerminatedByOtherGetUpdates,
+
+        /// Occurs when bot tries to get file by invalid file id.
+        ///
+        /// May happen in methods:
+        /// 1. [`GetFile`]
+        ///
+        /// [`GetFile`]: crate::requests::GetFile
+        #[serde(rename = "Bad Request: invalid file id")]
+        FileIdInvalid,
+    }
 }
diff --git a/src/lib.rs b/src/lib.rs
index 6e7b78b0..e12421e4 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -19,7 +19,7 @@ mod local_macros;
 // FIXME(waffle): rethink modules, find a place for wrappers.
 pub use self::{
     bot::{AutoSend, Bot, BotBuilder, CacheMe},
-    errors::{ApiErrorKind, DownloadError, KnownApiErrorKind, RequestError},
+    errors::{ApiError, DownloadError, RequestError},
 };
 
 pub mod payloads;
diff --git a/src/local_macros.rs b/src/local_macros.rs
index 5a48a207..85c421f6 100644
--- a/src/local_macros.rs
+++ b/src/local_macros.rs
@@ -106,3 +106,85 @@ macro_rules! req_future {
 
     };
 }
+
+#[macro_use]
+macro_rules! serde_or_unknown {
+    (
+        #[unknown_mod = $mod_:ident]
+        #[unknown_kind = $Kind:ident]
+        #[unknown_path = $path:literal]
+        $(
+            #[ $($meta:tt)* ]
+        )*
+        $v:vis enum $Name:ident {
+            #[unknown]
+            $(
+                #[ $($unknown_meta:tt)* ]
+            )*
+            $Unknown:ident(String)
+
+            $(
+                ,
+                $(
+                    #[ $($var_meta:tt)* ]
+                )*
+                $Var:ident
+            )*
+            $(,)?
+        }
+    ) => {
+        mod $mod_ {
+            #[allow(unused_imports)]
+            use super::{*, $Name as _};
+
+            $(
+                #[ $($meta)* ]
+            )*
+            $v enum $Name {
+                $(
+                    $(
+                        #[ $($var_meta)* ]
+                    )*
+                    $Var,
+                )*
+            }
+
+            #[derive(::serde::Deserialize)]
+            #[serde(untagged)]
+            $v enum $Kind {
+                Known($Name),
+                Unknown(String),
+            }
+
+            impl ::core::convert::From<$Kind> for super::$Name {
+                fn from(kind: $Kind) -> Self {
+                    match kind {
+                        $Kind::Unknown(string) => Self::Unknown(string),
+                        $Kind::Known(known) => match known {
+                            $(
+                                $Name::$Var => Self::$Var,
+                            )*
+                        }
+                    }
+                }
+            }
+        }
+
+        $(
+            #[ $($meta)* ]
+        )*
+        #[serde(from = $path)]
+        $v enum $Name {
+            $(
+                $(
+                    #[ $($var_meta)* ]
+                )*
+                $Var,
+            )*
+            $(
+                #[ $($unknown_meta)* ]
+            )*
+            $Unknown(String),
+        }
+    }
+}
diff --git a/src/net/mod.rs b/src/net/mod.rs
index de38dacc..97db838a 100644
--- a/src/net/mod.rs
+++ b/src/net/mod.rs
@@ -1,7 +1,7 @@
 #[cfg(feature = "unstable-stream")]
-pub use download::download_file_stream;
+pub(crate) use download::download_file_stream;
 
-pub use self::{
+pub(crate) use self::{
     download::download_file,
     request::{request_json, request_json2, request_multipart, request_multipart2},
     telegram_response::TelegramResponse,
diff --git a/src/net/telegram_response.rs b/src/net/telegram_response.rs
index d5062e8f..4bf3849b 100644
--- a/src/net/telegram_response.rs
+++ b/src/net/telegram_response.rs
@@ -4,18 +4,18 @@ use serde::Deserialize;
 use crate::{
     requests::ResponseResult,
     types::{False, ResponseParameters, True},
-    ApiErrorKind, RequestError,
+    ApiError, RequestError,
 };
 
 #[derive(Deserialize)]
 #[serde(untagged)]
-pub enum TelegramResponse<R> {
+pub(crate) enum TelegramResponse<R> {
     Ok {
         /// A dummy field. Used only for deserialization.
         #[allow(dead_code)]
         ok: True,
 
-        result: R,
+        response: R,
     },
     Err {
         /// A dummy field. Used only for deserialization.
@@ -23,7 +23,7 @@ pub enum TelegramResponse<R> {
         ok: False,
 
         #[serde(rename = "description")]
-        kind: ApiErrorKind,
+        error: ApiError,
         error_code: u16,
         response_parameters: Option<ResponseParameters>,
     },
@@ -32,8 +32,8 @@ pub enum TelegramResponse<R> {
 impl<R> Into<ResponseResult<R>> for TelegramResponse<R> {
     fn into(self) -> Result<R, RequestError> {
         match self {
-            TelegramResponse::Ok { result, .. } => Ok(result),
-            TelegramResponse::Err { kind, error_code, response_parameters, .. } => {
+            TelegramResponse::Ok { response, .. } => Ok(response),
+            TelegramResponse::Err { error, error_code, response_parameters, .. } => {
                 if let Some(params) = response_parameters {
                     match params {
                         ResponseParameters::RetryAfter(i) => Err(RequestError::RetryAfter(i)),
@@ -43,7 +43,7 @@ impl<R> Into<ResponseResult<R>> for TelegramResponse<R> {
                     }
                 } else {
                     Err(RequestError::ApiError {
-                        kind,
+                        kind: error,
                         status_code: StatusCode::from_u16(error_code).unwrap(),
                     })
                 }
@@ -55,16 +55,15 @@ impl<R> Into<ResponseResult<R>> for TelegramResponse<R> {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::{errors::KnownApiErrorKind, types::Update};
+    use crate::{errors::ApiError, types::Update};
 
     #[test]
-    fn terminated_by_other_get_updates() {
-        let expected = ApiErrorKind::Known(KnownApiErrorKind::TerminatedByOtherGetUpdates);
-        if let TelegramResponse::Err{ kind, .. } = serde_json::from_str::<TelegramResponse<Update>>(r#"{"ok":false,"error_code":409,"description":"Conflict: terminated by other getUpdates request; make sure that only one bot instance is running"}"#).unwrap() {
-            assert_eq!(expected, kind);
-        }
-        else {
-            panic!("Expected ApiErrorKind::TerminatedByOtherGetUpdates");
-        }
+    fn parse_terminated_by_other_get_updates() {
+        let s = r#"{"ok":false,"error_code":409,"description":"Conflict: terminated by other getUpdates request; make sure that only one bot instance is running"}"#;
+        let val = serde_json::from_str::<TelegramResponse<Update>>(s).unwrap();
+
+        assert!(
+            matches!(val, TelegramResponse::Err { error: ApiError::TerminatedByOtherGetUpdates, .. })
+        );
     }
 }