diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Poscat (c) 2020
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Poscat nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,1 @@
+# telegram-types
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/src/Web/Telegram/Types.hs b/src/Web/Telegram/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Telegram/Types.hs
@@ -0,0 +1,116 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DerivingVia #-}
+
+module Web.Telegram.Types
+  ( -- ** User
+    User (..),
+
+    -- ** Message
+    Message (..),
+    MessageMetadata (..),
+    MessageEntity (..),
+    MessageContent (..),
+    ParseMode (..),
+
+    -- ** Chat
+    ChatId (..),
+    Chat (..),
+    ChatPermissions (..),
+    ChatPhoto (..),
+    ChatMember (..),
+
+    -- ** Media Types
+
+    -- *** Image
+    PhotoSize (..),
+
+    -- *** Audio
+    Audio (..),
+
+    -- *** Animation
+    Animation (..),
+
+    -- *** Document
+    Document (..),
+
+    -- *** Video
+    Video (..),
+
+    -- *** Voice
+    Voice (..),
+
+    -- *** VideoNote
+    VideoNote (..),
+
+    -- *** Contact
+    Contact (..),
+
+    -- *** Location
+    Location (..),
+
+    -- *** Venue
+    Venue (..),
+
+    -- *** PollOption
+    PollOption (..),
+
+    -- *** Poll
+    Poll (..),
+    PollType (..),
+
+    -- *** PollAnswer
+    PollAnswer (..),
+
+    -- *** Avatar
+    UserProfilePhotos (..),
+
+    -- *** File
+    File (..),
+
+    -- ** Stickers
+    Sticker (..),
+    StickerSet (..),
+    MaskPosition (..),
+
+    -- *** Misc
+    SuccessfulPayment (..),
+
+    -- *** Utilities
+    coe,
+    liftUnion,
+    QueryR,
+    Default (..),
+  )
+where
+
+import Data.Aeson
+import Data.Coerce
+import Data.OpenUnion
+import Data.Text (Text)
+import Deriving.Aeson
+import Servant.API
+import Web.Telegram.Types.Internal.Common
+import Web.Telegram.Types.Internal.Media
+import Web.Telegram.Types.Internal.Sticker
+import Web.Telegram.Types.Internal.User
+import Web.Telegram.Types.Internal.Utils
+import Web.Telegram.Types.Internal.InputMedia
+
+data ChatId
+  = ChatId Integer
+  | ChanId Text
+  deriving (Show, Eq, Generic, Default)
+  deriving (FromJSON, ToJSON) via UntaggedSum ChatId
+
+instance ToHttpApiData ChatId where
+  toQueryParam (ChatId i) = toQueryParam i
+  toQueryParam (ChanId t) = t
+
+-- | Alias to coerce
+coe :: Coercible a b => a -> b
+coe = coerce
+
+-- | Alias to required param
+type QueryR = QueryParam' '[Required, Strict]
diff --git a/src/Web/Telegram/Types/Inline.hs b/src/Web/Telegram/Types/Inline.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Telegram/Types/Inline.hs
@@ -0,0 +1,10 @@
+module Web.Telegram.Types.Inline
+  ( InlineQueryResult (..),
+    InlineQuery (..),
+    ChosenInlineResult (..),
+    InputMessageContent (..),
+  )
+where
+
+import Web.Telegram.Types.Internal.InlineQuery
+import Web.Telegram.Types.Internal.InputMedia
diff --git a/src/Web/Telegram/Types/Input.hs b/src/Web/Telegram/Types/Input.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Telegram/Types/Input.hs
@@ -0,0 +1,33 @@
+-- | Sending files
+module Web.Telegram.Types.Input
+  ( -- * Sending Media
+    InputMediaAnimation (..),
+    InputMediaAudio (..),
+    InputMediaDocument (..),
+    InputMediaVideo (..),
+    InputMediaPhoto (..),
+    InputMedia,
+
+    -- * Uploading Files
+    InputFile (..),
+    VideoOrPhoto,
+    Cert (..),
+    Thumb (..),
+    Photo (..),
+    Doc (..),
+    Animation (..),
+    Audio (..),
+    Voice (..),
+    Video (..),
+    VideoNote (..),
+    Sticker (..),
+    PngSticker (..),
+    TgsSticker (..),
+    Media (..),
+    -- Functions
+    readInput,
+  )
+where
+
+import Web.Telegram.Types.Internal.InputFile
+import Web.Telegram.Types.Internal.InputMedia
diff --git a/src/Web/Telegram/Types/Interaction.hs b/src/Web/Telegram/Types/Interaction.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Telegram/Types/Interaction.hs
@@ -0,0 +1,68 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE StandaloneDeriving #-}
+
+-- | User interactions: customized keyboards, clickable buttons, popups and inline displays
+module Web.Telegram.Types.Interaction
+  ( -- ** queries
+    CallbackQuery (..),
+    ShippingQuery (..),
+    PreCheckoutQuery (..),
+    -- ** replys
+    ReplyKeyboardMarkup (..),
+    KeyboardButton (..),
+    KeyboardButtonPollType (..),
+    ReplyKeyboardRemove (..),
+    InlineKeyboardMarkup (..),
+    InlineKeyboardButton (..),
+    ForceReply (..),
+    LoginUrl (..),
+    ReplyMarkup,
+    Action (..),
+  )
+where
+
+import Data.Aeson
+import Data.OpenUnion
+import Deriving.Aeson
+import Servant.API
+import Web.Telegram.Types.Internal.Keyboard
+import Web.Telegram.Types.Internal.Utils
+import Web.Telegram.Types.Internal.Common
+
+type ReplyMarkup =
+  Union
+    '[ InlineKeyboardMarkup,
+       ReplyKeyboardMarkup,
+       ReplyKeyboardRemove,
+       ForceReply
+     ]
+
+instance ToJSON ReplyMarkup where
+  toJSON =
+    (\(inlineM :: InlineKeyboardMarkup) -> toJSON inlineM)
+      @> (\(replyM :: ReplyKeyboardMarkup) -> toJSON replyM)
+      @> (\(replyR :: ReplyKeyboardRemove) -> toJSON replyR)
+      @> (\(forceR :: ForceReply) -> toJSON forceR)
+      @> typesExhausted
+
+deriving via Serialize ReplyMarkup instance ToHttpApiData ReplyMarkup
+
+data Action
+  = Typing
+  | UploadPhoto
+  | RecordVideo
+  | UploadVideo
+  | RecordAudio
+  | UploadAudio
+  | UploadDocument
+  | FindLocation
+  | RecordVideoNote
+  | UploadVideoNote
+  deriving (Show, Eq, Ord, Generic, Default)
+  deriving (ToJSON, FromJSON) via Snake Action
+  deriving (ToHttpApiData) via Serialize Action
diff --git a/src/Web/Telegram/Types/Internal/Common.hs b/src/Web/Telegram/Types/Internal/Common.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Telegram/Types/Internal/Common.hs
@@ -0,0 +1,495 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE PolyKinds #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Web.Telegram.Types.Internal.Common where
+
+import Control.Applicative
+import Data.Aeson
+import Data.Text (Text)
+import Deriving.Aeson
+import Servant.API
+import Web.Telegram.Types.Internal.Keyboard
+import Web.Telegram.Types.Internal.Media
+import Web.Telegram.Types.Internal.Passport
+import Web.Telegram.Types.Internal.Sticker
+import Web.Telegram.Types.Internal.User
+import Web.Telegram.Types.Internal.Utils
+
+data ChatType
+  = Private
+  | Group
+  | Supergroup
+  | Channel
+  deriving (Show, Eq, Ord, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via CustomJSON '[SumUntaggedValue, ConstructorTagModifier CamelToSnake] ChatType
+  deriving (ToHttpApiData) via Serialize ChatType
+
+-- | https://core.telegram.org/bots/api#chat
+data Chat
+  = Chat
+      { chatId :: Integer,
+        chatType :: ChatType,
+        title :: Maybe Text,
+        username :: Maybe Text,
+        firstName :: Maybe Text,
+        lastName :: Maybe Text,
+        photo :: Maybe ChatPhoto,
+        description :: Maybe Text,
+        inviteLink :: Maybe Text,
+        pinnedMessage :: Maybe Message,
+        permissions :: Maybe ChatPermissions,
+        slowModeDelay :: Maybe Integer,
+        stickerSetName :: Maybe Integer,
+        canSetStickerSet :: Maybe Bool
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via PrefixedSnake "chat" Chat
+  deriving (ToHttpApiData) via Serialize Chat
+
+data Message
+  = Msg
+      { metadata :: MessageMetadata,
+        content :: MessageContent
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving (ToHttpApiData) via Serialize Message
+
+instance FromJSON Message where
+  parseJSON o = do
+    metadata <- parseJSON o
+    content <- parseJSON o
+    return Msg {..}
+
+instance ToJSON Message where
+  toJSON Msg {..} =
+    let Object hm1 = toJSON metadata
+        Object hm2 = toJSON content
+     in Object (hm1 <> hm2)
+
+-- |
+data MessageMetadata
+  = MMetadata
+      { messageId :: Integer,
+        from :: Maybe User,
+        date :: Integer,
+        chat :: Chat,
+        forwardFrom :: Maybe User,
+        forwardFromChat :: Maybe Chat,
+        forwardFromMessageId :: Maybe Integer,
+        forwardSignature :: Maybe Text,
+        forwardSenderName :: Maybe Text,
+        forwardDate :: Maybe Integer,
+        replyToMessage :: Maybe Message,
+        editDate :: Maybe Integer,
+        mediaGroupId :: Maybe Text,
+        authorSignature :: Maybe Text,
+        replyMarkup :: Maybe InlineKeyboardMarkup
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via CustomJSON '[SumUntaggedValue, FieldLabelModifier CamelToSnake] MessageMetadata
+  deriving (ToHttpApiData) via Serialize MessageMetadata
+
+data MessageContent
+  = TextM
+      { text :: Text,
+        entities :: Maybe [MessageEntity]
+      }
+  | AudioM
+      { audio :: Audio,
+        caption :: Maybe Text,
+        captionEntities :: Maybe [MessageEntity]
+      }
+  | DocumentM
+      { document :: Document,
+        caption :: Maybe Text,
+        captionEntities :: Maybe [MessageEntity]
+      }
+  | AnimationM
+      { animation :: Animation
+      }
+  | GameM
+      { game :: Game
+      }
+  | PhotoM
+      { photo :: [PhotoSize],
+        caption :: Maybe Text,
+        captionEntities :: Maybe [MessageEntity]
+      }
+  | StickerM
+      { sticker :: Sticker
+      }
+  | VideoM
+      { video :: Video,
+        caption :: Maybe Text,
+        captionEntities :: Maybe [MessageEntity]
+      }
+  | VoiceM
+      { voice :: Voice,
+        caption :: Maybe Text,
+        captionEntities :: Maybe [MessageEntity]
+      }
+  | VideoNoteM
+      { videoNote :: VideoNote
+      }
+  | ContactM
+      { contact :: Contact
+      }
+  | LocationM
+      { location :: Location
+      }
+  | VenueM
+      { venue :: Venue
+      }
+  | PollM
+      { poll :: Poll
+      }
+  | NewChatMembers
+      { newChatMembers :: [User]
+      }
+  | LeftChatMember
+      { leftChatMember :: User
+      }
+  | NewChatPhoto
+      { newChatPhoto :: [PhotoSize]
+      }
+  | DeleteChatPhoto
+      { deleteChatPhoto :: Bool
+      }
+  | GroupChatCreated
+      { groupChatCreated :: Bool
+      }
+  | SupergroupChatCreated
+      { supergroupChatCreated :: Bool
+      }
+  | ChannelChatCreated
+      { channelChatCreated :: Bool
+      }
+  | MigrateToChatId
+      { migrateToChatId :: Integer
+      }
+  | MigrateFromChatId
+      { migrateFromChatId :: Integer
+      }
+  | PinnedMessage
+      { pinnedMessage :: Message
+      }
+  | InvoiceM
+      { invoice :: Invoice
+      }
+  | SuccessfulPaymentM
+      { successfulPayment :: SuccessfulPayment
+      }
+  | ConnectedWebsite
+      { connectedWebsite :: Text
+      }
+  | PassportData
+      { passPortData :: PassportData
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via CustomJSON '[SumUntaggedValue, FieldLabelModifier CamelToSnake] MessageContent
+  deriving (ToHttpApiData) via Serialize MessageContent
+
+data MessageEntityType
+  = Mention
+  | Hashtag
+  | Cashtag
+  | BotCommand
+  | Url
+  | Email
+  | PhoneNumber
+  | Bold
+  | Italic
+  | Underline
+  | Strikethrough
+  | Code
+  | Pre
+  | TextLink
+  | TextMention
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via CustomJSON '[SumUntaggedValue, ConstructorTagModifier CamelToSnake] MessageEntityType
+  deriving (ToHttpApiData) via Serialize MessageEntityType
+
+data MessageEntity
+  = MessageEntity
+      { entityType :: MessageEntityType,
+        offset :: Integer,
+        length :: Integer,
+        url :: Maybe Text,
+        user :: Maybe User,
+        language :: Maybe Text
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via PrefixedSnake "entity" MessageEntity
+  deriving (ToHttpApiData) via Serialize MessageEntity
+
+data CallbackQuery
+  = CBQuery
+      { callbackId :: Text,
+        from :: User,
+        message :: Message,
+        inlineMessageId :: Maybe Text,
+        chatInstance :: Text,
+        callbackData :: Maybe Text,
+        gameShortName :: Maybe Text
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via PrefixedSnake "callback" CallbackQuery
+  deriving (ToHttpApiData) via Serialize CallbackQuery
+
+data ChatPhoto
+  = ChatPhoto
+      { smallFileId :: Text,
+        smallFileUniqueId :: Text,
+        bigFileId :: Text,
+        bitFileUniqueId :: Text
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake ChatPhoto
+  deriving (ToHttpApiData) via Serialize ChatPhoto
+
+data ChatMember
+  = ChatMember
+      { user :: User,
+        status :: Text,
+        customTitle :: Text,
+        untilDate :: Maybe Integer,
+        canBeEdited :: Maybe Bool,
+        canPostMessages :: Maybe Bool,
+        canEditMessages :: Maybe Bool,
+        canDeleteMessages :: Maybe Bool,
+        canRestrictMembers :: Maybe Bool,
+        canPromoteMembers :: Maybe Bool,
+        canChangeInfo :: Maybe Bool,
+        canInviteUsers :: Maybe Bool,
+        canPinMessages :: Maybe Bool,
+        isMember :: Maybe Bool,
+        canSendMessages :: Maybe Bool,
+        canSendMediaMessages :: Maybe Bool,
+        canSendPolls :: Maybe Bool,
+        canSendOtherMesssages :: Maybe Bool,
+        canAddWebPagePreviews :: Maybe Bool
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake ChatMember
+  deriving (ToHttpApiData) via Serialize ChatMember
+
+data ChatPermissions
+  = ChatPermissions
+      { canSendMessages :: Maybe Bool,
+        canSendMediaMessages :: Maybe Bool,
+        canSendPolls :: Maybe Bool,
+        canSendOtherMesssages :: Maybe Bool,
+        canAddWebPagePreviews :: Maybe Bool,
+        canChangeInfo :: Maybe Bool,
+        canInviteUsers :: Maybe Bool,
+        canPinMessages :: Maybe Bool
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake ChatPermissions
+  deriving (ToHttpApiData) via Serialize ChatPermissions
+
+data BotCommand
+  = BC
+      { command :: Text,
+        description :: Text
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via OmitNothing BotCommand
+  deriving (ToHttpApiData) via Serialize BotCommand
+
+data LabeledPrice
+  = LabeledPrice
+      { label :: Text,
+        amount :: Integer
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via OmitNothing LabeledPrice
+  deriving (ToHttpApiData) via Serialize LabeledPrice
+
+data Invoice
+  = Invoice
+      { title :: Text,
+        description :: Text,
+        startParameter :: Text,
+        currency :: Text,
+        totalAmount :: Integer
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake Invoice
+  deriving (ToHttpApiData) via Serialize Invoice
+
+data ShippingAddress
+  = ShippingAddress
+      { countryCode :: Text,
+        state :: Text,
+        city :: Text,
+        streetLine1 :: Text,
+        streetLine2 :: Text,
+        postCode :: Text
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake ShippingAddress
+  deriving (ToHttpApiData) via Serialize ShippingAddress
+
+data OrderInfo
+  = OrderInfo
+      { name :: Maybe Text,
+        phoneNumber :: Maybe Text,
+        email :: Maybe Text,
+        shippingAddress :: ShippingAddress
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake OrderInfo
+  deriving (ToHttpApiData) via Serialize OrderInfo
+
+data ShippingOption
+  = ShippingOption
+      { optionId :: Text,
+        title :: Text,
+        prices :: Text
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via PrefixedSnake "option" ShippingOption
+  deriving (ToHttpApiData) via Serialize ShippingOption
+
+data SuccessfulPayment
+  = SuccessfulPayment
+      { currency :: Text,
+        totalAmount :: Integer,
+        invoicePayload :: Text,
+        shippingOptionId :: Maybe Text,
+        orderInfo :: Maybe OrderInfo,
+        telegramPaymentChargeId :: Text,
+        providerPaymentChargeId :: Text
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake SuccessfulPayment
+  deriving (ToHttpApiData) via Serialize SuccessfulPayment
+
+data ShippingQuery
+  = SQuery
+      { queryId :: Text,
+        from :: User,
+        invoicePayload :: Text,
+        shippingAddress :: ShippingAddress
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via PrefixedSnake "query" ShippingQuery
+  deriving (ToHttpApiData) via Serialize ShippingQuery
+
+data PreCheckoutQuery
+  = PCQuery
+      { queryId :: Text,
+        from :: User,
+        currency :: Text,
+        totalAmount :: Integer,
+        invoicePayload :: Text,
+        shippingOptionId :: Maybe String,
+        orderInfo :: Maybe OrderInfo
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via PrefixedSnake "query" PreCheckoutQuery
+  deriving (ToHttpApiData) via Serialize PreCheckoutQuery
+
+data Game
+  = Game
+      { title :: Text,
+        description :: Text,
+        photo :: [PhotoSize],
+        text :: Maybe Text,
+        textEntities :: Maybe MessageEntity,
+        animation :: Animation
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake Game
+  deriving (ToHttpApiData) via Serialize Game
+
+data CallbackGame
+
+data GameHighScore
+  = GameHighScore
+      { position :: Integer,
+        user :: User,
+        score :: Integer
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via OmitNothing GameHighScore
+  deriving (ToHttpApiData) via Serialize GameHighScore
+
+data ResponseParameters
+  = ResponseParameters
+      { migrateToChatId :: Maybe Integer,
+        retryAfter :: Maybe Integer
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake ResponseParameters
+  deriving (ToHttpApiData) via Serialize ResponseParameters
+
+newtype ReqResult a
+  = Ok a
+  deriving (Show, Eq, Generic)
+
+instance (FromJSON a) => FromJSON (ReqResult a) where
+  parseJSON = withObject "request result" $ \o -> do
+    a <- o .: "result"
+    a' <- parseJSON a
+    return $ Ok a'
+
+data ReqEither a b
+  = LLL a
+  | RRR b
+  deriving (Show, Eq, Generic)
+
+instance (FromJSON a, FromJSON b) => FromJSON (ReqEither a b) where
+  parseJSON o = LLL <$> parseJSON o <|> RRR <$> parseJSON o
diff --git a/src/Web/Telegram/Types/Internal/InlineQuery.hs b/src/Web/Telegram/Types/Internal/InlineQuery.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Telegram/Types/Internal/InlineQuery.hs
@@ -0,0 +1,288 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+
+module Web.Telegram.Types.Internal.InlineQuery where
+
+import Data.Aeson
+import Data.Text (Text)
+import Deriving.Aeson
+import Servant.API
+import Web.Telegram.Types.Internal.InputMedia
+import Web.Telegram.Types.Internal.Keyboard
+import Web.Telegram.Types.Internal.Media
+import Web.Telegram.Types.Internal.User
+import Web.Telegram.Types.Internal.Utils
+
+data InlineQuery
+  = IQ
+      { queryId :: Text,
+        from :: User,
+        location :: Maybe Location,
+        query :: Text,
+        offset :: Text
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via PrefixedSnake' "query" InlineQuery
+  deriving (ToHttpApiData) via Serialize InlineQuery
+
+data InlineQueryResult
+  = InlineQueryResultArticle
+      { resultType :: Text,
+        resultId :: Text,
+        title :: Text,
+        resultInputMessageContent :: InputMessageContent,
+        replyMarkup :: Maybe InlineKeyboardMarkup,
+        url :: Text,
+        hideUrl :: Maybe Bool,
+        description :: Maybe Text,
+        thumbUrl :: Maybe Text,
+        thumbWidth :: Maybe Integer,
+        thumbHeight :: Maybe Integer
+      }
+  | InlineQueryResultPhoto
+      { resultType :: Text,
+        resultId :: Text,
+        photoUrl :: Text,
+        thumbUrl' :: Text,
+        photoWidth :: Maybe Integer,
+        photoHeight :: Maybe Integer,
+        resultTitle :: Maybe Text,
+        description :: Maybe Text,
+        caption :: Maybe Text,
+        parseMode :: Maybe ParseMode,
+        replyMarkup :: Maybe InlineKeyboardMarkup,
+        inputMessageContent :: Maybe InputMessageContent
+      }
+  | InlineQueryResultGif
+      { resultType :: Text,
+        resultId :: Text,
+        gifUrl :: Text,
+        gifWidth :: Maybe Integer,
+        gifHeight :: Maybe Integer,
+        gifDuration :: Maybe Integer,
+        thumbUrl' :: Text,
+        resultTitle :: Maybe Text,
+        caption :: Maybe Text,
+        parseMode :: Maybe ParseMode,
+        replyMarkup :: Maybe InlineKeyboardMarkup,
+        inputMessageContent :: Maybe InputMessageContent
+      }
+  | InlineQueryResultMpeg4Gif
+      { resultType :: Text,
+        resultId :: Text,
+        mpeg4Url :: Text,
+        mpeg4Width :: Maybe Integer,
+        mpeg4Height :: Maybe Integer,
+        mpeg4Duration :: Maybe Integer,
+        thumbUrl' :: Text,
+        resultTitle :: Maybe Text,
+        caption :: Maybe Text,
+        parseMode :: Maybe ParseMode,
+        replyMarkup :: Maybe InlineKeyboardMarkup,
+        inputMessageContent :: Maybe InputMessageContent
+      }
+  | InlineQueryResultVideo
+      { resultType :: Text,
+        resultId :: Text,
+        videoUrl :: Text,
+        mimeType :: Text,
+        thumbUrl' :: Text,
+        title :: Text,
+        caption :: Maybe Text,
+        parseMode :: Maybe ParseMode,
+        videoWidth :: Maybe Integer,
+        videoHeight :: Maybe Integer,
+        videoDuration :: Maybe Integer,
+        description :: Maybe Text,
+        replyMarkup :: Maybe InlineKeyboardMarkup,
+        inputMessageContent :: Maybe InputMessageContent
+      }
+  | InlineQueryResultAudio
+      { resultType :: Text,
+        resultId :: Text,
+        audioUrl :: Text,
+        title :: Text,
+        caption :: Maybe Text,
+        parseMode :: Maybe ParseMode,
+        performer :: Maybe Text,
+        audioDuration :: Maybe Integer,
+        replyMarkup :: Maybe InlineKeyboardMarkup,
+        inputMessageContent :: Maybe InputMessageContent
+      }
+  | InlineQueryResultVoice
+      { resultType :: Text,
+        resultId :: Text,
+        voiceUrl :: Text,
+        title :: Text,
+        caption :: Maybe Text,
+        parseMode :: Maybe ParseMode,
+        voiceDuration :: Maybe Integer,
+        replyMarkup :: Maybe InlineKeyboardMarkup,
+        inputMessageContent :: Maybe InputMessageContent
+      }
+  | InlineQueryResultDocument
+      { resultType :: Text,
+        resultId :: Text,
+        documentUrl :: Text,
+        mimeType :: Text,
+        title :: Text,
+        caption :: Maybe Text,
+        parseMode :: Maybe ParseMode,
+        description :: Maybe Text,
+        replyMarkup :: Maybe InlineKeyboardMarkup,
+        inputMessageContent :: Maybe InputMessageContent,
+        thumbUrl :: Maybe Text,
+        thumbWidth :: Maybe Integer,
+        thumbHeight :: Maybe Integer
+      }
+  | InlineQueryResultLocation
+      { resultType :: Text,
+        resultId :: Text,
+        latitude :: Float,
+        longitude :: Float,
+        title :: Text,
+        livePeriod :: Maybe Integer,
+        replyMarkup :: Maybe InlineKeyboardMarkup,
+        inputMessageContent :: Maybe InputMessageContent,
+        thumbUrl :: Maybe Text,
+        thumbWidth :: Maybe Integer,
+        thumbHeight :: Maybe Integer
+      }
+  | InlineQueryResultVenue
+      { resultType :: Text,
+        resultId :: Text,
+        latitude :: Float,
+        longitude :: Float,
+        title :: Text,
+        address :: Text,
+        foursquareId :: Text,
+        foursquareType :: Maybe Text,
+        replyMarkup :: Maybe InlineKeyboardMarkup,
+        inputMessageContent :: Maybe InputMessageContent,
+        thumbUrl :: Maybe Text,
+        thumbWidth :: Maybe Integer,
+        thumbHeight :: Maybe Integer
+      }
+  | InlineQueryResultContact
+      { resultType :: Text,
+        resultId :: Text,
+        phoneNumber :: Text,
+        firstName :: Text,
+        lastName :: Maybe Text,
+        vcard :: Maybe Text,
+        replyMarkup :: Maybe InlineKeyboardMarkup,
+        inputMessageContent :: Maybe InputMessageContent,
+        thumbUrl :: Maybe Text,
+        thumbWidth :: Maybe Integer,
+        thumbHeight :: Maybe Integer
+      }
+  | InlineQueryResultGame
+      { resultType :: Text,
+        resultId :: Text,
+        gameShortName :: Text,
+        replyMarkup :: Maybe InlineKeyboardMarkup
+      }
+  | InlineQueryResultCachedPhoto
+      { resultType :: Text,
+        resultId :: Text,
+        photoFileId :: Text,
+        resultTitle :: Maybe Text,
+        description :: Maybe Text,
+        caption :: Maybe Text,
+        parseMode :: Maybe ParseMode,
+        replyMarkup :: Maybe InlineKeyboardMarkup,
+        inputMessageContent :: Maybe InputMessageContent
+      }
+  | InlineQueryResultCachedGif
+      { resultType :: Text,
+        resultId :: Text,
+        gifFileId :: Text,
+        title :: Text,
+        caption :: Maybe Text,
+        parseMode :: Maybe ParseMode,
+        replyMarkup :: Maybe InlineKeyboardMarkup,
+        inputMessageContent :: Maybe InputMessageContent
+      }
+  | InlineQueryResultCachedMpeg4Gif
+      { resultType :: Text,
+        resultId :: Text,
+        mpeg4FileId :: Text,
+        title :: Text,
+        caption :: Maybe Text,
+        parseMode :: Maybe ParseMode,
+        replyMarkup :: Maybe InlineKeyboardMarkup,
+        inputMessageContent :: Maybe InputMessageContent
+      }
+  | InlineQueryResultCachedSticker
+      { resultType :: Text,
+        resultId :: Text,
+        stickerFileId :: Text,
+        title :: Text,
+        replyMarkup :: Maybe InlineKeyboardMarkup,
+        inputMessageContent :: Maybe InputMessageContent
+      }
+  | InlineQueryResultCachedDocument
+      { resultType :: Text,
+        resultId :: Text,
+        title :: Text,
+        documentFileId :: Text,
+        description :: Maybe Text,
+        caption :: Maybe Text,
+        parseMode :: Maybe ParseMode,
+        replyMarkup :: Maybe InlineKeyboardMarkup,
+        inputMessageContent :: Maybe InputMessageContent
+      }
+  | InlineQueryResultCachedVideo
+      { resultType :: Text,
+        resultId :: Text,
+        videoFileId :: Text,
+        title :: Text,
+        description :: Maybe Text,
+        caption :: Maybe Text,
+        parseMode :: Maybe ParseMode,
+        replyMarkup :: Maybe InlineKeyboardMarkup,
+        inputMessageContent :: Maybe InputMessageContent
+      }
+  | InlineQueryResultCachedVoice
+      { resultType :: Text,
+        resultId :: Text,
+        voiceFileId :: Text,
+        title :: Text,
+        caption :: Maybe Text,
+        parseMode :: Maybe ParseMode,
+        replyMarkup :: Maybe InlineKeyboardMarkup,
+        inputMessageContent :: Maybe InputMessageContent
+      }
+  | InlineQueryResultCachedAudio
+      { resultType :: Text,
+        resultId :: Text,
+        audioFileId :: Text,
+        caption :: Maybe Text,
+        parseMode :: Maybe ParseMode,
+        replyMarkup :: Maybe InlineKeyboardMarkup,
+        inputMessageContent :: Maybe InputMessageContent
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via PrefixedSnake "result" InlineQueryResult
+  deriving (ToHttpApiData) via Serialize InlineQueryResult
+
+data ChosenInlineResult
+  = ChosenIR
+      { resultId :: Text,
+        from :: User,
+        location :: Maybe Location,
+        inlineMessageId :: Maybe Text,
+        query :: Text
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake ChosenInlineResult
+  deriving (ToHttpApiData) via Serialize ChosenInlineResult
diff --git a/src/Web/Telegram/Types/Internal/InputFile.hs b/src/Web/Telegram/Types/Internal/InputFile.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Telegram/Types/Internal/InputFile.hs
@@ -0,0 +1,110 @@
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE PolyKinds #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications #-}
+
+module Web.Telegram.Types.Internal.InputFile where
+
+import Data.ByteString.Lazy (ByteString)
+import qualified Data.ByteString.Lazy as LBS
+import Data.Proxy
+import Data.Text (Text, pack)
+import Data.Text.Encoding
+import GHC.Generics
+import GHC.TypeLits
+import Network.Mime
+import Servant.Multipart
+import System.FilePath
+import Web.Telegram.Types.Internal.Utils
+
+data InputFile
+  = InputFile
+      { fileName :: Text,
+        mimeType :: Text,
+        content :: ByteString
+      }
+  deriving (Show, Eq, Generic, Default)
+
+readInput :: FilePath -> IO InputFile
+readInput fp = do
+  content <- LBS.readFile fp
+  let fileName = pack $ takeFileName fp
+  let mimeType = decodeUtf8 $ defaultMimeLookup fileName
+  return InputFile {..}
+
+type Multi = ToMultipart Mem
+
+newtype InputF n = InputF InputFile
+
+instance (KnownSymbol s) => ToMultipart Mem (InputF s) where
+  toMultipart (InputF f) =
+    MultipartData
+      { inputs = [],
+        files =
+          pure $
+            FileData
+              { fdInputName = pack $ symbolVal @s Proxy,
+                fdFileName = fileName f,
+                fdFileCType = mimeType f,
+                fdPayload = content f
+              }
+      }
+
+newtype Cert = Cert InputFile
+  deriving (Show, Eq, Generic, Default)
+  deriving (Multi) via InputF "certificate"
+
+newtype Thumb = Thumb InputFile
+  deriving (Show, Eq, Generic, Default)
+  deriving (Multi) via InputF "thumb"
+
+newtype Photo = Photo InputFile
+  deriving (Show, Eq, Generic, Default)
+  deriving (Multi) via InputF "photo"
+
+newtype Audio = Audio InputFile
+  deriving (Show, Eq, Generic, Default)
+  deriving (Multi) via InputF "audio"
+
+newtype Doc = Doc InputFile
+  deriving (Show, Eq, Generic, Default)
+  deriving (Multi) via InputF "document"
+
+newtype Video = Video InputFile
+  deriving (Show, Eq, Generic, Default)
+  deriving (Multi) via InputF "video"
+
+newtype Animation = Animation InputFile
+  deriving (Show, Eq, Generic, Default)
+  deriving (Multi) via InputF "animation"
+
+newtype Voice = Voice InputFile
+  deriving (Show, Eq, Generic, Default)
+  deriving (Multi) via InputF "voice"
+
+newtype VideoNote = VideoNote InputFile
+  deriving (Show, Eq, Generic, Default)
+  deriving (Multi) via InputF "video_note"
+
+newtype Sticker = Sticker InputFile
+  deriving (Show, Eq, Generic, Default)
+  deriving (Multi) via InputF "sticker"
+
+newtype PngSticker = PngSticker InputFile
+  deriving (Show, Eq, Generic, Default)
+  deriving (Multi) via InputF "png_sticker"
+
+newtype TgsSticker = TgsSticker InputFile
+  deriving (Show, Eq, Generic, Default)
+  deriving (Multi) via InputF "tgs_sticker"
+
+newtype Media = Media InputFile
+  deriving (Show, Eq, Generic, Default)
+  deriving (Multi) via InputF "media"
diff --git a/src/Web/Telegram/Types/Internal/InputMedia.hs b/src/Web/Telegram/Types/Internal/InputMedia.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Telegram/Types/Internal/InputMedia.hs
@@ -0,0 +1,283 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE TypeOperators #-}
+
+module Web.Telegram.Types.Internal.InputMedia where
+
+import Data.Aeson
+import Data.Maybe (catMaybes)
+import Data.OpenUnion
+import Data.Text (Text)
+import Deriving.Aeson
+import Servant.API
+import Servant.Multipart
+import Web.Telegram.Types.Internal.InputFile
+import Web.Telegram.Types.Internal.Utils
+
+data ParseMode
+  = MarkdownV2
+  | HTML
+  | Markdown
+  deriving (Show, Eq, Ord, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via OmitNothing ParseMode
+  deriving (ToHttpApiData) via Serialize ParseMode
+
+type VideoOrPhoto = Union '[InputMediaPhoto, InputMediaVideo]
+
+instance ToJSON VideoOrPhoto where
+  toJSON =
+    (\(photo :: InputMediaPhoto) -> toJSON photo)
+      @> (\(video :: InputMediaVideo) -> toJSON video)
+      @> typesExhausted
+
+instance ToMultipart Mem VideoOrPhoto where
+  toMultipart =
+    (\(photo :: InputMediaPhoto) -> toMultipart photo)
+      @> (\(video :: InputMediaVideo) -> toMultipart video)
+      @> typesExhausted
+
+deriving via Serialize VideoOrPhoto instance ToHttpApiData VideoOrPhoto
+
+type InputMedia =
+  Union
+    '[ InputMediaAnimation,
+       InputMediaDocument,
+       InputMediaAudio,
+       InputMediaVideo,
+       InputMediaPhoto
+     ]
+
+instance ToJSON InputMedia where
+  toJSON =
+    (\(anim :: InputMediaAnimation) -> toJSON anim)
+      @> (\(doc :: InputMediaDocument) -> toJSON doc)
+      @> (\(audio :: InputMediaAudio) -> toJSON audio)
+      @> (\(video :: InputMediaVideo) -> toJSON video)
+      @> (\(photo :: InputMediaPhoto) -> toJSON photo)
+      @> typesExhausted
+
+instance ToMultipart Mem InputMedia where
+  toMultipart =
+    (\(anim :: InputMediaAnimation) -> toMultipart anim)
+      @> (\(doc :: InputMediaDocument) -> toMultipart doc)
+      @> (\(audio :: InputMediaAudio) -> toMultipart audio)
+      @> (\(video :: InputMediaVideo) -> toMultipart video)
+      @> (\(photo :: InputMediaPhoto) -> toMultipart photo)
+      @> typesExhausted
+
+deriving via Serialize InputMedia instance ToHttpApiData InputMedia
+
+data InputMediaAnimation
+  = InputMediaAnimation
+      { media :: Either Text Media,
+        thumb :: Maybe Text,
+        caption :: Maybe Text,
+        parseMode :: Maybe ParseMode,
+        width :: Maybe Integer,
+        height :: Maybe Integer,
+        duration :: Maybe Integer
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving (ToHttpApiData) via Serialize InputMediaAnimation
+
+(.=?) :: ToJSON v => Text -> Maybe v -> Maybe (Text, Value)
+k .=? v = (k .=) <$> v
+
+instance ToJSON InputMediaAnimation where
+  toJSON InputMediaAnimation {..} =
+    let m = case media of
+          Left t -> t
+          Right (Media m') -> "attach://" <> fileName m'
+     in object $
+          [ "type" .= ("animation" :: Text),
+            "media" .= m
+          ]
+            <> catMaybes
+              [ "thumb" .=? thumb,
+                "caption" .=? caption,
+                "parse_mode" .=? parseMode,
+                "width" .=? width,
+                "height" .=? height,
+                "duration" .=? duration
+              ]
+
+instance ToMultipart Mem InputMediaAnimation where
+  toMultipart InputMediaAnimation {..} =
+    case media of
+      Right m -> toMultipart m
+      Left _ -> emptyData
+
+data InputMediaAudio
+  = InputMediaAudio
+      { media :: Either Text Media,
+        thumb :: Maybe Text,
+        caption :: Maybe Text,
+        parseMode :: Maybe ParseMode,
+        duration :: Maybe Integer,
+        performer :: Maybe Text,
+        title :: Maybe Text
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving (ToHttpApiData) via Serialize InputMediaAudio
+
+instance ToJSON InputMediaAudio where
+  toJSON InputMediaAudio {..} =
+    let m = case media of
+          Left t -> t
+          Right (Media m') -> "attach://" <> fileName m'
+     in object $
+          [ "type" .= ("audio" :: Text),
+            "media" .= m
+          ]
+            <> catMaybes
+              [ "thumb" .=? thumb,
+                "caption" .=? caption,
+                "parse_mode" .=? parseMode,
+                "duration" .=? duration,
+                "performer" .=? performer,
+                "title" .=? title
+              ]
+
+instance ToMultipart Mem InputMediaAudio where
+  toMultipart InputMediaAudio {..} =
+    case media of
+      Right m -> toMultipart m
+      Left _ -> emptyData
+
+data InputMediaDocument
+  = InputMediaDocument
+      { media :: Either Text Media,
+        thumb :: Maybe Text,
+        caption :: Maybe Text,
+        parseMode :: Maybe ParseMode
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving (ToHttpApiData) via Serialize InputMediaDocument
+
+instance ToJSON InputMediaDocument where
+  toJSON InputMediaDocument {..} =
+    let m = case media of
+          Left t -> t
+          Right (Media m') -> "attach://" <> fileName m'
+     in object $
+          [ "type" .= ("document" :: Text),
+            "media" .= m
+          ]
+            <> catMaybes
+              [ "thumb" .=? thumb,
+                "caption" .=? caption,
+                "parse_mode" .=? parseMode
+              ]
+
+instance ToMultipart Mem InputMediaDocument where
+  toMultipart InputMediaDocument {..} =
+    case media of
+      Right m -> toMultipart m
+      Left _ -> emptyData
+
+data InputMediaPhoto
+  = InputMediaPhoto
+      { media :: Either Text Media,
+        caption :: Maybe Text,
+        parseMode :: Maybe ParseMode
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving (ToHttpApiData) via Serialize InputMediaPhoto
+
+instance ToJSON InputMediaPhoto where
+  toJSON InputMediaPhoto {..} =
+    let m = case media of
+          Left t -> t
+          Right (Media m') -> "attach://" <> fileName m'
+     in object $
+          [ "type" .= ("photo" :: Text),
+            "media" .= m
+          ]
+            <> catMaybes
+              [ "caption" .=? caption,
+                "parse_mode" .=? parseMode
+              ]
+
+instance ToMultipart Mem InputMediaPhoto where
+  toMultipart InputMediaPhoto {..} =
+    case media of
+      Right m -> toMultipart m
+      Left _ -> emptyData
+
+data InputMediaVideo
+  = InputMediaVideo
+      { media :: Either Text Media,
+        thumb :: Maybe Text,
+        caption :: Maybe Text,
+        parseMode :: Maybe ParseMode
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving (ToHttpApiData) via Serialize InputMediaVideo
+
+instance ToJSON InputMediaVideo where
+  toJSON InputMediaVideo {..} =
+    let m = case media of
+          Left t -> t
+          Right (Media m') -> "attach://" <> fileName m'
+     in object $
+          [ "type" .= ("video" :: Text),
+            "media" .= m
+          ]
+            <> catMaybes
+              [ "thumb" .=? thumb,
+                "caption" .=? caption,
+                "parse_mode" .=? parseMode
+              ]
+
+instance ToMultipart Mem InputMediaVideo where
+  toMultipart InputMediaVideo {..} =
+    case media of
+      Right m -> toMultipart m
+      Left _ -> emptyData
+
+data InputMessageContent
+  = InputTextMessageContent
+      { messageText :: Text,
+        parseMode :: Maybe ParseMode,
+        disableWebPagePreview :: Maybe Bool
+      }
+  | InputLocationMessageContent
+      { latitude :: Float,
+        longitude :: Float,
+        livePeriod :: Maybe Integer
+      }
+  | InputVenueMessageContent
+      { latitude :: Float,
+        longitude :: Float,
+        title :: Text,
+        address :: Text,
+        foursquareId :: Text,
+        foursquareType :: Text
+      }
+  | InputContactMessageContent
+      { phoneNumber :: Text,
+        firstName :: Text,
+        lastName :: Maybe Text,
+        vcard :: Maybe Text
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake InputMessageContent
+  deriving (ToHttpApiData) via Serialize InputMessageContent
+
+------
+
+emptyData :: MultipartData a
+emptyData = MultipartData [] []
diff --git a/src/Web/Telegram/Types/Internal/Keyboard.hs b/src/Web/Telegram/Types/Internal/Keyboard.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Telegram/Types/Internal/Keyboard.hs
@@ -0,0 +1,110 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+
+module Web.Telegram.Types.Internal.Keyboard where
+
+import Data.Text (Text)
+import Deriving.Aeson
+import Servant.API
+import Web.Telegram.Types.Internal.Media
+import Web.Telegram.Types.Internal.Utils
+
+data ReplyKeyboardMarkup
+  = ReplyKeyboardMarkup
+      { keyboard :: [[KeyboardButton]],
+        resizeKeyboard :: Maybe Bool,
+        oneTimeKeyboard :: Maybe Bool,
+        selective :: Maybe Bool
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake ReplyKeyboardMarkup
+  deriving (ToHttpApiData) via Serialize ReplyKeyboardMarkup
+
+data KeyboardButton
+  = KeyboardButton
+      { text :: Text,
+        requestContact :: Maybe Bool,
+        requestLocation :: Maybe Bool,
+        requestPoll :: Maybe KeyboardButtonPollType
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake KeyboardButton
+  deriving (ToHttpApiData) via Serialize KeyboardButton
+
+newtype KeyboardButtonPollType
+  = KeyboardButtonPollType
+      { pollType :: Maybe PollType
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via PrefixedSnake "poll" KeyboardButtonPollType
+  deriving (ToHttpApiData) via Serialize KeyboardButtonPollType
+
+data ReplyKeyboardRemove
+  = ReplyKeyboardRemove
+      { removeKeyboard :: Bool,
+        selective :: Maybe Bool
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake ReplyKeyboardRemove
+  deriving (ToHttpApiData) via Serialize ReplyKeyboardRemove
+
+newtype InlineKeyboardMarkup
+  = InlineKeyboardMarkup
+      { inlineKeyboard :: [[InlineKeyboardButton]]
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (ToJSON, FromJSON)
+    via Snake InlineKeyboardMarkup
+  deriving (ToHttpApiData) via Serialize InlineKeyboardMarkup
+
+data InlineKeyboardButton
+  = InlineKeyboardButton
+      { text :: Text,
+        url :: Maybe Text,
+        loginUrl :: Maybe LoginUrl,
+        callbackData :: Maybe Text,
+        switchInlineQuery :: Maybe Text,
+        switchInlineQueryCurrentChat :: Maybe Text,
+        pay :: Maybe Bool
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake InlineKeyboardButton
+  deriving (ToHttpApiData) via Serialize InlineKeyboardButton
+
+data LoginUrl
+  = LoginUrl
+      { url :: Text,
+        forwardText :: Maybe Text,
+        botUsername :: Maybe Text,
+        requestWriteAccess :: Maybe Bool
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake LoginUrl
+  deriving (ToHttpApiData) via Serialize LoginUrl
+
+data ForceReply
+  = ForceReply
+      { forceReply :: Bool,
+        selective :: Maybe Bool
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake ForceReply
+  deriving (ToHttpApiData) via Serialize ForceReply
diff --git a/src/Web/Telegram/Types/Internal/Media.hs b/src/Web/Telegram/Types/Internal/Media.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Telegram/Types/Internal/Media.hs
@@ -0,0 +1,237 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+
+module Web.Telegram.Types.Internal.Media where
+
+import Data.Aeson
+import Data.Text (Text)
+import Deriving.Aeson
+import Servant.API
+import Web.Telegram.Types.Internal.User
+import Web.Telegram.Types.Internal.Utils
+
+data PhotoSize
+  = PhotoSize
+      { fileId :: Text,
+        fileUniqueId :: Text,
+        width :: Integer,
+        height :: Integer,
+        fileSize :: Maybe Integer
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake PhotoSize
+  deriving (ToHttpApiData) via Serialize PhotoSize
+
+data Audio
+  = Audio
+      { fileId :: Text,
+        fileUniqueId :: Text,
+        duration :: Integer,
+        performer :: Maybe Text,
+        title :: Maybe Text,
+        mimeType :: Maybe Text,
+        fileSize :: Maybe Integer,
+        thumb :: Maybe PhotoSize
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake Audio
+  deriving (ToHttpApiData) via Serialize Audio
+
+data Document
+  = Document
+      { fileId :: Text,
+        fileUniqueId :: Text,
+        thumb :: Maybe PhotoSize,
+        fileName :: Maybe Text,
+        mimeType :: Maybe Text,
+        fileSize :: Maybe Integer
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake Document
+  deriving (ToHttpApiData) via Serialize Document
+
+data Video
+  = Video
+      { fileId :: Text,
+        fileUniqueId :: Text,
+        width :: Integer,
+        height :: Integer,
+        duration :: Integer,
+        thumb :: Maybe PhotoSize,
+        mimeType :: Maybe Text,
+        fileSize :: Maybe Integer
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake Video
+  deriving (ToHttpApiData) via Serialize Video
+
+data Animation
+  = Animation
+      { fileId :: Text,
+        fileUniqueId :: Text,
+        width :: Integer,
+        height :: Integer,
+        duration :: Integer,
+        thumb :: Maybe PhotoSize,
+        fileName :: Maybe Text,
+        mimeType :: Maybe Text,
+        fileSize :: Integer
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake Animation
+  deriving (ToHttpApiData) via Serialize Animation
+
+data Voice
+  = Voice
+      { fileId :: Text,
+        fileUniqueId :: Text,
+        duration :: Integer,
+        mimeType :: Maybe Text,
+        fileSize :: Maybe Integer
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake Voice
+  deriving (ToHttpApiData) via Serialize Voice
+
+data VideoNote
+  = VideoNote
+      { fileId :: Text,
+        fileUniqueId :: Text,
+        length :: Integer,
+        duration :: Integer,
+        thumb :: Maybe PhotoSize,
+        fileSize :: Integer
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake VideoNote
+  deriving (ToHttpApiData) via Serialize VideoNote
+
+data Contact
+  = Contact
+      { phoneNumber :: Text,
+        firstName :: Text,
+        lastName :: Maybe Text,
+        userId :: Maybe Integer,
+        vcard :: Maybe Text
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake Contact
+  deriving (ToHttpApiData) via Serialize Contact
+
+data Location
+  = Location
+      { longitude :: Float,
+        latitude :: Float
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via OmitNothing Location
+  deriving (ToHttpApiData) via Serialize Location
+
+data Venue
+  = Venue
+      { location :: Location,
+        title :: Text,
+        address :: Text,
+        foursquareId :: Maybe Text,
+        foursquareType :: Maybe Text
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake Venue
+  deriving (ToHttpApiData) via Serialize Venue
+
+data PollOption
+  = PollOption
+      { text :: Text,
+        voterCount :: Integer
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake PollOption
+  deriving (ToHttpApiData) via Serialize PollOption
+
+data PollAnswer
+  = PollAnswer
+      { pollId :: Text,
+        user :: User,
+        optionIds :: [Integer]
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via PrefixedSnake "poll" PollAnswer
+  deriving (ToHttpApiData) via Serialize PollAnswer
+
+data PollType
+  = Regular
+  | Quiz
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via CustomJSON '[SumUntaggedValue, ConstructorTagModifier CamelToSnake] PollType
+  deriving (ToHttpApiData) via Serialize PollType
+
+data Poll
+  = Poll
+      { pollId :: Text,
+        question :: Text,
+        options :: [PollOption],
+        totalVoterCount :: Integer,
+        isClosed :: Bool,
+        isAnonymous :: Bool,
+        pollType :: PollType,
+        allowsMultipleAnswers :: Bool,
+        correctOptionId :: Maybe Integer
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via PrefixedSnake "poll" Poll
+  deriving (ToHttpApiData) via Serialize Poll
+
+data UserProfilePhotos
+  = UserProfilePhotos
+      { totalCount :: Integer,
+        photos :: [[PhotoSize]]
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake UserProfilePhotos
+  deriving (ToHttpApiData) via Serialize UserProfilePhotos
+
+data File
+  = File
+      { fileId :: Text,
+        fileUniqueId :: Text,
+        fileSize :: Maybe Integer,
+        filePath :: Maybe Text
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake File
+  deriving (ToHttpApiData) via Serialize File
diff --git a/src/Web/Telegram/Types/Internal/Passport.hs b/src/Web/Telegram/Types/Internal/Passport.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Telegram/Types/Internal/Passport.hs
@@ -0,0 +1,147 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+
+module Web.Telegram.Types.Internal.Passport where
+
+import Data.Text (Text)
+import Deriving.Aeson
+import Servant.API
+import Web.Telegram.Types.Internal.Utils
+
+data PassportData
+  = PassportData
+      { passportData :: [EncryptedPassportElement],
+        credentials :: EncryptedCredentials
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via PrefixedSnake "passport" PassportData
+  deriving (ToHttpApiData) via Serialize PassportData
+
+data PassportFile
+  = PassportFile
+      { fileId :: Text,
+        fileUniqueId :: Text,
+        fileSize :: Integer,
+        fileDate :: Integer
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (ToJSON, FromJSON)
+    via Snake PassportFile
+  deriving (ToHttpApiData) via Serialize PassportFile
+
+data EncryptedPassportElement
+  = EncryptedPassportElement
+      { elementType :: EncryptedPassportElementType,
+        elementData :: Maybe Text,
+        phoneNumber :: Maybe Text,
+        email :: Maybe Text,
+        files :: Maybe [PassportFile],
+        frontSide :: Maybe PassportFile,
+        reverseSide :: Maybe PassportFile,
+        selfie :: Maybe PassportFile,
+        translation :: Maybe [PassportFile],
+        hash :: Text
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (ToJSON, FromJSON)
+    via PrefixedSnake "element" EncryptedPassportElement
+
+data EncryptedCredentials
+  = EncryptedCredentials
+      { credentialData :: Text,
+        hash :: Text,
+        secret :: Text
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (ToJSON, FromJSON)
+    via PrefixedSnake "credential" EncryptedCredentials
+
+data PassportElementError
+  = PassportElementErrorDataField
+      { source :: Text,
+        errorType :: EncryptedPassportElementType,
+        fieldName :: Text,
+        dataHash :: Text,
+        message :: Text
+      }
+  | PassportElementErrorFrontSide
+      { source :: Text,
+        errorType :: EncryptedPassportElementType,
+        fileHash :: Text,
+        message :: Text
+      }
+  | PassportElementErrorReverseSide
+      { source :: Text,
+        errorType :: EncryptedPassportElementType,
+        fileHash :: Text,
+        message :: Text
+      }
+  | PassportElementErrorSelfie
+      { source :: Text,
+        errorType :: EncryptedPassportElementType,
+        fileHash :: Text,
+        message :: Text
+      }
+  | PassportElementErrorFile
+      { source :: Text,
+        errorType :: EncryptedPassportElementType,
+        fileHash :: Text,
+        message :: Text
+      }
+  | PassportElementErrorFiles
+      { source :: Text,
+        errorType :: EncryptedPassportElementType,
+        fileHashes :: [Text],
+        message :: Text
+      }
+  | PassportElementErrorTranslationFile
+      { source :: Text,
+        errorType :: EncryptedPassportElementType,
+        fileHash :: Text,
+        message :: Text
+      }
+  | PassportElementErrorTranslationFiles
+      { source :: Text,
+        errorType :: EncryptedPassportElementType,
+        fileHashes :: [Text],
+        message :: Text
+      }
+  | PassportElementErrorUnspecified
+      { source :: Text,
+        errorType :: EncryptedPassportElementType,
+        elementHash :: Text,
+        message :: Text
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (ToJSON, FromJSON)
+    via PrefixedSnake "error" PassportElementError
+  deriving (ToHttpApiData) via Serialize PassportElementError
+
+data EncryptedPassportElementType
+  = PersonalDetails
+  | Passport
+  | DriverLicense
+  | IdentityCard
+  | InternalPassport
+  | Address
+  | UtilityBill
+  | BankStatement
+  | RentalAgreement
+  | PassportRegistration
+  | TemporaryRegistration
+  | PhoneNumber
+  | Email
+  deriving (Show, Eq, Ord, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via CustomJSON '[SumUntaggedValue, ConstructorTagModifier CamelToSnake] EncryptedPassportElementType
+  deriving (ToHttpApiData) via Serialize EncryptedPassportElementType
diff --git a/src/Web/Telegram/Types/Internal/Sticker.hs b/src/Web/Telegram/Types/Internal/Sticker.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Telegram/Types/Internal/Sticker.hs
@@ -0,0 +1,58 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+
+module Web.Telegram.Types.Internal.Sticker where
+
+import Data.Text (Text)
+import Deriving.Aeson
+import Servant.API
+import Web.Telegram.Types.Internal.Media
+import Web.Telegram.Types.Internal.Utils
+
+data Sticker
+  = Sticker
+      { fileId :: Text,
+        fileUniqueId :: Text,
+        width :: Integer,
+        height :: Integer,
+        isAnimated :: Bool,
+        thumb :: Maybe PhotoSize,
+        emoji :: Maybe Text,
+        setName :: Maybe Text,
+        maskPosition :: Maybe MaskPosition,
+        fileSize :: Maybe Integer
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake Sticker
+  deriving (ToHttpApiData) via Serialize Sticker
+
+data StickerSet
+  = StickerSet
+      { name :: Text,
+        title :: Text,
+        isAnimated :: Bool,
+        containsMasks :: Bool,
+        stickers :: [Sticker]
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake StickerSet
+  deriving (ToHttpApiData) via Serialize StickerSet
+
+data MaskPosition
+  = MaskPosition
+      { point :: Text,
+        xShift :: Float,
+        yShift :: Float,
+        scale :: Float
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake MaskPosition
+  deriving (ToHttpApiData) via Serialize MaskPosition
diff --git a/src/Web/Telegram/Types/Internal/Update.hs b/src/Web/Telegram/Types/Internal/Update.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Telegram/Types/Internal/Update.hs
@@ -0,0 +1,125 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RankNTypes #-}
+
+module Web.Telegram.Types.Internal.Update where
+
+import Data.Aeson
+import Data.Monoid
+import Data.Text (Text)
+import Deriving.Aeson
+import qualified Web.Telegram.Types.Internal.Common as C
+import qualified Web.Telegram.Types.Internal.InlineQuery as IQ
+import qualified Web.Telegram.Types.Internal.Media as M
+import Web.Telegram.Types.Internal.UpdateType (UpdateType)
+import Web.Telegram.Types.Internal.Utils
+
+-- | An incoming update
+data Update
+  = -- | New incoming message of any kind — text, photo, sticker, etc.
+    Message
+      { updateId :: Integer,
+        message :: C.Message
+      }
+  | -- | New version of a message that is known to the bot and was edited
+    EditedMessage
+      { updateId :: Integer,
+        message :: C.Message
+      }
+  | -- | New incoming channel post of any kind — text, photo, sticker, etc.
+    ChannelPost
+      { updateId :: Integer,
+        message :: C.Message
+      }
+  | -- | New version of a channel post that is known to the bot and was edited
+    EditedChannelPost
+      { updateId :: Integer,
+        message :: C.Message
+      }
+  | -- | New incoming inline query
+    InlineQuery
+      { updateId :: Integer,
+        iquery :: IQ.InlineQuery
+      }
+  | -- | The result of an inline query that was chosen by a user and sent to their chat partner. Please see our documentation on the feedback collecting for details on how to enable these updates for your bot
+    ChosenInlineResult
+      { updateId :: Integer,
+        result :: IQ.ChosenInlineResult
+      }
+  | -- | New incoming callback query
+    CallbackQuery
+      { updateId :: Integer,
+        cbquery :: C.CallbackQuery
+      }
+  | -- | New incoming shipping query. Only for invoices with flexible price
+    ShippingQuery
+      { updateId :: Integer,
+        squery :: C.ShippingQuery
+      }
+  | -- | New incoming pre-checkout query. Contains full information about checkout
+    PreCheckoutQuery
+      { updateId :: Integer,
+        pcquery :: C.PreCheckoutQuery
+      }
+  | -- | New poll state. Bots receive only updates about stopped polls and polls, which are sent by the bot
+    PollUpdate
+      { updateId :: Integer,
+        poll :: M.Poll
+      }
+  | -- | A user changed their answer in a non-anonymous poll. Bots receive new votes only in polls that were sent by the bot itself.
+    PollAnswer
+      { updateId :: Integer,
+        answer :: M.PollAnswer
+      }
+  deriving (Show, Eq, Generic, Default)
+
+instance FromJSON Update where
+  parseJSON = withObject "Update object" $ \o -> do
+    uid <- o .: "update_id"
+    let pair (k, c) = do
+          m <- o .:? k
+          return $ fmap (c uid) m
+    l <-
+      sequence
+        [ pair ("message", Message),
+          pair ("edited_message", EditedMessage),
+          pair ("channel_post", ChannelPost),
+          pair ("edited_channel_post", EditedChannelPost),
+          pair ("inline_query", InlineQuery),
+          pair ("chosen_inline_result", ChosenInlineResult),
+          pair ("callback_query", CallbackQuery),
+          pair ("shipping_query", ShippingQuery),
+          pair ("pre_checkout_query", PreCheckoutQuery),
+          pair ("poll", PollUpdate),
+          pair ("poll_answer", PollAnswer)
+        ]
+    let r = getFirst $ foldMap First l
+    case r of
+      Nothing -> fail "Empty Message"
+      Just r' -> return r'
+
+-- | Contains information about the current status of a webhook.
+data WebhookInfo
+  = WebhookInfo
+      { -- | Webhook URL, may be empty if webhook is not set up
+        url :: Text,
+        -- | True, if a custom certificate was provided for webhook certificate checks
+        hasCustomCertificate :: Bool,
+        -- | Number of updates awaiting delivery
+        pendingUpdateCount :: Integer,
+        -- | Unix time for the most recent error that happened when trying to deliver an update via webhook
+        lastErrorDate :: Maybe Integer,
+        -- | Error message in human-readable format for the most recent error that happened when trying to deliver an update via webhook
+        lastErrorMessage :: Maybe Text,
+        -- | Maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery
+        maxConnections :: Maybe Integer,
+        -- | A list of update types the bot is subscribed to. Defaults to all update types
+        allowedUpdates :: Maybe [UpdateType]
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via Snake WebhookInfo
diff --git a/src/Web/Telegram/Types/Internal/UpdateType.hs b/src/Web/Telegram/Types/Internal/UpdateType.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Telegram/Types/Internal/UpdateType.hs
@@ -0,0 +1,26 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingVia #-}
+
+module Web.Telegram.Types.Internal.UpdateType where
+
+import Deriving.Aeson
+import Web.Telegram.Types.Internal.Utils
+
+data UpdateType
+  = Message
+  | EditedMessage
+  | ChannelPost
+  | EditedChannelPost
+  | InlineQuery
+  | ChosenInlineResult
+  | CallbackQuery
+  | ShippingQuery
+  | PreCheckoutQuery
+  | PollUpdate
+  | PollAnswer
+  deriving (Show, Eq, Generic, Default, Enum)
+  deriving
+    (FromJSON, ToJSON)
+    via CustomJSON '[SumUntaggedValue, ConstructorTagModifier CamelToSnake] UpdateType
diff --git a/src/Web/Telegram/Types/Internal/User.hs b/src/Web/Telegram/Types/Internal/User.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Telegram/Types/Internal/User.hs
@@ -0,0 +1,45 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE PolyKinds #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Web.Telegram.Types.Internal.User
+  ( User (..),
+  )
+where
+
+import Data.Aeson
+import Data.Text (Text)
+import Deriving.Aeson
+import Servant.API
+import Web.Telegram.Types.Internal.Utils
+
+-- | A Telegram user or bot.
+data User
+  = User
+      { -- | Unique identifier for this user or bot
+        userId :: Integer,
+        -- | True, if this user is a bot
+        isBot :: Bool,
+        -- | User's or bot's first name
+        firstName :: Text,
+        -- | User's or bot's last name
+        lastName :: Maybe Text,
+        -- | User's or bot's username
+        username :: Maybe Text,
+        -- | [IETF language tag](https://en.wikipedia.org/wiki/IETF_language_tag) of the user's language
+        languageCode :: Maybe Text,
+        -- | True, if the bot can be invited to groups. Returned only in getMe
+        canJoinGroups :: Maybe Bool,
+        -- | True, if privacy mode is disabled for the bot. Returned only in getMe
+        canReadAllGroupMessages :: Maybe Bool,
+        -- | True, if the bot supports inline queries. Returned only in getMe.
+        supportsInlineQueries :: Maybe Bool
+      }
+  deriving (Show, Eq, Generic, Default)
+  deriving
+    (FromJSON, ToJSON)
+    via PrefixedSnake' "user" User
+  deriving (ToHttpApiData) via Serialize User
diff --git a/src/Web/Telegram/Types/Internal/Utils.hs b/src/Web/Telegram/Types/Internal/Utils.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Telegram/Types/Internal/Utils.hs
@@ -0,0 +1,19 @@
+module Web.Telegram.Types.Internal.Utils
+  ( module Web.Telegram.Types.Internal.Utils.Default,
+    module Web.Telegram.Types.Internal.Utils.Stock,
+    Serialize (..),
+  )
+where
+
+import Data.Aeson
+import Data.Aeson.Text
+import Data.Text.Lazy
+import Servant.API
+import Web.Telegram.Types.Internal.Utils.Default
+import Web.Telegram.Types.Internal.Utils.Stock
+
+-- | wrapper for serializing
+newtype Serialize a = Serialize a
+
+instance (ToJSON a) => ToHttpApiData (Serialize a) where
+  toQueryParam (Serialize a) = toStrict $ encodeToLazyText a
diff --git a/src/Web/Telegram/Types/Internal/Utils/Default.hs b/src/Web/Telegram/Types/Internal/Utils/Default.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Telegram/Types/Internal/Utils/Default.hs
@@ -0,0 +1,87 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DefaultSignatures #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeOperators #-}
+
+module Web.Telegram.Types.Internal.Utils.Default where
+
+import Data.ByteString.Lazy (ByteString)
+import Data.Dynamic
+import Data.Int
+import Data.OpenUnion
+import Data.Text (Text)
+import GHC.Generics
+
+class GDefault f where
+  gdef :: f a
+
+instance GDefault U1 where
+  gdef = U1
+
+instance (Default a) => GDefault (K1 i a) where
+  gdef = K1 def
+
+instance (GDefault a, GDefault b) => GDefault (a :*: b) where
+  gdef = gdef :*: gdef
+
+instance (GDefault a) => GDefault (M1 i c a) where
+  gdef = M1 gdef
+
+instance (GDefault a, GDefault b) => GDefault (a :+: b) where
+  gdef = L1 gdef
+
+-- | A class for types with a default value.
+class Default a where
+  def :: a
+  default def :: (Generic a, GDefault (Rep a)) => a
+  def = to gdef
+
+instance Default (Maybe a) where
+  def = Nothing
+
+instance Default () where
+  def = ()
+
+instance Default Bool where
+  def = False
+
+instance Default Int where
+  def = 0
+
+instance Default Int8 where
+  def = 0
+
+instance Default Int16 where
+  def = 0
+
+instance Default Int32 where
+  def = 0
+
+instance Default Int64 where
+  def = 0
+
+instance Default Integer where
+  def = 0
+
+instance Default Float where
+  def = 0
+
+instance Default Double where
+  def = 0
+
+instance Default Text where
+  def = mempty
+
+instance Default ByteString where
+  def = mempty
+
+instance Default a => Default (Either a b) where
+  def = Left def
+
+instance Default [a] where
+  def = mempty
+
+instance (Default a, Typeable a) => Default (Union (a ': as)) where
+  def = liftUnion (def :: a)
diff --git a/src/Web/Telegram/Types/Internal/Utils/Stock.hs b/src/Web/Telegram/Types/Internal/Utils/Stock.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Telegram/Types/Internal/Utils/Stock.hs
@@ -0,0 +1,48 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE PolyKinds #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications #-}
+
+module Web.Telegram.Types.Internal.Utils.Stock
+  ( Snake,
+    OmitNothing,
+    UntaggedSum,
+    PrefixedSnake,
+    Prefixed,
+    PrefixedSnake',
+  )
+where
+
+import Control.Monad
+import Data.Char (isLower)
+import Data.List (stripPrefix)
+import Data.Maybe
+  ( fromMaybe,
+    listToMaybe,
+  )
+import Data.Proxy
+import Deriving.Aeson
+import GHC.TypeLits
+
+type Snake = CustomJSON '[FieldLabelModifier CamelToSnake, OmitNothingFields]
+
+type OmitNothing = CustomJSON '[OmitNothingFields]
+
+type UntaggedSum = CustomJSON '[SumUntaggedValue, OmitNothingFields]
+
+type PrefixedSnake str = CustomJSON '[FieldLabelModifier (StripPrefix str, CamelToSnake), OmitNothingFields]
+
+type Prefixed str = CustomJSON '[FieldLabelModifier (StripPrefix str), OmitNothingFields]
+
+data StrictStrip t
+
+strictStrip :: String -> String -> Maybe String
+strictStrip pre s = do
+  t <- stripPrefix pre s
+  guard $ maybe False (not . isLower) $ listToMaybe t
+  return t
+
+instance KnownSymbol k => StringModifier (StrictStrip k) where
+  getStringModifier = fromMaybe <*> strictStrip (symbolVal (Proxy @k))
+
+type PrefixedSnake' str = CustomJSON '[FieldLabelModifier (StrictStrip str, CamelToSnake), OmitNothingFields]
diff --git a/src/Web/Telegram/Types/Lens.hs b/src/Web/Telegram/Types/Lens.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Telegram/Types/Lens.hs
@@ -0,0 +1,408 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE TypeApplications #-}
+
+module Web.Telegram.Types.Lens {-# DEPRECATED "Use labels from generics-lens instead" #-}  where
+
+import Control.Lens
+import Data.Generics.Product
+
+userId :: HasField "userId" s t a b => Lens s t a b
+userId = field @"userId"
+
+isBot :: HasField "isBot" s t a b => Lens s t a b
+isBot = field @"isBot"
+
+firstName :: HasField "firstName" s t a b => Lens s t a b
+firstName = field @"firstName"
+
+lastName :: HasField "lastName" s t a b => Lens s t a b
+lastName = field @"lastName"
+
+userName :: HasField "userName" s t a b => Lens s t a b
+userName = field @"userName"
+
+languageCode :: HasField "languageCode" s t a b => Lens s t a b
+languageCode = field @"languageCode"
+
+canJoinGroups :: HasField "canJoinGroups" s t a b => Lens s t a b
+canJoinGroups = field @"canJoinGroups"
+
+canReadAllGroupMessages :: HasField "canReadAllGroupMessages" s t a b => Lens s t a b
+canReadAllGroupMessages = field @"canReadAllGroupMessages"
+
+supportsInlineQueries :: HasField "supportsInlineQueries" s t a b => Lens s t a b
+supportsInlineQueries = field @"supportsInlineQueries"
+
+metadata :: HasField "metadata" s t a b => Lens s t a b
+metadata = field @"metadata"
+
+content :: HasField "content" s t a b => Lens s t a b
+content = field @"content"
+
+messageId :: HasField "messageId" s t a b => Lens s t a b
+messageId = field @"messageId"
+
+from :: HasField "from" s t a b => Lens s t a b
+from = field @"from"
+
+date :: HasField "date" s t a b => Lens s t a b
+date = field @"date"
+
+chat :: HasField "chat" s t a b => Lens s t a b
+chat = field @"chat"
+
+forwardFrom :: HasField "forwardFrom" s t a b => Lens s t a b
+forwardFrom = field @"forwardFrom"
+
+forwardFromChat :: HasField "forwardFromChat" s t a b => Lens s t a b
+forwardFromChat = field @"forwardFromChat"
+
+forwardFromMessageId :: HasField "forwardFromMessageId" s t a b => Lens s t a b
+forwardFromMessageId = field @"forwardFromMessageId"
+
+forwardSignature :: HasField "forwardSignature" s t a b => Lens s t a b
+forwardSignature = field @"forwardSignature"
+
+forwardSenderName :: HasField "forwardSenderName" s t a b => Lens s t a b
+forwardSenderName = field @"forwardSenderName"
+
+forwardDate :: HasField "forwardDate" s t a b => Lens s t a b
+forwardDate = field @"forwardDate"
+
+replyToMessage :: HasField "replyToMessage" s t a b => Lens s t a b
+replyToMessage = field @"replyToMessage"
+
+editDate :: HasField "editDate" s t a b => Lens s t a b
+editDate = field @"editDate"
+
+mediaGroupId :: HasField "mediaGroupId" s t a b => Lens s t a b
+mediaGroupId = field @"mediaGroupId"
+
+authorSignature :: HasField "authorSignature" s t a b => Lens s t a b
+authorSignature = field @"authorSignature"
+
+replyMarkup :: HasField "replyMarkup" s t a b => Lens s t a b
+replyMarkup = field @"replyMarkup"
+
+text :: HasField "text" s t a b => Lens s t a b
+text = field @"text"
+
+entities :: HasField "entities" s t a b => Lens s t a b
+entities = field @"entities"
+
+audio :: HasField "audio" s t a b => Lens s t a b
+audio = field @"audio"
+
+document :: HasField "document" s t a b => Lens s t a b
+document = field @"document"
+
+animation :: HasField "animation" s t a b => Lens s t a b
+animation = field @"animation"
+
+game :: HasField "game" s t a b => Lens s t a b
+game = field @"game"
+
+photo :: HasField "photo" s t a b => Lens s t a b
+photo = field @"photo"
+
+sticker :: HasField "sticker" s t a b => Lens s t a b
+sticker = field @"sticker"
+
+video :: HasField "video" s t a b => Lens s t a b
+video = field @"video"
+
+voice :: HasField "voice" s t a b => Lens s t a b
+voice = field @"voice"
+
+videoNote :: HasField "videoNote" s t a b => Lens s t a b
+videoNote = field @"videoNote"
+
+contact :: HasField "contact" s t a b => Lens s t a b
+contact = field @"contact"
+
+location :: HasField "location" s t a b => Lens s t a b
+location = field @"location"
+
+venue :: HasField "venue" s t a b => Lens s t a b
+venue = field @"venue"
+
+poll :: HasField "poll" s t a b => Lens s t a b
+poll = field @"poll"
+
+newChatMembers :: HasField "newChatMembers" s t a b => Lens s t a b
+newChatMembers = field @"newChatMembers"
+
+leftChatMember :: HasField "leftChatMember" s t a b => Lens s t a b
+leftChatMember = field @"leftChatMember"
+
+newChatPhoto :: HasField "newChatPhoto" s t a b => Lens s t a b
+newChatPhoto = field @"newChatPhoto"
+
+deleteChatPhoto :: HasField "deleteChatPhoto" s t a b => Lens s t a b
+deleteChatPhoto = field @"deleteChatPhoto"
+
+groupChatCreated :: HasField "groupChatCreated" s t a b => Lens s t a b
+groupChatCreated = field @"groupChatCreated"
+
+supergroupChatCreated :: HasField "supergroupChatCreated" s t a b => Lens s t a b
+supergroupChatCreated = field @"supergroupChatCreated"
+
+channelChatCreated :: HasField "channelChatCreated" s t a b => Lens s t a b
+channelChatCreated = field @"channelChatCreated"
+
+migrateToChatId :: HasField "migrateToChatId" s t a b => Lens s t a b
+migrateToChatId = field @"migrateToChatId"
+
+migrateFromChatId :: HasField "migrateFromChatId" s t a b => Lens s t a b
+migrateFromChatId = field @"migrateFromChatId"
+
+pinnedMessage :: HasField "pinnedMessage" s t a b => Lens s t a b
+pinnedMessage = field @"pinnedMessage"
+
+invoice :: HasField "invoice" s t a b => Lens s t a b
+invoice = field @"invoice"
+
+successfulPayment :: HasField "successfulPayment" s t a b => Lens s t a b
+successfulPayment = field @"successfulPayment"
+
+connectedWebsite :: HasField "connectedWebsite" s t a b => Lens s t a b
+connectedWebsite = field @"connectedWebsite"
+
+passPortData :: HasField "passPortData" s t a b => Lens s t a b
+passPortData = field @"passPortData"
+
+caption :: HasField "caption" s t a b => Lens s t a b
+caption = field @"caption"
+
+captionEntities :: HasField "captionEntities" s t a b => Lens s t a b
+captionEntities = field @"captionEntities"
+
+chatId :: HasField "chatId" s t a b => Lens s t a b
+chatId = field @"chatId"
+
+chatType :: HasField "chatType" s t a b => Lens s t a b
+chatType = field @"chatType"
+
+title :: HasField "title" s t a b => Lens s t a b
+title = field @"title"
+
+username :: HasField "username" s t a b => Lens s t a b
+username = field @"username"
+
+description :: HasField "description" s t a b => Lens s t a b
+description = field @"description"
+
+inviteLink :: HasField "inviteLink" s t a b => Lens s t a b
+inviteLink = field @"inviteLink"
+
+permissions :: HasField "permissions" s t a b => Lens s t a b
+permissions = field @"permissions"
+
+slowModeDelay :: HasField "slowModeDelay" s t a b => Lens s t a b
+slowModeDelay = field @"slowModeDelay"
+
+stickerSetName :: HasField "stickerSetName" s t a b => Lens s t a b
+stickerSetName = field @"stickerSetName"
+
+canSetStickerSet :: HasField "canSetStickerSet" s t a b => Lens s t a b
+canSetStickerSet = field @"canSetStickerSet"
+
+canSendMessages :: HasField "canSendMessages" s t a b => Lens s t a b
+canSendMessages = field @"canSendMessages"
+
+canSendMediaMessages :: HasField "canSendMediaMessages" s t a b => Lens s t a b
+canSendMediaMessages = field @"canSendMediaMessages"
+
+canSendPolls :: HasField "canSendPolls" s t a b => Lens s t a b
+canSendPolls = field @"canSendPolls"
+
+canSendOtherMesssages :: HasField "canSendOtherMesssages" s t a b => Lens s t a b
+canSendOtherMesssages = field @"canSendOtherMesssages"
+
+canAddWebPagePreviews :: HasField "canAddWebPagePreviews" s t a b => Lens s t a b
+canAddWebPagePreviews = field @"canAddWebPagePreviews"
+
+canChangeInfo :: HasField "canChangeInfo" s t a b => Lens s t a b
+canChangeInfo = field @"canChangeInfo"
+
+canInviteUsers :: HasField "canInviteUsers" s t a b => Lens s t a b
+canInviteUsers = field @"canInviteUsers"
+
+canPinMessages :: HasField "canPinMessages" s t a b => Lens s t a b
+canPinMessages = field @"canPinMessages"
+
+smallFileId :: HasField "smallFileId" s t a b => Lens s t a b
+smallFileId = field @"smallFileId"
+
+smallFileUniqueId :: HasField "smallFileUniqueId" s t a b => Lens s t a b
+smallFileUniqueId = field @"smallFileUniqueId"
+
+bigFileId :: HasField "bigFileId" s t a b => Lens s t a b
+bigFileId = field @"bigFileId"
+
+bitFileUniqueId :: HasField "bitFileUniqueId" s t a b => Lens s t a b
+bitFileUniqueId = field @"bitFileUniqueId"
+
+user :: HasField "user" s t a b => Lens s t a b
+user = field @"user"
+
+status :: HasField "status" s t a b => Lens s t a b
+status = field @"status"
+
+customTitle :: HasField "customTitle" s t a b => Lens s t a b
+customTitle = field @"customTitle"
+
+untilDate :: HasField "untilDate" s t a b => Lens s t a b
+untilDate = field @"untilDate"
+
+canBeEdited :: HasField "canBeEdited" s t a b => Lens s t a b
+canBeEdited = field @"canBeEdited"
+
+canPostMessages :: HasField "canPostMessages" s t a b => Lens s t a b
+canPostMessages = field @"canPostMessages"
+
+canEditMessages :: HasField "canEditMessages" s t a b => Lens s t a b
+canEditMessages = field @"canEditMessages"
+
+canDeleteMessages :: HasField "canDeleteMessages" s t a b => Lens s t a b
+canDeleteMessages = field @"canDeleteMessages"
+
+canRestrictMembers :: HasField "canRestrictMembers" s t a b => Lens s t a b
+canRestrictMembers = field @"canRestrictMembers"
+
+canPromoteMembers :: HasField "canPromoteMembers" s t a b => Lens s t a b
+canPromoteMembers = field @"canPromoteMembers"
+
+isMember :: HasField "isMember" s t a b => Lens s t a b
+isMember = field @"isMember"
+
+fileId :: HasField "fileId" s t a b => Lens s t a b
+fileId = field @"fileId"
+
+fileUniqueId :: HasField "fileUniqueId" s t a b => Lens s t a b
+fileUniqueId = field @"fileUniqueId"
+
+fileSize :: HasField "fileSize" s t a b => Lens s t a b
+fileSize = field @"fileSize"
+
+width :: HasField "width" s t a b => Lens s t a b
+width = field @"width"
+
+height :: HasField "height" s t a b => Lens s t a b
+height = field @"height"
+
+duration :: HasField "duration" s t a b => Lens s t a b
+duration = field @"duration"
+
+performer :: HasField "performer" s t a b => Lens s t a b
+performer = field @"performer"
+
+mimeType :: HasField "mimeType" s t a b => Lens s t a b
+mimeType = field @"mimeType"
+
+thumb :: HasField "thumb" s t a b => Lens s t a b
+thumb = field @"thumb"
+
+phoneNumber :: HasField "phoneNumber" s t a b => Lens s t a b
+phoneNumber = field @"phoneNumber"
+
+vcard :: HasField "vcard" s t a b => Lens s t a b
+vcard = field @"vcard"
+
+longitude :: HasField "longitude" s t a b => Lens s t a b
+longitude = field @"longitude"
+
+latitude :: HasField "latitude" s t a b => Lens s t a b
+latitude = field @"latitude"
+
+address :: HasField "address" s t a b => Lens s t a b
+address = field @"address"
+
+foursquareId :: HasField "foursquareId" s t a b => Lens s t a b
+foursquareId = field @"foursquareId"
+
+foursquareType :: HasField "foursquareType" s t a b => Lens s t a b
+foursquareType = field @"foursquareType"
+
+voterCount :: HasField "voterCount" s t a b => Lens s t a b
+voterCount = field @"voterCount"
+
+pollId :: HasField "pollId" s t a b => Lens s t a b
+pollId = field @"pollId"
+
+question :: HasField "question" s t a b => Lens s t a b
+question = field @"question"
+
+options :: HasField "options" s t a b => Lens s t a b
+options = field @"options"
+
+totalVoterCount :: HasField "totalVoterCount" s t a b => Lens s t a b
+totalVoterCount = field @"totalVoterCount"
+
+isClosed :: HasField "isClosed" s t a b => Lens s t a b
+isClosed = field @"isClosed"
+
+isAnonymous :: HasField "isAnonymous" s t a b => Lens s t a b
+isAnonymous = field @"isAnonymous"
+
+pollType :: HasField "pollType" s t a b => Lens s t a b
+pollType = field @"pollType"
+
+allowsMultipleAnswers :: HasField "allowsMultipleAnswers" s t a b => Lens s t a b
+allowsMultipleAnswers = field @"allowsMultipleAnswers"
+
+correctOptionId :: HasField "correctOptionId" s t a b => Lens s t a b
+correctOptionId = field @"correctOptionId"
+
+optionIds :: HasField "optionIds" s t a b => Lens s t a b
+optionIds = field @"optionIds"
+
+totalCount :: HasField "totalCount" s t a b => Lens s t a b
+totalCount = field @"totalCount"
+
+photos :: HasField "photos" s t a b => Lens s t a b
+photos = field @"photos"
+
+filePath :: HasField "filePath" s t a b => Lens s t a b
+filePath = field @"filePath"
+
+isAnimated :: HasField "isAnimated" s t a b => Lens s t a b
+isAnimated = field @"isAnimated"
+
+emoji :: HasField "emoji" s t a b => Lens s t a b
+emoji = field @"emoji"
+
+setName :: HasField "setName" s t a b => Lens s t a b
+setName = field @"setName"
+
+maskPosition :: HasField "maskPosition" s t a b => Lens s t a b
+maskPosition = field @"maskPosition"
+
+containsMasks :: HasField "containsMasks" s t a b => Lens s t a b
+containsMasks = field @"containsMasks"
+
+stickers :: HasField "stickers" s t a b => Lens s t a b
+stickers = field @"stickers"
+
+name :: HasField "name" s t a b => Lens s t a b
+name = field @"name"
+
+point :: HasField "point" s t a b => Lens s t a b
+point = field @"point"
+
+xShift :: HasField "xShift" s t a b => Lens s t a b
+xShift = field @"xShift"
+
+yShift :: HasField "yShift" s t a b => Lens s t a b
+yShift = field @"yShift"
+
+scale :: HasField "scale" s t a b => Lens s t a b
+scale = field @"scale"
+
+retryAfter :: HasField "retryAfter" s t a b => Lens s t a b
+retryAfter = field @"retryAfter"
+
+command :: HasField "command" s t a b => Lens s t a b
+command = field @"command"
+
diff --git a/src/Web/Telegram/Types/Passport.hs b/src/Web/Telegram/Types/Passport.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Telegram/Types/Passport.hs
@@ -0,0 +1,11 @@
+module Web.Telegram.Types.Passport
+  ( PassportData (..),
+    PassportFile (..),
+    EncryptedPassportElement (..),
+    EncryptedPassportElementType (..),
+    EncryptedCredentials (..),
+    PassportElementError (..),
+  )
+where
+
+import Web.Telegram.Types.Internal.Passport
diff --git a/src/Web/Telegram/Types/Stock.hs b/src/Web/Telegram/Types/Stock.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Telegram/Types/Stock.hs
@@ -0,0 +1,6 @@
+module Web.Telegram.Types.Stock
+  ( module Web.Telegram.Types.Internal.Utils.Stock,
+  )
+where
+
+import Web.Telegram.Types.Internal.Utils.Stock
diff --git a/src/Web/Telegram/Types/Update.hs b/src/Web/Telegram/Types/Update.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Telegram/Types/Update.hs
@@ -0,0 +1,14 @@
+module Web.Telegram.Types.Update
+  ( Update (..),
+    WebhookInfo (..),
+
+    -- ** Response
+    ResponseParameters (..),
+    ReqResult (..),
+    ReqEither (..),
+    BotCommand (..),
+  )
+where
+
+import Web.Telegram.Types.Internal.Update
+import Web.Telegram.Types.Internal.Common
diff --git a/src/Web/Telegram/Types/UpdateType.hs b/src/Web/Telegram/Types/UpdateType.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Telegram/Types/UpdateType.hs
@@ -0,0 +1,6 @@
+module Web.Telegram.Types.UpdateType
+  ( module Web.Telegram.Types.Internal.UpdateType,
+  )
+where
+
+import Web.Telegram.Types.Internal.UpdateType
diff --git a/telegram-types.cabal b/telegram-types.cabal
new file mode 100644
--- /dev/null
+++ b/telegram-types.cabal
@@ -0,0 +1,95 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.33.0.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: a4fed3a03d8478f0f467cbd86c8dc1ccdb4b4f777198571ebaecc78e4dfeaf38
+
+name:           telegram-types
+version:        0.1.0
+synopsis:       Types used in Telegram bot API
+description:    Defines various datatypes and their serialization methods useful for writing bindings to Telegram bot API.
+category:       Web
+homepage:       https://github.com/poscat0x04/telegram-types#readme
+bug-reports:    https://github.com/poscat0x04/telegram-types/issues
+author:         Poscat
+maintainer:     poscat@mail.poscat.moe
+copyright:      2020 Poscat
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+
+source-repository head
+  type: git
+  location: https://github.com/poscat0x04/telegram-types
+
+library
+  exposed-modules:
+      Web.Telegram.Types
+      Web.Telegram.Types.Lens
+      Web.Telegram.Types.Stock
+      Web.Telegram.Types.Interaction
+      Web.Telegram.Types.Inline
+      Web.Telegram.Types.Input
+      Web.Telegram.Types.Update
+      Web.Telegram.Types.UpdateType
+  other-modules:
+      Web.Telegram.Types.Internal.Common
+      Web.Telegram.Types.Internal.InlineQuery
+      Web.Telegram.Types.Internal.InputFile
+      Web.Telegram.Types.Internal.InputMedia
+      Web.Telegram.Types.Internal.Keyboard
+      Web.Telegram.Types.Internal.Media
+      Web.Telegram.Types.Internal.Passport
+      Web.Telegram.Types.Internal.Sticker
+      Web.Telegram.Types.Internal.Update
+      Web.Telegram.Types.Internal.UpdateType
+      Web.Telegram.Types.Internal.User
+      Web.Telegram.Types.Internal.Utils
+      Web.Telegram.Types.Internal.Utils.Default
+      Web.Telegram.Types.Internal.Utils.Stock
+      Web.Telegram.Types.Passport
+      Paths_telegram_types
+  hs-source-dirs:
+      src
+  build-depends:
+      aeson >=1.4.7.1 && <1.5
+    , base >=4.7 && <5
+    , bytestring >=0.10.10.0 && <0.11
+    , deriving-aeson >=0.2.3 && <0.3
+    , filepath >=1.4.2.1 && <1.5
+    , generic-lens >=1.2.0.1 && <1.3
+    , lens >=4.18.1 && <4.19
+    , mime-types >=0.1.0.9 && <0.2
+    , open-union >=0.4.0.0 && <0.5
+    , servant >=0.16.2 && <0.17
+    , servant-multipart >=0.11.5 && <0.12
+    , text >=1.2.4.0 && <1.3
+  default-language: Haskell2010
+
+test-suite telegram-types-test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  other-modules:
+      Paths_telegram_types
+  hs-source-dirs:
+      test
+  ghc-options: -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      aeson >=1.4.7.1 && <1.5
+    , base >=4.7 && <5
+    , bytestring >=0.10.10.0 && <0.11
+    , deriving-aeson >=0.2.3 && <0.3
+    , filepath >=1.4.2.1 && <1.5
+    , generic-lens >=1.2.0.1 && <1.3
+    , lens >=4.18.1 && <4.19
+    , mime-types >=0.1.0.9 && <0.2
+    , open-union >=0.4.0.0 && <0.5
+    , servant >=0.16.2 && <0.17
+    , servant-multipart >=0.11.5 && <0.12
+    , telegram-types
+    , text >=1.2.4.0 && <1.3
+  default-language: Haskell2010
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,13 @@
+module Main where
+
+import Data.Aeson
+import qualified Data.ByteString.Lazy as LBS
+import Web.Telegram.Types.Input
+import Web.Telegram.Types.Update
+import Web.Telegram.Types
+
+main :: IO ()
+main = do
+  f <- LBS.readFile "test/Message1"
+  print $ (eitherDecode f :: Either String Update)
+  LBS.putStrLn $ encode (def :: InputMedia)
