Repository: slack-go/slack
Pull request #1201 (2023-05-17 18:17:03)

docs: Add an example demonstrating correct usage of GetUsersPaginated
I had a really hard time finding an example of how `(*Client).GetUsersPaginated()` is intended to be used. After debugging my own implementation for the better part of a day, I thought it would be nice to add an official example.


Patch:
diff --git a/.gitignore b/.gitignore
index ac6f3ee..027f4de 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 *.test
 *~
 .idea/
+/vendor/
\ No newline at end of file
diff --git a/apps.go b/apps.go
index 10d4297..7322b15 100644
--- a/apps.go
+++ b/apps.go
@@ -19,11 +19,15 @@ type EventAuthorization struct {
 	IsEnterpriseInstall bool   `json:"is_enterprise_install"`
 }
 
+// ListEventAuthorizations lists authed users and teams for the given event_context.
+// You must provide an app-level token to the client using OptionAppLevelToken.
+// For more details, see ListEventAuthorizationsContext documentation.
 func (api *Client) ListEventAuthorizations(eventContext string) ([]EventAuthorization, error) {
 	return api.ListEventAuthorizationsContext(context.Background(), eventContext)
 }
 
-// ListEventAuthorizationsContext lists authed users and teams for the given event_context. You must provide an app-level token to the client using OptionAppLevelToken. More info: https://api.slack.com/methods/apps.event.authorizations.list
+// ListEventAuthorizationsContext lists authed users and teams for the given event_context with a custom context.
+// Slack API docs: https://api.slack.com/methods/apps.event.authorizations.list
 func (api *Client) ListEventAuthorizationsContext(ctx context.Context, eventContext string) ([]EventAuthorization, error) {
 	resp := &listEventAuthorizationsResponse{}
 
@@ -43,10 +47,14 @@ func (api *Client) ListEventAuthorizationsContext(ctx context.Context, eventCont
 	return resp.Authorizations, nil
 }
 
+// UninstallApp uninstalls your app from a workspace.
+// For more details, see UninstallAppContext documentation.
 func (api *Client) UninstallApp(clientID, clientSecret string) error {
 	return api.UninstallAppContext(context.Background(), clientID, clientSecret)
 }
 
+// UninstallAppContext uninstalls your app from a workspace with a custom context.
+// Slack API docs: https://api.slack.com/methods/apps.uninstall
 func (api *Client) UninstallAppContext(ctx context.Context, clientID, clientSecret string) error {
 	values := url.Values{
 		"client_id":     {clientID},
diff --git a/auth.go b/auth.go
index bf6e80d..7fff1a1 100644
--- a/auth.go
+++ b/auth.go
@@ -22,12 +22,14 @@ func (api *Client) authRequest(ctx context.Context, path string, values url.Valu
 	return response, response.Err()
 }
 
-// SendAuthRevoke will send a revocation for our token
+// SendAuthRevoke will send a revocation for our token.
+// For more details, see SendAuthRevokeContext documentation.
 func (api *Client) SendAuthRevoke(token string) (*AuthRevokeResponse, error) {
 	return api.SendAuthRevokeContext(context.Background(), token)
 }
 
-// SendAuthRevokeContext will send a revocation request for our token to api.revoke with context
+// SendAuthRevokeContext will send a revocation request for our token to api.revoke with a custom context.
+// Slack API docs: https://api.slack.com/methods/auth.revoke
 func (api *Client) SendAuthRevokeContext(ctx context.Context, token string) (*AuthRevokeResponse, error) {
 	if token == "" {
 		token = api.token
@@ -50,12 +52,13 @@ type ListTeamsParameters struct {
 }
 
 // ListTeams returns all workspaces a token can access.
-// More info: https://api.slack.com/methods/admin.teams.list
+// For more details, see ListTeamsContext documentation.
 func (api *Client) ListTeams(params ListTeamsParameters) ([]Team, string, error) {
 	return api.ListTeamsContext(context.Background(), params)
 }
 
-// ListTeams returns all workspaces a token can access with a custom context.
+// ListTeamsContext returns all workspaces a token can access with a custom context.
+// Slack API docs: https://api.slack.com/methods/auth.teams.list
 func (api *Client) ListTeamsContext(ctx context.Context, params ListTeamsParameters) ([]Team, string, error) {
 	values := url.Values{
 		"token": {api.token},
diff --git a/block.go b/block.go
index 18222bd..1d6ba2d 100644
--- a/block.go
+++ b/block.go
@@ -18,6 +18,7 @@ const (
 	MBTInput    MessageBlockType = "input"
 	MBTHeader   MessageBlockType = "header"
 	MBTRichText MessageBlockType = "rich_text"
+	MBTVideo    MessageBlockType = "video"
 )
 
 // Block defines an interface all block types should implement
@@ -39,6 +40,7 @@ type BlockAction struct {
 	Type                  ActionType          `json:"type"`
 	Text                  TextBlockObject     `json:"text"`
 	Value                 string              `json:"value"`
+	Files                 []File              `json:"files"`
 	ActionTs              string              `json:"action_ts"`
 	SelectedOption        OptionBlockObject   `json:"selected_option"`
 	SelectedOptions       []OptionBlockObject `json:"selected_options"`
diff --git a/block_conv.go b/block_conv.go
index 4ab58de..7570be2 100644
--- a/block_conv.go
+++ b/block_conv.go
@@ -2,7 +2,6 @@ package slack
 
 import (
 	"encoding/json"
-	"errors"
 	"fmt"
 )
 
@@ -66,8 +65,12 @@ func (b *Blocks) UnmarshalJSON(data []byte) error {
 			block = &InputBlock{}
 		case "rich_text":
 			block = &RichTextBlock{}
+		case "rich_text_input":
+			block = &RichTextBlock{}
 		case "section":
 			block = &SectionBlock{}
+		case "video":
+			block = &VideoBlock{}
 		default:
 			block = &UnknownBlock{}
 		}
@@ -114,6 +117,8 @@ func (b *InputBlock) UnmarshalJSON(data []byte) error {
 		e = &DateTimePickerBlockElement{}
 	case "plain_text_input":
 		e = &PlainTextInputBlockElement{}
+	case "rich_text_input":
+		e = &RichTextInputBlockElement{}
 	case "email_text_input":
 		e = &EmailTextInputBlockElement{}
 	case "url_text_input":
@@ -130,8 +135,10 @@ func (b *InputBlock) UnmarshalJSON(data []byte) error {
 		e = &RadioButtonsBlockElement{}
 	case "number_input":
 		e = &NumberInputBlockElement{}
+	case "file_input":
+		e = &FileInputBlockElement{}
 	default:
-		return errors.New("unsupported block element type")
+		return fmt.Errorf("unsupported block element type %v", s.TypeVal)
 	}
 
 	if err := json.Unmarshal(a.Element, e); err != nil {
@@ -196,6 +203,8 @@ func (b *BlockElements) UnmarshalJSON(data []byte) error {
 			blockElement = &DateTimePickerBlockElement{}
 		case "plain_text_input":
 			blockElement = &PlainTextInputBlockElement{}
+		case "rich_text_input":
+			blockElement = &RichTextInputBlockElement{}
 		case "email_text_input":
 			blockElement = &EmailTextInputBlockElement{}
 		case "url_text_input":
@@ -298,6 +307,12 @@ func (a *Accessory) UnmarshalJSON(data []byte) error {
 			return err
 		}
 		a.PlainTextInputElement = element.(*PlainTextInputBlockElement)
+	case "rich_text_input":
+		element, err := unmarshalBlockElement(r, &RichTextInputBlockElement{})
+		if err != nil {
+			return err
+		}
+		a.RichTextInputElement = element.(*RichTextInputBlockElement)
 	case "radio_buttons":
 		element, err := unmarshalBlockElement(r, &RadioButtonsBlockElement{})
 		if err != nil {
@@ -429,7 +444,7 @@ func (e *ContextElements) UnmarshalJSON(data []byte) error {
 
 			e.Elements = append(e.Elements, elem.(*ImageBlockElement))
 		default:
-			return errors.New("unsupported context element type")
+			return fmt.Errorf("unsupported context element type %v", contextElementType)
 		}
 	}
 
diff --git a/block_element.go b/block_element.go
index a70d8f2..a2b755b 100644
--- a/block_element.go
+++ b/block_element.go
@@ -12,9 +12,11 @@ const (
 	METDatetimepicker MessageElementType = "datetimepicker"
 	METPlainTextInput MessageElementType = "plain_text_input"
 	METRadioButtons   MessageElementType = "radio_buttons"
+	METRichTextInput  MessageElementType = "rich_text_input"
 	METEmailTextInput MessageElementType = "email_text_input"
 	METURLTextInput   MessageElementType = "url_text_input"
 	METNumber         MessageElementType = "number_input"
+	METFileInput      MessageElementType = "file_input"
 
 	MixedElementImage MixedElementType = "mixed_image"
 	MixedElementText  MixedElementType = "mixed_text"
@@ -51,6 +53,7 @@ type Accessory struct {
 	DatePickerElement          *DatePickerBlockElement
 	TimePickerElement          *TimePickerBlockElement
 	PlainTextInputElement      *PlainTextInputBlockElement
+	RichTextInputElement       *RichTextInputBlockElement
 	RadioButtonsElement        *RadioButtonsBlockElement
 	SelectElement              *SelectBlockElement
 	MultiSelectElement         *MultiSelectBlockElement
@@ -73,6 +76,8 @@ func NewAccessory(element BlockElement) *Accessory {
 		return &Accessory{TimePickerElement: element.(*TimePickerBlockElement)}
 	case *PlainTextInputBlockElement:
 		return &Accessory{PlainTextInputElement: element.(*PlainTextInputBlockElement)}
+	case *RichTextInputBlockElement:
+		return &Accessory{RichTextInputElement: element.(*RichTextInputBlockElement)}
 	case *RadioButtonsBlockElement:
 		return &Accessory{RadioButtonsElement: element.(*RadioButtonsBlockElement)}
 	case *SelectBlockElement:
@@ -177,6 +182,12 @@ func (s *ButtonBlockElement) WithConfirm(confirm *ConfirmationBlockObject) *Butt
 	return s
 }
 
+// WithURL adds a URL for the button to link to and returns the modified ButtonBlockElement
+func (s *ButtonBlockElement) WithURL(url string) *ButtonBlockElement {
+	s.URL = url
+	return s
+}
+
 // NewButtonBlockElement returns an instance of a new button element to be used within a block
 func NewButtonBlockElement(actionID, value string, text *TextBlockObject) *ButtonBlockElement {
 	return &ButtonBlockElement{
@@ -509,6 +520,32 @@ func NewPlainTextInputBlockElement(placeholder *TextBlockObject, actionID string
 	}
 }
 
+// RichTextInputBlockElement creates a field where allows users to enter formatted text
+// in a WYSIWYG composer, offering the same messaging writing experience as in Slack
+// More Information: https://api.slack.com/reference/block-kit/block-elements#rich_text_input
+type RichTextInputBlockElement struct {
+	Type                 MessageElementType    `json:"type"`
+	ActionID             string                `json:"action_id,omitempty"`
+	Placeholder          *TextBlockObject      `json:"placeholder,omitempty"`
+	InitialValue         string                `json:"initial_value,omitempty"`
+	DispatchActionConfig *DispatchActionConfig `json:"dispatch_action_config,omitempty"`
+	FocusOnLoad          bool                  `json:"focus_on_load,omitempty"`
+}
+
+// ElementType returns the type of the Element
+func (s RichTextInputBlockElement) ElementType() MessageElementType {
+	return s.Type
+}
+
+// NewRichTextInputBlockElement returns an instance of a rich-text input element
+func NewRichTextInputBlockElement(placeholder *TextBlockObject, actionID string) *RichTextInputBlockElement {
+	return &RichTextInputBlockElement{
+		Type:        METRichTextInput,
+		ActionID:    actionID,
+		Placeholder: placeholder,
+	}
+}
+
 // CheckboxGroupsBlockElement defines an element which allows users to choose
 // one or more items from a list of possible options.
 //
@@ -591,3 +628,40 @@ func NewNumberInputBlockElement(placeholder *TextBlockObject, actionID string, i
 		IsDecimalAllowed: isDecimalAllowed,
 	}
 }
+
+// FileInputBlockElement creates a field where a user can upload a file.
+//
+// File input elements are currently only available in modals.
+//
+// More Information: https://api.slack.com/reference/block-kit/block-elements#file_input
+type FileInputBlockElement struct {
+	Type      MessageElementType `json:"type"`
+	ActionID  string             `json:"action_id,omitempty"`
+	FileTypes []string           `json:"filetypes,omitempty"`
+	MaxFiles  int                `json:"max_files,omitempty"`
+}
+
+// ElementType returns the type of the Element
+func (s FileInputBlockElement) ElementType() MessageElementType {
+	return s.Type
+}
+
+// NewFileInputBlockElement returns an instance of a file input element
+func NewFileInputBlockElement(actionID string) *FileInputBlockElement {
+	return &FileInputBlockElement{
+		Type:     METFileInput,
+		ActionID: actionID,
+	}
+}
+
+// WithFileTypes sets the file types that can be uploaded
+func (s *FileInputBlockElement) WithFileTypes(fileTypes ...string) *FileInputBlockElement {
+	s.FileTypes = fileTypes
+	return s
+}
+
+// WithMaxFiles sets the maximum number of files that can be uploaded
+func (s *FileInputBlockElement) WithMaxFiles(maxFiles int) *FileInputBlockElement {
+	s.MaxFiles = maxFiles
+	return s
+}
diff --git a/block_object.go b/block_object.go
index f70405e..a3e78d4 100644
--- a/block_object.go
+++ b/block_object.go
@@ -147,6 +147,16 @@ func (s TextBlockObject) Validate() error {
 		return errors.New("emoji cannot be true in mrkdown")
 	}
 
+	// https://api.slack.com/reference/block-kit/composition-objects#text__fields
+	if len(s.Text) == 0 {
+		return errors.New("text must have a minimum length of 1")
+	}
+
+	// https://api.slack.com/reference/block-kit/composition-objects#text__fields
+	if len(s.Text) > 3000 {
+		return errors.New("text cannot be longer than 3000 characters")
+	}
+
 	return nil
 }
 
@@ -177,7 +187,7 @@ type ConfirmationBlockObject struct {
 	Title   *TextBlockObject `json:"title"`
 	Text    *TextBlockObject `json:"text"`
 	Confirm *TextBlockObject `json:"confirm"`
-	Deny    *TextBlockObject `json:"deny"`
+	Deny    *TextBlockObject `json:"deny,omitempty"`
 	Style   Style            `json:"style,omitempty"`
 }
 
diff --git a/block_rich_text.go b/block_rich_text.go
index 01e5cdb..b6a4b4c 100644
--- a/block_rich_text.go
+++ b/block_rich_text.go
@@ -40,6 +40,12 @@ func (e *RichTextBlock) UnmarshalJSON(b []byte) error {
 		switch s.Type {
 		case RTESection:
 			elem = &RichTextSection{}
+		case RTEList:
+			elem = &RichTextList{}
+		case RTEQuote:
+			elem = &RichTextQuote{}
+		case RTEPreformatted:
+			elem = &RichTextPreformatted{}
 		default:
 			elems = append(elems, &RichTextUnknown{
 				Type: s.Type,
@@ -92,12 +98,92 @@ func (u RichTextUnknown) RichTextElementType() RichTextElementType {
 	return u.Type
 }
 
+type RichTextListElementType string
+
+const (
+	RTEListOrdered RichTextListElementType = "ordered"
+	RTEListBullet  RichTextListElementType = "bullet"
+)
+
+type RichTextList struct {
+	Type     RichTextElementType     `json:"type"`
+	Elements []RichTextElement       `json:"elements"`
+	Style    RichTextListElementType `json:"style"`
+	Indent   int                     `json:"indent"`
+}
+
+// NewRichTextList returns a new rich text list element.
+func NewRichTextList(style RichTextListElementType, indent int, elements ...RichTextElement) *RichTextList {
+	return &RichTextList{
+		Type:     RTEList,
+		Elements: elements,
+		Style:    style,
+		Indent:   indent,
+	}
+}
+
+// ElementType returns the type of the Element
+func (s RichTextList) RichTextElementType() RichTextElementType {
+	return s.Type
+}
+
+func (e *RichTextList) UnmarshalJSON(b []byte) error {
+	var raw struct {
+		RawElements []json.RawMessage       `json:"elements"`
+		Style       RichTextListElementType `json:"style"`
+		Indent      int                     `json:"indent"`
+	}
+	if string(b) == "{}" {
+		return nil
+	}
+	if err := json.Unmarshal(b, &raw); err != nil {
+		return err
+	}
+	elems := make([]RichTextElement, 0, len(raw.RawElements))
+	for _, r := range raw.RawElements {
+		var s struct {
+			Type RichTextElementType `json:"type"`
+		}
+		if err := json.Unmarshal(r, &s); err != nil {
+			return err
+		}
+		var elem RichTextElement
+		switch s.Type {
+		case RTESection:
+			elem = &RichTextSection{}
+		case RTEList:
+			elem = &RichTextList{}
+		case RTEQuote:
+			elem = &RichTextQuote{}
+		case RTEPreformatted:
+			elem = &RichTextPreformatted{}
+		default:
+			elems = append(elems, &RichTextUnknown{
+				Type: s.Type,
+				Raw:  string(r),
+			})
+			continue
+		}
+		if err := json.Unmarshal(r, elem); err != nil {
+			return err
+		}
+		elems = append(elems, elem)
+	}
+	*e = RichTextList{
+		Type:     RTEList,
+		Elements: elems,
+		Style:    raw.Style,
+		Indent:   raw.Indent,
+	}
+	return nil
+}
+
 type RichTextSection struct {
 	Type     RichTextElementType      `json:"type"`
 	Elements []RichTextSectionElement `json:"elements"`
 }
 
-// ElementType returns the type of the Element
+// RichTextElementType returns the type of the Element
 func (s RichTextSection) RichTextElementType() RichTextElementType {
 	return s.Type
 }
@@ -381,3 +467,62 @@ type RichTextSectionUnknownElement struct {
 func (r RichTextSectionUnknownElement) RichTextSectionElementType() RichTextSectionElementType {
 	return r.Type
 }
+
+// RichTextQuote represents rich_text_quote element type.
+type RichTextQuote RichTextSection
+
+// RichTextElementType returns the type of the Element
+func (s *RichTextQuote) RichTextElementType() RichTextElementType {
+	return s.Type
+}
+
+func (s *RichTextQuote) UnmarshalJSON(b []byte) error {
+	// reusing the RichTextSection struct, as it's the same as RichTextQuote.
+	var rts RichTextSection
+	if err := json.Unmarshal(b, &rts); err != nil {
+		return err
+	}
+	*s = RichTextQuote(rts)
+	s.Type = RTEQuote
+	return nil
+}
+
+// RichTextPreformatted represents rich_text_quote element type.
+type RichTextPreformatted struct {
+	RichTextSection
+	Border int `json:"border"`
+}
+
+// RichTextElementType returns the type of the Element
+func (s *RichTextPreformatted) RichTextElementType() RichTextElementType {
+	return s.Type
+}
+
+func (s *RichTextPreformatted) UnmarshalJSON(b []byte) error {
+	var rts RichTextSection
+	if err := json.Unmarshal(b, &rts); err != nil {
+		return err
+	}
+	// we define standalone fields because we need to unmarshal the border
+	// field.  We can not directly unmarshal the data into
+	// RichTextPreformatted because it will cause an infinite loop.  We also
+	// can not define a struct with embedded RichTextSection and Border fields
+	// because the json package will not unmarshal the data into the
+	// standalone fields, once it sees UnmarshalJSON method on the embedded
+	// struct.  The drawback is that we have to process the data twice, and
+	// have to define a standalone struct with the same set of fields as the
+	// original struct, which may become a maintenance burden (i.e. update the
+	// fields in two places, should it ever change).
+	var standalone struct {
+		Border int `json:"border"`
+	}
+	if err := json.Unmarshal(b, &standalone); err != nil {
+		return err
+	}
+	*s = RichTextPreformatted{
+		RichTextSection: rts,
+		Border:          standalone.Border,
+	}
+	s.Type = RTEPreformatted
+	return nil
+}
diff --git a/block_video.go b/block_video.go
new file mode 100644
index 0000000..322c614
--- /dev/null
+++ b/block_video.go
@@ -0,0 +1,65 @@
+package slack
+
+// VideoBlock defines data required to display a video as a block element
+//
+// More Information: https://api.slack.com/reference/block-kit/blocks#video
+type VideoBlock struct {
+	Type            MessageBlockType `json:"type"`
+	VideoURL        string           `json:"video_url"`
+	ThumbnailURL    string           `json:"thumbnail_url"`
+	AltText         string           `json:"alt_text"`
+	Title           *TextBlockObject `json:"title"`
+	BlockID         string           `json:"block_id,omitempty"`
+	TitleURL        string           `json:"title_url,omitempty"`
+	AuthorName      string           `json:"author_name,omitempty"`
+	ProviderName    string           `json:"provider_name,omitempty"`
+	ProviderIconURL string           `json:"provider_icon_url,omitempty"`
+	Description     *TextBlockObject `json:"description,omitempty"`
+}
+
+// BlockType returns the type of the block
+func (s VideoBlock) BlockType() MessageBlockType {
+	return s.Type
+}
+
+// NewVideoBlock returns an instance of a new Video Block type
+func NewVideoBlock(videoURL, thumbnailURL, altText, blockID string, title *TextBlockObject) *VideoBlock {
+	return &VideoBlock{
+		Type:         MBTVideo,
+		VideoURL:     videoURL,
+		ThumbnailURL: thumbnailURL,
+		AltText:      altText,
+		BlockID:      blockID,
+		Title:        title,
+	}
+}
+
+// WithAuthorName sets the author name for the VideoBlock
+func (s *VideoBlock) WithAuthorName(authorName string) *VideoBlock {
+	s.AuthorName = authorName
+	return s
+}
+
+// WithTitleURL sets the title URL for the VideoBlock
+func (s *VideoBlock) WithTitleURL(titleURL string) *VideoBlock {
+	s.TitleURL = titleURL
+	return s
+}
+
+// WithDescription sets the description for the VideoBlock
+func (s *VideoBlock) WithDescription(description *TextBlockObject) *VideoBlock {
+	s.Description = description
+	return s
+}
+
+// WithProviderIconURL sets the provider icon URL for the VideoBlock
+func (s *VideoBlock) WithProviderIconURL(providerIconURL string) *VideoBlock {
+	s.ProviderIconURL = providerIconURL
+	return s
+}
+
+// WithProviderName sets the provider name for the VideoBlock
+func (s *VideoBlock) WithProviderName(providerName string) *VideoBlock {
+	s.ProviderName = providerName
+	return s
+}
diff --git a/bookmarks.go b/bookmarks.go
index 7875350..1f07e59 100644
--- a/bookmarks.go
+++ b/bookmarks.go
@@ -55,12 +55,14 @@ type listBookmarksResponse struct {
 	SlackResponse
 }
 
-// AddBookmark adds a bookmark in a channel
+// AddBookmark adds a bookmark in a channel.
+// For more details, see AddBookmarkContext documentation.
 func (api *Client) AddBookmark(channelID string, params AddBookmarkParameters) (Bookmark, error) {
 	return api.AddBookmarkContext(context.Background(), channelID, params)
 }
 
-// AddBookmarkContext adds a bookmark in a channel with a custom context
+// AddBookmarkContext adds a bookmark in a channel with a custom context.
+// Slack API docs: https://api.slack.com/methods/bookmarks.add
 func (api *Client) AddBookmarkContext(ctx context.Context, channelID string, params AddBookmarkParameters) (Bookmark, error) {
 	values := url.Values{
 		"channel_id": {channelID},
@@ -89,12 +91,14 @@ func (api *Client) AddBookmarkContext(ctx context.Context, channelID string, par
 	return response.Bookmark, response.Err()
 }
 
-// RemoveBookmark removes a bookmark from a channel
+// RemoveBookmark removes a bookmark from a channel.
+// For more details, see RemoveBookmarkContext documentation.
 func (api *Client) RemoveBookmark(channelID, bookmarkID string) error {
 	return api.RemoveBookmarkContext(context.Background(), channelID, bookmarkID)
 }
 
-// RemoveBookmarkContext removes a bookmark from a channel with a custom context
+// RemoveBookmarkContext removes a bookmark from a channel with a custom context.
+// Slack API docs: https://api.slack.com/methods/bookmarks.remove
 func (api *Client) RemoveBookmarkContext(ctx context.Context, channelID, bookmarkID string) error {
 	values := url.Values{
 		"channel_id":  {channelID},
@@ -111,11 +115,13 @@ func (api *Client) RemoveBookmarkContext(ctx context.Context, channelID, bookmar
 }
 
 // ListBookmarks returns all bookmarks for a channel.
+// For more details, see ListBookmarksContext documentation.
 func (api *Client) ListBookmarks(channelID string) ([]Bookmark, error) {
 	return api.ListBookmarksContext(context.Background(), channelID)
 }
 
 // ListBookmarksContext returns all bookmarks for a channel with a custom context.
+// Slack API docs: https://api.slack.com/methods/bookmarks.edit
 func (api *Client) ListBookmarksContext(ctx context.Context, channelID string) ([]Bookmark, error) {
 	values := url.Values{
 		"channel_id": {channelID},
@@ -130,10 +136,14 @@ func (api *Client) ListBookmarksContext(ctx context.Context, channelID string) (
 	return response.Bookmarks, response.Err()
 }
 
+// EditBookmark edits a bookmark in a channel.
+// For more details, see EditBookmarkContext documentation.
 func (api *Client) EditBookmark(channelID, bookmarkID string, params EditBookmarkParameters) (Bookmark, error) {
 	return api.EditBookmarkContext(context.Background(), channelID, bookmarkID, params)
 }
 
+// EditBookmarkContext edits a bookmark in a channel with a custom context.
+// Slack API docs: https://api.slack.com/methods/bookmarks.edit
 func (api *Client) EditBookmarkContext(ctx context.Context, channelID, bookmarkID string, params EditBookmarkParameters) (Bookmark, error) {
 	values := url.Values{
 		"channel_id":  {channelID},
diff --git a/bots.go b/bots.go
index da21ba0..1ab9469 100644
--- a/bots.go
+++ b/bots.go
@@ -35,19 +35,30 @@ func (api *Client) botRequest(ctx context.Context, path string, values url.Value
 	return response, nil
 }
 
-// GetBotInfo will retrieve the complete bot information
-func (api *Client) GetBotInfo(bot string) (*Bot, error) {
-	return api.GetBotInfoContext(context.Background(), bot)
+type GetBotInfoParameters struct {
+	Bot    string
+	TeamID string
 }
 
-// GetBotInfoContext will retrieve the complete bot information using a custom context
-func (api *Client) GetBotInfoContext(ctx context.Context, bot string) (*Bot, error) {
+// GetBotInfo will retrieve the complete bot information.
+// For more details, see GetBotInfoContext documentation.
+func (api *Client) GetBotInfo(parameters GetBotInfoParameters) (*Bot, error) {
+	return api.GetBotInfoContext(context.Background(), parameters)
+}
+
+// GetBotInfoContext will retrieve the complete bot information using a custom context.
+// Slack API docs: https://api.slack.com/methods/bots.info
+func (api *Client) GetBotInfoContext(ctx context.Context, parameters GetBotInfoParameters) (*Bot, error) {
 	values := url.Values{
 		"token": {api.token},
 	}
 
-	if bot != "" {
-		values.Add("bot", bot)
+	if parameters.Bot != "" {
+		values.Add("bot", parameters.Bot)
+	}
+
+	if parameters.TeamID != "" {
+		values.Add("team_id", parameters.TeamID)
 	}
 
 	response, err := api.botRequest(ctx, "bots.info", values)
diff --git a/chat.go b/chat.go
index 4448e9f..916d05a 100644
--- a/chat.go
+++ b/chat.go
@@ -7,6 +7,7 @@ import (
 	"io/ioutil"
 	"net/http"
 	"net/url"
+	"regexp"
 	"strconv"
 
 	"github.com/slack-go/slack/slackutilsx"
@@ -29,14 +30,14 @@ const (
 
 type chatResponseFull struct {
 	Channel            string `json:"channel"`
-	Timestamp          string `json:"ts"`                             //Regular message timestamp
-	MessageTimeStamp   string `json:"message_ts"`                     //Ephemeral message timestamp
-	ScheduledMessageID string `json:"scheduled_message_id,omitempty"` //Scheduled message id
+	Timestamp          string `json:"ts"`                             // Regular message timestamp
+	MessageTimeStamp   string `json:"message_ts"`                     // Ephemeral message timestamp
+	ScheduledMessageID string `json:"scheduled_message_id,omitempty"` // Scheduled message id
 	Text               string `json:"text"`
 	SlackResponse
 }
 
-// getMessageTimestamp will inspect the `chatResponseFull` to ruturn a timestamp value
+// getMessageTimestamp will inspect the `chatResponseFull` to return a timestamp value
 // in `chat.postMessage` its under `ts`
 // in `chat.postEphemeral` its under `message_ts`
 func (c chatResponseFull) getMessageTimestamp() string {
@@ -87,12 +88,14 @@ func NewPostMessageParameters() PostMessageParameters {
 	}
 }
 
-// DeleteMessage deletes a message in a channel
+// DeleteMessage deletes a message in a channel.
+// For more details, see DeleteMessageContext documentation.
 func (api *Client) DeleteMessage(channel, messageTimestamp string) (string, string, error) {
 	return api.DeleteMessageContext(context.Background(), channel, messageTimestamp)
 }
 
-// DeleteMessageContext deletes a message in a channel with a custom context
+// DeleteMessageContext deletes a message in a channel with a custom context.
+// Slack API docs: https://api.slack.com/methods/chat.delete
 func (api *Client) DeleteMessageContext(ctx context.Context, channel, messageTimestamp string) (string, string, error) {
 	respChannel, respTimestamp, _, err := api.SendMessageContext(
 		ctx,
@@ -105,13 +108,13 @@ func (api *Client) DeleteMessageContext(ctx context.Context, channel, messageTim
 // ScheduleMessage sends a message to a channel.
 // Message is escaped by default according to https://api.slack.com/docs/formatting
 // Use http://davestevens.github.io/slack-message-builder/ to help crafting your message.
+// For more details, see ScheduleMessageContext documentation.
 func (api *Client) ScheduleMessage(channelID, postAt string, options ...MsgOption) (string, string, error) {
 	return api.ScheduleMessageContext(context.Background(), channelID, postAt, options...)
 }
 
-// ScheduleMessageContext sends a message to a channel with a custom context
-//
-// For more details, see ScheduleMessage documentation.
+// ScheduleMessageContext sends a message to a channel with a custom context.
+// Slack API docs: https://api.slack.com/methods/chat.scheduleMessage
 func (api *Client) ScheduleMessageContext(ctx context.Context, channelID, postAt string, options ...MsgOption) (string, string, error) {
 	respChannel, respTimestamp, _, err := api.SendMessageContext(
 		ctx,
@@ -125,12 +128,13 @@ func (api *Client) ScheduleMessageContext(ctx context.Context, channelID, postAt
 // PostMessage sends a message to a channel.
 // Message is escaped by default according to https://api.slack.com/docs/formatting
 // Use http://davestevens.github.io/slack-message-builder/ to help crafting your message.
+// For more details, see PostMessageContext documentation.
 func (api *Client) PostMessage(channelID string, options ...MsgOption) (string, string, error) {
 	return api.PostMessageContext(context.Background(), channelID, options...)
 }
 
-// PostMessageContext sends a message to a channel with a custom context
-// For more details, see PostMessage documentation.
+// PostMessageContext sends a message to a channel with a custom context.
+// Slack API docs: https://api.slack.com/methods/chat.postMessage
 func (api *Client) PostMessageContext(ctx context.Context, channelID string, options ...MsgOption) (string, string, error) {
 	respChannel, respTimestamp, _, err := api.SendMessageContext(
 		ctx,
@@ -144,12 +148,13 @@ func (api *Client) PostMessageContext(ctx context.Context, channelID string, opt
 // PostEphemeral sends an ephemeral message to a user in a channel.
 // Message is escaped by default according to https://api.slack.com/docs/formatting
 // Use http://davestevens.github.io/slack-message-builder/ to help crafting your message.
+// For more details, see PostEphemeralContext documentation.
 func (api *Client) PostEphemeral(channelID, userID string, options ...MsgOption) (string, error) {
 	return api.PostEphemeralContext(context.Background(), channelID, userID, options...)
 }
 
-// PostEphemeralContext sends an ephemeal message to a user in a channel with a custom context
-// For more details, see PostEphemeral documentation
+// PostEphemeralContext sends an ephemeral message to a user in a channel with a custom context.
+// Slack API docs: https://api.slack.com/methods/chat.postEphemeral
 func (api *Client) PostEphemeralContext(ctx context.Context, channelID, userID string, options ...MsgOption) (timestamp string, err error) {
 	_, timestamp, _, err = api.SendMessageContext(
 		ctx,
@@ -160,12 +165,14 @@ func (api *Client) PostEphemeralContext(ctx context.Context, channelID, userID s
 	return timestamp, err
 }
 
-// UpdateMessage updates a message in a channel
+// UpdateMessage updates a message in a channel.
+// For more details, see UpdateMessageContext documentation.
 func (api *Client) UpdateMessage(channelID, timestamp string, options ...MsgOption) (string, string, string, error) {
 	return api.UpdateMessageContext(context.Background(), channelID, timestamp, options...)
 }
 
-// UpdateMessageContext updates a message in a channel
+// UpdateMessageContext updates a message in a channel with a custom context.
+// Slack API docs: https://api.slack.com/methods/chat.update
 func (api *Client) UpdateMessageContext(ctx context.Context, channelID, timestamp string, options ...MsgOption) (string, string, string, error) {
 	return api.SendMessageContext(
 		ctx,
@@ -175,38 +182,38 @@ func (api *Client) UpdateMessageContext(ctx context.Context, channelID, timestam
 	)
 }
 
-// UnfurlMessage unfurls a message in a channel
+// UnfurlMessage unfurls a message in a channel.
+// For more details, see UnfurlMessageContext documentation.
 func (api *Client) UnfurlMessage(channelID, timestamp string, unfurls map[string]Attachment, options ...MsgOption) (string, string, string, error) {
 	return api.UnfurlMessageContext(context.Background(), channelID, timestamp, unfurls, options...)
 }
 
-// UnfurlMessageContext unfurls a message in a channel with a custom context
+// UnfurlMessageContext unfurls a message in a channel with a custom context.
+// Slack API docs: https://api.slack.com/methods/chat.unfurl
 func (api *Client) UnfurlMessageContext(ctx context.Context, channelID, timestamp string, unfurls map[string]Attachment, options ...MsgOption) (string, string, string, error) {
 	return api.SendMessageContext(ctx, channelID, MsgOptionUnfurl(timestamp, unfurls), MsgOptionCompose(options...))
 }
 
-// UnfurlMessageWithAuthURL sends an unfurl request containing an
-// authentication URL.
-// For more details see:
-// https://api.slack.com/reference/messaging/link-unfurling#authenticated_unfurls
+// UnfurlMessageWithAuthURL sends an unfurl request containing an authentication URL.
+// For more details, see UnfurlMessageWithAuthURLContext documentation.
 func (api *Client) UnfurlMessageWithAuthURL(channelID, timestamp string, userAuthURL string, options ...MsgOption) (string, string, string, error) {
 	return api.UnfurlMessageWithAuthURLContext(context.Background(), channelID, timestamp, userAuthURL, options...)
 }
 
-// UnfurlMessageWithAuthURLContext sends an unfurl request containing an
-// authentication URL.
-// For more details see:
-// https://api.slack.com/reference/messaging/link-unfurling#authenticated_unfurls
+// UnfurlMessageWithAuthURLContext sends an unfurl request containing an authentication URL with a custom context.
+// For more details see: https://api.slack.com/reference/messaging/link-unfurling#authenticated_unfurls
 func (api *Client) UnfurlMessageWithAuthURLContext(ctx context.Context, channelID, timestamp string, userAuthURL string, options ...MsgOption) (string, string, string, error) {
 	return api.SendMessageContext(ctx, channelID, MsgOptionUnfurlAuthURL(timestamp, userAuthURL), MsgOptionCompose(options...))
 }
 
 // SendMessage more flexible method for configuring messages.
+// For more details, see SendMessageContext documentation.
 func (api *Client) SendMessage(channel string, options ...MsgOption) (string, string, string, error) {
 	return api.SendMessageContext(context.Background(), channel, options...)
 }
 
 // SendMessageContext more flexible method for configuring messages with a custom context.
+// Slack API docs: https://api.slack.com/methods/chat.postMessage
 func (api *Client) SendMessageContext(ctx context.Context, channelID string, options ...MsgOption) (_channel string, _timestamp string, _text string, err error) {
 	var (
 		req      *http.Request
@@ -224,7 +231,7 @@ func (api *Client) SendMessageContext(ctx context.Context, channelID string, opt
 			return "", "", "", err
 		}
 		req.Body = ioutil.NopCloser(bytes.NewBuffer(reqBody))
-		api.Debugf("Sending request: %s", string(reqBody))
+		api.Debugf("Sending request: %s", redactToken(reqBody))
 	}
 
 	if err = doPost(ctx, api.httpclient, req, parser(&response), api); err != nil {
@@ -234,6 +241,20 @@ func (api *Client) SendMessageContext(ctx context.Context, channelID string, opt
 	return response.Channel, response.getMessageTimestamp(), response.Text, response.Err()
 }
 
+func redactToken(b []byte) []byte {
+	// See https://api.slack.com/authentication/token-types
+	// and https://api.slack.com/authentication/rotation
+	re, err := regexp.Compile(`(token=x[a-z.]+)-[0-9A-Za-z-]+`)
+	if err != nil {
+		// The regular expression above should never result in errors,
+		// but just in case, do no harm.
+		return b
+	}
+	// Keep "token=" and the first element of the token, which identifies its type
+	// (this could be useful for debugging, e.g. when using a wrong token).
+	return re.ReplaceAll(b, []byte("$1-REDACTED"))
+}
+
 // UnsafeApplyMsgOptions utility function for debugging/testing chat requests.
 // NOTE: USE AT YOUR OWN RISK: No issues relating to the use of this function
 // will be supported by the library.
@@ -681,6 +702,14 @@ func MsgOptionMetadata(metadata SlackMetadata) MsgOption {
 	}
 }
 
+// MsgOptionLinkNames finds and links user groups. Does not support linking individual users
+func MsgOptionLinkNames(linkName bool) MsgOption {
+	return func(config *sendConfig) error {
+		config.values.Set("link_names", strconv.FormatBool(linkName))
+		return nil
+	}
+}
+
 // UnsafeMsgOptionEndpoint deliver the message to the specified endpoint.
 // NOTE: USE AT YOUR OWN RISK: No issues relating to the use of this Option
 // will be supported by the library, it is subject to change without notice that
@@ -748,22 +777,21 @@ func MsgOptionPostMessageParameters(params PostMessageParameters) MsgOption {
 	}
 }
 
-// PermalinkParameters are the parameters required to get a permalink to a
-// message. Slack documentation can be found here:
-// https://api.slack.com/methods/chat.getPermalink
+// PermalinkParameters are the parameters required to get a permalink to a message.
 type PermalinkParameters struct {
 	Channel string
 	Ts      string
 }
 
-// GetPermalink returns the permalink for a message. It takes
-// PermalinkParameters and returns a string containing the permalink. It
-// returns an error if unable to retrieve the permalink.
+// GetPermalink returns the permalink for a message. It takes PermalinkParameters and returns a string containing the
+// permalink. It returns an error if unable to retrieve the permalink.
+// For more details, see GetPermalinkContext documentation.
 func (api *Client) GetPermalink(params *PermalinkParameters) (string, error) {
 	return api.GetPermalinkContext(context.Background(), params)
 }
 
 // GetPermalinkContext returns the permalink for a message using a custom context.
+// Slack API docs: https://api.slack.com/methods/chat.getPermalink
 func (api *Client) GetPermalinkContext(ctx context.Context, params *PermalinkParameters) (string, error) {
 	values := url.Values{
 		"channel":    {params.Channel},
@@ -784,18 +812,21 @@ func (api *Client) GetPermalinkContext(ctx context.Context, params *PermalinkPar
 
 type GetScheduledMessagesParameters struct {
 	Channel string
+	TeamID  string
 	Cursor  string
 	Latest  string
 	Limit   int
 	Oldest  string
 }
 
-// GetScheduledMessages returns the list of scheduled messages based on params
+// GetScheduledMessages returns the list of scheduled messages based on params.
+// For more details, see GetScheduledMessagesContext documentation.
 func (api *Client) GetScheduledMessages(params *GetScheduledMessagesParameters) (channels []ScheduledMessage, nextCursor string, err error) {
 	return api.GetScheduledMessagesContext(context.Background(), params)
 }
 
-// GetScheduledMessagesContext returns the list of scheduled messages in a Slack team with a custom context
+// GetScheduledMessagesContext returns the list of scheduled messages based on params with a custom context.
+// Slack API docs: https://api.slack.com/methods/chat.getScheduledMessages.list
 func (api *Client) GetScheduledMessagesContext(ctx context.Context, params *GetScheduledMessagesParameters) (channels []ScheduledMessage, nextCursor string, err error) {
 	values := url.Values{
 		"token": {api.token},
@@ -803,6 +834,9 @@ func (api *Client) GetScheduledMessagesContext(ctx context.Context, params *GetS
 	if params.Channel != "" {
 		values.Add("channel", params.Channel)
 	}
+	if params.TeamID != "" {
+		values.Add("team_id", params.TeamID)
+	}
 	if params.Cursor != "" {
 		values.Add("cursor", params.Cursor)
 	}
@@ -835,12 +869,14 @@ type DeleteScheduledMessageParameters struct {
 	AsUser             bool
 }
 
-// DeleteScheduledMessage returns the list of scheduled messages based on params
+// DeleteScheduledMessage deletes a pending scheduled message.
+// For more details, see DeleteScheduledMessageContext documentation.
 func (api *Client) DeleteScheduledMessage(params *DeleteScheduledMessageParameters) (bool, error) {
 	return api.DeleteScheduledMessageContext(context.Background(), params)
 }
 
-// DeleteScheduledMessageContext returns the list of scheduled messages in a Slack team with a custom context
+// DeleteScheduledMessageContext deletes a pending scheduled message with a custom context.
+// Slack API docs: https://api.slack.com/methods/chat.deleteScheduledMessage
 func (api *Client) DeleteScheduledMessageContext(ctx context.Context, params *DeleteScheduledMessageParameters) (bool, error) {
 	values := url.Values{
 		"token":                {api.token},
diff --git a/conversation.go b/conversation.go
index 44f06ea..6e608eb 100644
--- a/conversation.go
+++ b/conversation.go
@@ -22,14 +22,19 @@ type Conversation struct {
 	IsIM               bool     `json:"is_im"`
 	IsExtShared        bool     `json:"is_ext_shared"`
 	IsOrgShared        bool     `json:"is_org_shared"`
+	IsGlobalShared     bool     `json:"is_global_shared"`
 	IsPendingExtShared bool     `json:"is_pending_ext_shared"`
 	IsPrivate          bool     `json:"is_private"`
+	IsReadOnly         bool     `json:"is_read_only"`
 	IsMpIM             bool     `json:"is_mpim"`
 	Unlinked           int      `json:"unlinked"`
 	NameNormalized     string   `json:"name_normalized"`
 	NumMembers         int      `json:"num_members"`
 	Priority           float64  `json:"priority"`
 	User               string   `json:"user"`
+	ConnectedTeamIDs   []string `json:"connected_team_ids,omitempty"`
+	SharedTeamIDs      []string `json:"shared_team_ids,omitempty"`
+	InternalTeamIDs    []string `json:"internal_team_ids,omitempty"`
 
 	// TODO support pending_shared
 	// TODO support previous_names
@@ -79,12 +84,14 @@ type responseMetaData struct {
 	NextCursor string `json:"next_cursor"`
 }
 
-// GetUsersInConversation returns the list of users in a conversation
+// GetUsersInConversation returns the list of users in a conversation.
+// For more details, see GetUsersInConversationContext documentation.
 func (api *Client) GetUsersInConversation(params *GetUsersInConversationParameters) ([]string, string, error) {
 	return api.GetUsersInConversationContext(context.Background(), params)
 }
 
-// GetUsersInConversationContext returns the list of users in a conversation with a custom context
+// GetUsersInConversationContext returns the list of users in a conversation with a custom context.
+// Slack API docs: https://api.slack.com/methods/conversations.members
 func (api *Client) GetUsersInConversationContext(ctx context.Context, params *GetUsersInConversationParameters) ([]string, string, error) {
 	values := url.Values{
 		"token":   {api.token},
@@ -114,12 +121,14 @@ func (api *Client) GetUsersInConversationContext(ctx context.Context, params *Ge
 	return response.Members, response.ResponseMetaData.NextCursor, nil
 }
 
-// GetConversationsForUser returns the list conversations for a given user
+// GetConversationsForUser returns the list conversations for a given user.
+// For more details, see GetConversationsForUserContext documentation.
 func (api *Client) GetConversationsForUser(params *GetConversationsForUserParameters) (channels []Channel, nextCursor string, err error) {
 	return api.GetConversationsForUserContext(context.Background(), params)
 }
 
 // GetConversationsForUserContext returns the list conversations for a given user with a custom context
+// Slack API docs: https://api.slack.com/methods/users.conversations
 func (api *Client) GetConversationsForUserContext(ctx context.Context, params *GetConversationsForUserParameters) (channels []Channel, nextCursor string, err error) {
 	values := url.Values{
 		"token": {api.token},
@@ -156,12 +165,14 @@ func (api *Client) GetConversationsForUserContext(ctx context.Context, params *G
 	return response.Channels, response.ResponseMetaData.NextCursor, response.Err()
 }
 
-// ArchiveConversation archives a conversation
+// ArchiveConversation archives a conversation.
+// For more details, see ArchiveConversationContext documentation.
 func (api *Client) ArchiveConversation(channelID string) error {
 	return api.ArchiveConversationContext(context.Background(), channelID)
 }
 
-// ArchiveConversationContext archives a conversation with a custom context
+// ArchiveConversationContext archives a conversation with a custom context.
+// Slack API docs: https://api.slack.com/methods/conversations.archive
 func (api *Client) ArchiveConversationContext(ctx context.Context, channelID string) error {
 	values := url.Values{
 		"token":   {api.token},
@@ -177,12 +188,14 @@ func (api *Client) ArchiveConversationContext(ctx context.Context, channelID str
 	return response.Err()
 }
 
-// UnArchiveConversation reverses conversation archival
+// UnArchiveConversation reverses conversation archival.
+// For more details, see UnArchiveConversationContext documentation.
 func (api *Client) UnArchiveConversation(channelID string) error {
 	return api.UnArchiveConversationContext(context.Background(), channelID)
 }
 
-// UnArchiveConversationContext reverses conversation archival with a custom context
+// UnArchiveConversationContext reverses conversation archival with a custom context.
+// Slack API docs: https://api.slack.com/methods/conversations.unarchive
 func (api *Client) UnArchiveConversationContext(ctx context.Context, channelID string) error {
 	values := url.Values{
 		"token":   {api.token},
@@ -197,12 +210,14 @@ func (api *Client) UnArchiveConversationContext(ctx context.Context, channelID s
 	return response.Err()
 }
 
-// SetTopicOfConversation sets the topic for a conversation
+// SetTopicOfConversation sets the topic for a conversation.
+// For more details, see SetTopicOfConversationContext documentation.
 func (api *Client) SetTopicOfConversation(channelID, topic string) (*Channel, error) {
 	return api.SetTopicOfConversationContext(context.Background(), channelID, topic)
 }
 
-// SetTopicOfConversationContext sets the topic for a conversation with a custom context
+// SetTopicOfConversationContext sets the topic for a conversation with a custom context.
+// Slack API docs: https://api.slack.com/methods/conversations.setTopic
 func (api *Client) SetTopicOfConversationContext(ctx context.Context, channelID, topic string) (*Channel, error) {
 	values := url.Values{
 		"token":   {api.token},
@@ -221,12 +236,14 @@ func (api *Client) SetTopicOfConversationContext(ctx context.Context, channelID,
 	return response.Channel, response.Err()
 }
 
-// SetPurposeOfConversation sets the purpose for a conversation
+// SetPurposeOfConversation sets the purpose for a conversation.
+// For more details, see SetPurposeOfConversationContext documentation.
 func (api *Client) SetPurposeOfConversation(channelID, purpose string) (*Channel, error) {
 	return api.SetPurposeOfConversationContext(context.Background(), channelID, purpose)
 }
 
-// SetPurposeOfConversationContext sets the purpose for a conversation with a custom context
+// SetPurposeOfConversationContext sets the purpose for a conversation with a custom context.
+// Slack API docs: https://api.slack.com/methods/conversations.setPurpose
 func (api *Client) SetPurposeOfConversationContext(ctx context.Context, channelID, purpose string) (*Channel, error) {
 	values := url.Values{
 		"token":   {api.token},
@@ -246,12 +263,14 @@ func (api *Client) SetPurposeOfConversationContext(ctx context.Context, channelI
 	return response.Channel, response.Err()
 }
 
-// RenameConversation renames a conversation
+// RenameConversation renames a conversation.
+// For more details, see RenameConversationContext documentation.
 func (api *Client) RenameConversation(channelID, channelName string) (*Channel, error) {
 	return api.RenameConversationContext(context.Background(), channelID, channelName)
 }
 
-// RenameConversationContext renames a conversation with a custom context
+// RenameConversationContext renames a conversation with a custom context.
+// Slack API docs: https://api.slack.com/methods/conversations.rename
 func (api *Client) RenameConversationContext(ctx context.Context, channelID, channelName string) (*Channel, error) {
 	values := url.Values{
 		"token":   {api.token},
@@ -271,12 +290,14 @@ func (api *Client) RenameConversationContext(ctx context.Context, channelID, cha
 	return response.Channel, response.Err()
 }
 
-// InviteUsersToConversation invites users to a channel
+// InviteUsersToConversation invites users to a channel.
+// For more details, see InviteUsersToConversation documentation.
 func (api *Client) InviteUsersToConversation(channelID string, users ...string) (*Channel, error) {
 	return api.InviteUsersToConversationContext(context.Background(), channelID, users...)
 }
 
-// InviteUsersToConversationContext invites users to a channel with a custom context
+// InviteUsersToConversationContext invites users to a channel with a custom context.
+// Slack API docs: https://api.slack.com/methods/conversations.invite
 func (api *Client) InviteUsersToConversationContext(ctx context.Context, channelID string, users ...string) (*Channel, error) {
 	values := url.Values{
 		"token":   {api.token},
@@ -296,12 +317,66 @@ func (api *Client) InviteUsersToConversationContext(ctx context.Context, channel
 	return response.Channel, response.Err()
 }
 
-// KickUserFromConversation removes a user from a conversation
+// InviteSharedEmailsToConversation invites users to a shared channels by email.
+// For more details, see InviteSharedEmailsToConversationContext documentation.
+func (api *Client) InviteSharedEmailsToConversation(channelID string, emails ...string) (string, bool, error) {
+	return api.inviteSharedToConversationHelper(context.Background(), channelID, emails, nil)
+}
+
+// InviteSharedEmailsToConversationContext invites users to a shared channels by email using context.
+// For more details, see inviteSharedToConversationHelper documentation.
+func (api *Client) InviteSharedEmailsToConversationContext(ctx context.Context, channelID string, emails ...string) (string, bool, error) {
+	return api.inviteSharedToConversationHelper(ctx, channelID, emails, nil)
+}
+
+// InviteSharedUserIDsToConversation invites users to a shared channels by user id.
+// For more details, see InviteSharedUserIDsToConversationContext documentation.
+func (api *Client) InviteSharedUserIDsToConversation(channelID string, userIDs ...string) (string, bool, error) {
+	return api.inviteSharedToConversationHelper(context.Background(), channelID, nil, userIDs)
+}
+
+// InviteSharedUserIDsToConversationContext invites users to a shared channels by user id with context.
+// For more details, see inviteSharedToConversationHelper documentation.
+func (api *Client) InviteSharedUserIDsToConversationContext(ctx context.Context, channelID string, userIDs ...string) (string, bool, error) {
+	return api.inviteSharedToConversationHelper(ctx, channelID, nil, userIDs)
+}
+
+// inviteSharedToConversationHelper invites emails or userIDs to a channel with a custom context.
+// This is a helper function for InviteSharedEmailsToConversation and InviteSharedUserIDsToConversation.
+// It accepts either emails or userIDs, but not both.
+// Slack API docs: https://api.slack.com/methods/conversations.inviteShared
+func (api *Client) inviteSharedToConversationHelper(ctx context.Context, channelID string, emails []string, userIDs []string) (string, bool, error) {
+	values := url.Values{
+		"token":   {api.token},
+		"channel": {channelID},
+	}
+	if len(emails) > 0 {
+		values.Add("emails", strings.Join(emails, ","))
+	} else if len(userIDs) > 0 {
+		values.Add("user_ids", strings.Join(userIDs, ","))
+	}
+	response := struct {
+		SlackResponse
+		InviteID              string `json:"invite_id"`
+		IsLegacySharedChannel bool   `json:"is_legacy_shared_channel"`
+	}{}
+
+	err := api.postMethod(ctx, "conversations.inviteShared", values, &response)
+	if err != nil {
+		return "", false, err
+	}
+
+	return response.InviteID, response.IsLegacySharedChannel, response.Err()
+}
+
+// KickUserFromConversation removes a user from a conversation.
+// For more details, see KickUserFromConversationContext documentation.
 func (api *Client) KickUserFromConversation(channelID string, user string) error {
 	return api.KickUserFromConversationContext(context.Background(), channelID, user)
 }
 
-// KickUserFromConversationContext removes a user from a conversation with a custom context
+// KickUserFromConversationContext removes a user from a conversation with a custom context.
+// Slack API docs: https://api.slack.com/methods/conversations.kick
 func (api *Client) KickUserFromConversationContext(ctx context.Context, channelID string, user string) error {
 	values := url.Values{
 		"token":   {api.token},
@@ -318,12 +393,14 @@ func (api *Client) KickUserFromConversationContext(ctx context.Context, channelI
 	return response.Err()
 }
 
-// CloseConversation closes a direct message or multi-person direct message
+// CloseConversation closes a direct message or multi-person direct message.
+// For more details, see CloseConversationContext documentation.
 func (api *Client) CloseConversation(channelID string) (noOp bool, alreadyClosed bool, err error) {
 	return api.CloseConversationContext(context.Background(), channelID)
 }
 
-// CloseConversationContext closes a direct message or multi-person direct message with a custom context
+// CloseConversationContext closes a direct message or multi-person direct message with a custom context.
+// Slack API docs: https://api.slack.com/methods/conversations.close
 func (api *Client) CloseConversationContext(ctx context.Context, channelID string) (noOp bool, alreadyClosed bool, err error) {
 	values := url.Values{
 		"token":   {api.token},
@@ -349,12 +426,14 @@ type CreateConversationParams struct {
 	TeamID      string
 }
 
-// CreateConversation initiates a public or private channel-based conversation
+// CreateConversation initiates a public or private channel-based conversation.
+// For more details, see CreateConversationContext documentation.
 func (api *Client) CreateConversation(params CreateConversationParams) (*Channel, error) {
 	return api.CreateConversationContext(context.Background(), params)
 }
 
-// CreateConversationContext initiates a public or private channel-based conversation with a custom context
+// CreateConversationContext initiates a public or private channel-based conversation with a custom context.
+// Slack API docs: https://api.slack.com/methods/conversations.create
 func (api *Client) CreateConversationContext(ctx context.Context, params CreateConversationParams) (*Channel, error) {
 	values := url.Values{
 		"token":      {api.token},
@@ -379,12 +458,14 @@ type GetConversationInfoInput struct {
 	IncludeNumMembers bool
 }
 
-// GetConversationInfo retrieves information about a conversation
+// GetConversationInfo retrieves information about a conversation.
+// For more details, see GetConversationInfoContext documentation.
 func (api *Client) GetConversationInfo(input *GetConversationInfoInput) (*Channel, error) {
 	return api.GetConversationInfoContext(context.Background(), input)
 }
 
-// GetConversationInfoContext retrieves information about a conversation with a custom context
+// GetConversationInfoContext retrieves information about a conversation with a custom context.
+// Slack API docs: https://api.slack.com/methods/conversations.info
 func (api *Client) GetConversationInfoContext(ctx context.Context, input *GetConversationInfoInput) (*Channel, error) {
 	if input == nil {
 		return nil, errors.New("GetConversationInfoInput must not be nil")
@@ -408,12 +489,14 @@ func (api *Client) GetConversationInfoContext(ctx context.Context, input *GetCon
 	return &response.Channel, response.Err()
 }
 
-// LeaveConversation leaves a conversation
+// LeaveConversation leaves a conversation.
+// For more details, see LeaveConversationContext documentation.
 func (api *Client) LeaveConversation(channelID string) (bool, error) {
 	return api.LeaveConversationContext(context.Background(), channelID)
 }
 
-// LeaveConversationContext leaves a conversation with a custom context
+// LeaveConversationContext leaves a conversation with a custom context.
+// Slack API docs: https://api.slack.com/methods/conversations.leave
 func (api *Client) LeaveConversationContext(ctx context.Context, channelID string) (bool, error) {
 	values := url.Values{
 		"token":   {api.token},
@@ -439,12 +522,14 @@ type GetConversationRepliesParameters struct {
 	IncludeAllMetadata bool
 }
 
-// GetConversationReplies retrieves a thread of messages posted to a conversation
+// GetConversationReplies retrieves a thread of messages posted to a conversation.
+// For more details, see GetConversationRepliesContext documentation.
 func (api *Client) GetConversationReplies(params *GetConversationRepliesParameters) (msgs []Message, hasMore bool, nextCursor string, err error) {
 	return api.GetConversationRepliesContext(context.Background(), params)
 }
 
-// GetConversationRepliesContext retrieves a thread of messages posted to a conversation with a custom context
+// GetConversationRepliesContext retrieves a thread of messages posted to a conversation with a custom context.
+// Slack API docs: https://api.slack.com/methods/conversations.replies
 func (api *Client) GetConversationRepliesContext(ctx context.Context, params *GetConversationRepliesParameters) (msgs []Message, hasMore bool, nextCursor string, err error) {
 	values := url.Values{
 		"token":   {api.token},
@@ -498,12 +583,14 @@ type GetConversationsParameters struct {
 	TeamID          string
 }
 
-// GetConversations returns the list of channels in a Slack team
+// GetConversations returns the list of channels in a Slack team.
+// For more details, see GetConversationsContext documentation.
 func (api *Client) GetConversations(params *GetConversationsParameters) (channels []Channel, nextCursor string, err error) {
 	return api.GetConversationsContext(context.Background(), params)
 }
 
-// GetConversationsContext returns the list of channels in a Slack team with a custom context
+// GetConversationsContext returns the list of channels in a Slack team with a custom context.
+// Slack API docs: https://api.slack.com/methods/conversations.list
 func (api *Client) GetConversationsContext(ctx context.Context, params *GetConversationsParameters) (channels []Channel, nextCursor string, err error) {
 	values := url.Values{
 		"token": {api.token},
@@ -544,12 +631,14 @@ type OpenConversationParameters struct {
 	Users     []string
 }
 
-// OpenConversation opens or resumes a direct message or multi-person direct message
+// OpenConversation opens or resumes a direct message or multi-person direct message.
+// For more details, see OpenConversationContext documentation.
 func (api *Client) OpenConversation(params *OpenConversationParameters) (*Channel, bool, bool, error) {
 	return api.OpenConversationContext(context.Background(), params)
 }
 
-// OpenConversationContext opens or resumes a direct message or multi-person direct message with a custom context
+// OpenConversationContext opens or resumes a direct message or multi-person direct message with a custom context.
+// Slack API docs: https://api.slack.com/methods/conversations.open
 func (api *Client) OpenConversationContext(ctx context.Context, params *OpenConversationParameters) (*Channel, bool, bool, error) {
 	values := url.Values{
 		"token":     {api.token},
@@ -576,12 +665,14 @@ func (api *Client) OpenConversationContext(ctx context.Context, params *OpenConv
 	return response.Channel, response.NoOp, response.AlreadyOpen, response.Err()
 }
 
-// JoinConversation joins an existing conversation
+// JoinConversation joins an existing conversation.
+// For more details, see JoinConversationContext documentation.
 func (api *Client) JoinConversation(channelID string) (*Channel, string, []string, error) {
 	return api.JoinConversationContext(context.Background(), channelID)
 }
 
-// JoinConversationContext joins an existing conversation with a custom context
+// JoinConversationContext joins an existing conversation with a custom context.
+// Slack API docs: https://api.slack.com/methods/conversations.join
 func (api *Client) JoinConversationContext(ctx context.Context, channelID string) (*Channel, string, []string, error) {
 	values := url.Values{"token": {api.token}, "channel": {channelID}}
 	response := struct {
@@ -628,12 +719,14 @@ type GetConversationHistoryResponse struct {
 	Messages []Message `json:"messages"`
 }
 
-// GetConversationHistory joins an existing conversation
+// GetConversationHistory joins an existing conversation.
+// For more details, see GetConversationHistoryContext documentation.
 func (api *Client) GetConversationHistory(params *GetConversationHistoryParameters) (*GetConversationHistoryResponse, error) {
 	return api.GetConversationHistoryContext(context.Background(), params)
 }
 
-// GetConversationHistoryContext joins an existing conversation with a custom context
+// GetConversationHistoryContext joins an existing conversation with a custom context.
+// Slack API docs: https://api.slack.com/methods/conversations.history
 func (api *Client) GetConversationHistoryContext(ctx context.Context, params *GetConversationHistoryParameters) (*GetConversationHistoryResponse, error) {
 	values := url.Values{"token": {api.token}, "channel": {params.ChannelID}}
 	if params.Cursor != "" {
@@ -669,12 +762,14 @@ func (api *Client) GetConversationHistoryContext(ctx context.Context, params *Ge
 	return &response, response.Err()
 }
 
-// MarkConversation sets the read mark of a conversation to a specific point
+// MarkConversation sets the read mark of a conversation to a specific point.
+// For more details, see MarkConversationContext documentation.
 func (api *Client) MarkConversation(channel, ts string) (err error) {
 	return api.MarkConversationContext(context.Background(), channel, ts)
 }
 
-// MarkConversationContext sets the read mark of a conversation to a specific point with a custom context
+// MarkConversationContext sets the read mark of a conversation to a specific point with a custom context.
+// Slack API docs: https://api.slack.com/methods/conversations.mark
 func (api *Client) MarkConversationContext(ctx context.Context, channel, ts string) error {
 	values := url.Values{
 		"token":   {api.token},
diff --git a/dnd.go b/dnd.go
index a3aa680..81eaf50 100644
--- a/dnd.go
+++ b/dnd.go
@@ -45,12 +45,14 @@ func (api *Client) dndRequest(ctx context.Context, path string, values url.Value
 	return response, response.Err()
 }
 
-// EndDND ends the user's scheduled Do Not Disturb session
+// EndDND ends the user's scheduled Do Not Disturb session.
+// For more information see the EndDNDContext documentation.
 func (api *Client) EndDND() error {
 	return api.EndDNDContext(context.Background())
 }
 
-// EndDNDContext ends the user's scheduled Do Not Disturb session with a custom context
+// EndDNDContext ends the user's scheduled Do Not Disturb session with a custom context.
+// Slack API docs: https://api.slack.com/methods/dnd.endDnd
 func (api *Client) EndDNDContext(ctx context.Context) error {
 	values := url.Values{
 		"token": {api.token},
@@ -65,12 +67,14 @@ func (api *Client) EndDNDContext(ctx context.Context) error {
 	return response.Err()
 }
 
-// EndSnooze ends the current user's snooze mode
+// EndSnooze ends the current user's snooze mode.
+// For more information see the EndSnoozeContext documentation.
 func (api *Client) EndSnooze() (*DNDStatus, error) {
 	return api.EndSnoozeContext(context.Background())
 }
 
-// EndSnoozeContext ends the current user's snooze mode with a custom context
+// EndSnoozeContext ends the current user's snooze mode with a custom context.
+// Slack API docs: https://api.slack.com/methods/dnd.endSnooze
 func (api *Client) EndSnoozeContext(ctx context.Context) (*DNDStatus, error) {
 	values := url.Values{
 		"token": {api.token},
@@ -84,11 +88,13 @@ func (api *Client) EndSnoozeContext(ctx context.Context) (*DNDStatus, error) {
 }
 
 // GetDNDInfo provides information about a user's current Do Not Disturb settings.
+// For more information see the GetDNDInfoContext documentation.
 func (api *Client) GetDNDInfo(user *string) (*DNDStatus, error) {
 	return api.GetDNDInfoContext(context.Background(), user)
 }
 
 // GetDNDInfoContext provides information about a user's current Do Not Disturb settings with a custom context.
+// Slack API docs: https://api.slack.com/methods/dnd.info
 func (api *Client) GetDNDInfoContext(ctx context.Context, user *string) (*DNDStatus, error) {
 	values := url.Values{
 		"token": {api.token},
@@ -105,11 +111,13 @@ func (api *Client) GetDNDInfoContext(ctx context.Context, user *string) (*DNDSta
 }
 
 // GetDNDTeamInfo provides information about a user's current Do Not Disturb settings.
+// For more information see the GetDNDTeamInfoContext documentation.
 func (api *Client) GetDNDTeamInfo(users []string) (map[string]DNDStatus, error) {
 	return api.GetDNDTeamInfoContext(context.Background(), users)
 }
 
 // GetDNDTeamInfoContext provides information about a user's current Do Not Disturb settings with a custom context.
+// Slack API docs: https://api.slack.com/methods/dnd.teamInfo
 func (api *Client) GetDNDTeamInfoContext(ctx context.Context, users []string) (map[string]DNDStatus, error) {
 	values := url.Values{
 		"token": {api.token},
@@ -128,15 +136,16 @@ func (api *Client) GetDNDTeamInfoContext(ctx context.Context, users []string) (m
 	return response.Users, nil
 }
 
-// SetSnooze adjusts the snooze duration for a user's Do Not Disturb
-// settings. If a snooze session is not already active for the user, invoking
-// this method will begin one for the specified duration.
+// SetSnooze adjusts the snooze duration for a user's Do Not Disturb settings.
+// For more information see the SetSnoozeContext documentation.
 func (api *Client) SetSnooze(minutes int) (*DNDStatus, error) {
 	return api.SetSnoozeContext(context.Background(), minutes)
 }
 
-// SetSnoozeContext adjusts the snooze duration for a user's Do Not Disturb settings with a custom context.
-// For more information see the SetSnooze docs
+// SetSnoozeContext adjusts the snooze duration for a user's Do Not Disturb settings.
+// If a snooze session is not already active for the user, invoking this method will
+// begin one for the specified duration.
+// Slack API docs: https://api.slack.com/methods/dnd.setSnooze
 func (api *Client) SetSnoozeContext(ctx context.Context, minutes int) (*DNDStatus, error) {
 	values := url.Values{
 		"token":       {api.token},
diff --git a/emoji.go b/emoji.go
index b2b0c6c..139df0f 100644
--- a/emoji.go
+++ b/emoji.go
@@ -10,12 +10,14 @@ type emojiResponseFull struct {
 	SlackResponse
 }
 
-// GetEmoji retrieves all the emojis
+// GetEmoji retrieves all the emojis.
+// For more details see GetEmojiContext documentation.
 func (api *Client) GetEmoji() (map[string]string, error) {
 	return api.GetEmojiContext(context.Background())
 }
 
-// GetEmojiContext retrieves all the emojis with a custom context
+// GetEmojiContext retrieves all the emojis with a custom context.
+// Slack API docs: https://api.slack.com/methods/emoji.list
 func (api *Client) GetEmojiContext(ctx context.Context) (map[string]string, error) {
 	values := url.Values{
 		"token": {api.token},
diff --git a/examples/blocks/README.md b/examples/blocks/README.md
index 588f248..2e5d81f 100644
--- a/examples/blocks/README.md
+++ b/examples/blocks/README.md
@@ -53,7 +53,7 @@ To preview this block on the builder website, you should copy just the contents
 The first example demonstrates usage of Sections, Fields and Action buttons.  You can view the [Approval Example](https://api.slack.com/tools/block-kit-builder?blocks=%5B%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22You%20have%20a%20new%20request%3A%5Cn*%3CfakeLink.toEmployeeProfile.com%7CFred%20Enriquez%20-%20New%20device%20request%3E*%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22fields%22%3A%20%5B%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%09%22text%22%3A%20%22*Type%3A*%5CnComputer%20(laptop)%22%0A%09%09%09%7D%2C%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%09%22text%22%3A%20%22*When%3A*%5CnSubmitted%20Aut%2010%22%0A%09%09%09%7D%2C%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%09%22text%22%3A%20%22*Last%20Update%3A*%5CnMar%2010%2C%202015%20(3%20years%2C%205%20months)%22%0A%09%09%09%7D%2C%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%09%22text%22%3A%20%22*Reason%3A*%5CnAll%20vowel%20keys%20aren%27t%20working.%22%0A%09%09%09%7D%2C%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%09%22text%22%3A%20%22*Specs%3A*%5Cn%5C%22Cheetah%20Pro%2015%5C%22%20-%20Fast%2C%20really%20fast%5C%22%22%0A%09%09%09%7D%0A%09%09%5D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22actions%22%2C%0A%09%09%22elements%22%3A%20%5B%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22button%22%2C%0A%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%22text%22%3A%20%22Approve%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%22value%22%3A%20%22click_me_123%22%0A%09%09%09%7D%2C%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22button%22%2C%0A%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%22text%22%3A%20%22Deny%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%22value%22%3A%20%22click_me_123%22%0A%09%09%09%7D%0A%09%09%5D%0A%09%7D%0A%5D) on the block kit builder website. This example can be generated with the function named `exampleOne`.
 
 #### Example 2 - Approval - With Images
-The secoond example adds additional complexity by introducing images as accessories to main blocks of text. You can view this [Approval Example with Images](https://api.slack.com/tools/block-kit-builder?blocks=%5B%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22You%20have%20a%20new%20request%3A%5Cn*%3Cgoogle.com%7CFred%20Enriquez%20-%20Time%20Off%20request%3E*%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*Type%3A*%5CnPaid%20time%20off%5Cn*When%3A*%5CnAug%2010-Aug%2013%5Cn*Hours%3A*%2016.0%20(2%20days)%5Cn*Remaining%20balance%3A*%2032.0%20hours%20(4%20days)%5Cn*Comments%3A*%20%5C%22Family%20in%20town%2C%20going%20camping!%5C%22%22%0A%09%09%7D%2C%0A%09%09%22accessory%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22image%22%2C%0A%09%09%09%22image_url%22%3A%20%22https%3A%2F%2Fapi.slack.com%2Fimg%2Fblocks%2Fbkb_template_images%2FapprovalsNewDevice.png%22%2C%0A%09%09%09%22alt_text%22%3A%20%22computer%20thumbnail%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22actions%22%2C%0A%09%09%22elements%22%3A%20%5B%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22button%22%2C%0A%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%22text%22%3A%20%22Approve%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%22value%22%3A%20%22click_me_123%22%0A%09%09%09%7D%2C%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22button%22%2C%0A%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%22text%22%3A%20%22Deny%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%22value%22%3A%20%22click_me_123%22%0A%09%09%09%7D%0A%09%09%5D%0A%09%7D%0A%5D) on the block kit builder website. This example can be generated with the function named `exampleTwo`.
+The second example adds additional complexity by introducing images as accessories to main blocks of text. You can view this [Approval Example with Images](https://api.slack.com/tools/block-kit-builder?blocks=%5B%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22You%20have%20a%20new%20request%3A%5Cn*%3Cgoogle.com%7CFred%20Enriquez%20-%20Time%20Off%20request%3E*%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*Type%3A*%5CnPaid%20time%20off%5Cn*When%3A*%5CnAug%2010-Aug%2013%5Cn*Hours%3A*%2016.0%20(2%20days)%5Cn*Remaining%20balance%3A*%2032.0%20hours%20(4%20days)%5Cn*Comments%3A*%20%5C%22Family%20in%20town%2C%20going%20camping!%5C%22%22%0A%09%09%7D%2C%0A%09%09%22accessory%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22image%22%2C%0A%09%09%09%22image_url%22%3A%20%22https%3A%2F%2Fapi.slack.com%2Fimg%2Fblocks%2Fbkb_template_images%2FapprovalsNewDevice.png%22%2C%0A%09%09%09%22alt_text%22%3A%20%22computer%20thumbnail%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22actions%22%2C%0A%09%09%22elements%22%3A%20%5B%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22button%22%2C%0A%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%22text%22%3A%20%22Approve%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%22value%22%3A%20%22click_me_123%22%0A%09%09%09%7D%2C%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22button%22%2C%0A%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%22text%22%3A%20%22Deny%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%22value%22%3A%20%22click_me_123%22%0A%09%09%09%7D%0A%09%09%5D%0A%09%7D%0A%5D) on the block kit builder website. This example can be generated with the function named `exampleTwo`.
 
 #### Example 3 - Notifications
 This example shows how to add actions to your block that will trigger an interactive message to your application.  You can view the rendered example for [Notifications](https://api.slack.com/tools/block-kit-builder?blocks=%5B%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%22text%22%3A%20%22Looks%20like%20you%20have%20a%20scheduling%20conflict%20with%20this%20event%3A%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22divider%22%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*%3CfakeLink.toUserProfiles.com%7CIris%20%2F%20Zelda%201-1%3E*%5CnTuesday%2C%20January%2021%204%3A00-4%3A30pm%5CnBuilding%202%20-%20Havarti%20Cheese%20(3)%5Cn2%20guests%22%0A%09%09%7D%2C%0A%09%09%22accessory%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22image%22%2C%0A%09%09%09%22image_url%22%3A%20%22https%3A%2F%2Fapi.slack.com%2Fimg%2Fblocks%2Fbkb_template_images%2Fnotifications.png%22%2C%0A%09%09%09%22alt_text%22%3A%20%22calendar%20thumbnail%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22context%22%2C%0A%09%09%22elements%22%3A%20%5B%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22image%22%2C%0A%09%09%09%09%22image_url%22%3A%20%22https%3A%2F%2Fapi.slack.com%2Fimg%2Fblocks%2Fbkb_template_images%2FnotificationsWarningIcon.png%22%2C%0A%09%09%09%09%22alt_text%22%3A%20%22notifications%20warning%20icon%22%0A%09%09%09%7D%2C%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%09%22text%22%3A%20%22*Conflicts%20with%20Team%20Huddle%3A%204%3A15-4%3A30pm*%22%0A%09%09%09%7D%0A%09%09%5D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22divider%22%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*Propose%20a%20new%20time%3A*%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*Today%20-%204%3A30-5pm*%5CnEveryone%20is%20available%3A%20%40iris%2C%20%40zelda%22%0A%09%09%7D%2C%0A%09%09%22accessory%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22button%22%2C%0A%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%22text%22%3A%20%22Choose%22%0A%09%09%09%7D%2C%0A%09%09%09%22value%22%3A%20%22click_me_123%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*Tomorrow%20-%204-4%3A30pm*%5CnEveryone%20is%20available%3A%20%40iris%2C%20%40zelda%22%0A%09%09%7D%2C%0A%09%09%22accessory%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22button%22%2C%0A%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%22text%22%3A%20%22Choose%22%0A%09%09%09%7D%2C%0A%09%09%09%22value%22%3A%20%22click_me_123%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*Tomorrow%20-%206-6%3A30pm*%5CnSome%20people%20aren%27t%20available%3A%20%40iris%2C%20~%40zelda~%22%0A%09%09%7D%2C%0A%09%09%22accessory%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22button%22%2C%0A%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%22text%22%3A%20%22Choose%22%0A%09%09%09%7D%2C%0A%09%09%09%22value%22%3A%20%22click_me_123%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*%3Cfakelink.ToMoreTimes.com%7CShow%20more%20times%3E*%22%0A%09%09%7D%0A%09%7D%0A%5D) on the block builder website. Refer to the function `exampleThree` for details on how this block can be generated.
diff --git a/examples/buttons/buttons.go b/examples/buttons/buttons.go
index 82772fa..b5ef09c 100644
--- a/examples/buttons/buttons.go
+++ b/examples/buttons/buttons.go
@@ -50,7 +50,7 @@ func main() {
 	if err != nil {
 		fmt.Printf("Could not send message: %v", err)
 	}
-	fmt.Printf("Message with buttons sucessfully sent to channel %s at %s", channelID, timestamp)
+	fmt.Printf("Message with buttons successfully sent to channel %s at %s", channelID, timestamp)
 	http.HandleFunc("/actions", actionHandler)
 	http.ListenAndServe(":3000", nil)
 }
diff --git a/examples/manifests/README.md b/examples/manifests/README.md
new file mode 100644
index 0000000..ba6db23
--- /dev/null
+++ b/examples/manifests/README.md
@@ -0,0 +1,38 @@
+# Manifest examples
+
+This example shows how to interact with the
+new [manifest endpoints](https://api.slack.com/reference/manifests#manifest_apis). These endpoints require a special set
+of tokens called `configuration tokens`. Refer to
+the [relevant documentation](https://api.slack.com/authentication/config-tokens) for how to create these tokens.
+
+For examples on how to use configuration tokens, see the [tokens example](../tokens).
+
+## Usage info
+
+The manifest endpoints allow you to configure your application programmatically instead of manually creating
+a `manifest.yaml` file and uploading it on your Slack application's dashboard.
+
+A manifest should follow a specific structure and has a handful of required fields. These are describe in
+the [manifest documentation](https://api.slack.com/reference/manifests#fields), but Slack additionally returns very
+informative error messages for malformed templates to help you pin down what the issue is. The library itself does not
+attempt to perform any form of validation on your manifest.
+
+**Note that each configuration token may only be used once before being invalidated. Again refer to the tokens example
+for more information.**
+
+## Available methods
+
+- ``Slack.CreateManifest()``
+- ``Slack.DeleteManifest()``
+- ``Slack.ExportManifest()``
+- ``Slack.UpdateManifest()``
+
+## Example details
+
+The example code here only shows how to _update_ an application using a manifest. The other available methods are either
+identical in usage or trivial to use, so no full example is provided for them.
+
+The example doesn't rotate the configuration tokens after updating the manifest. **You should almost always do this**.
+Your access token is invalidated after sending a request, and rotating your tokens will allow you to make another
+request in the future. This example does not do this explicitly as it would just repeat the tokens example. For sake of
+simplicity, it only focuses on the manifest part.
diff --git a/examples/manifests/manifest.go b/examples/manifests/manifest.go
new file mode 100644
index 0000000..bfcfa9c
--- /dev/null
+++ b/examples/manifests/manifest.go
@@ -0,0 +1,45 @@
+package manifests
+
+import (
+	"fmt"
+	"github.com/slack-go/slack"
+)
+
+// createManifest programmatically creates a Slack app manifest
+func createManifest() *slack.Manifest {
+	return &slack.Manifest{
+		Display: slack.Display{
+			Name: "Your Application",
+		},
+		// ... other configuration here
+	}
+}
+
+func main() {
+	api := slack.New(
+		"YOUR_TOKEN_HERE",
+		// You may choose to provide your access token when creating your Slack client
+		// or when invoking the method calls
+		slack.OptionConfigToken("YOUR_CONFIG_ACCESS_TOKEN_HERE"),
+	)
+
+	// Create a new Manifest object
+	manifest := createManifest()
+
+	// Update your application using the new manifest
+	// You may pass your token as a parameter here as well, if you didn't do it above
+	response, err := api.UpdateManifest(manifest, "", "YOUR_APP_ID_HERE")
+	if err != nil {
+		fmt.Printf("error updating Slack application: %v\n", err)
+		return
+	}
+
+	if !response.Ok {
+		fmt.Printf("unable to update Slack application: %v\n", response.Errors)
+	}
+
+	fmt.Println("successfully updated Slack application")
+
+	// The access token is now invalid, so it should be rotated for future use
+	// Refer to the examples about tokens for more details
+}
diff --git a/examples/modal/modal.go b/examples/modal/modal.go
index e628e85..155f2f7 100644
--- a/examples/modal/modal.go
+++ b/examples/modal/modal.go
@@ -19,6 +19,7 @@ import (
 	"net/http"
 
 	"github.com/slack-go/slack"
+	"time"
 )
 
 func generateModalRequest() slack.ModalViewRequest {
@@ -60,6 +61,30 @@ func generateModalRequest() slack.ModalViewRequest {
 	return modalRequest
 }
 
+func updateModal() slack.ModalViewRequest {
+	// Create a ModalViewRequest with a header and two inputs
+	titleText := slack.NewTextBlockObject("plain_text", "My App", false, false)
+	closeText := slack.NewTextBlockObject("plain_text", "Close", false, false)
+	submitText := slack.NewTextBlockObject("plain_text", "Submit", false, false)
+
+	headerText := slack.NewTextBlockObject("mrkdwn", "Modal updated!", false, false)
+	headerSection := slack.NewSectionBlock(headerText, nil, nil)
+
+	blocks := slack.Blocks{
+		BlockSet: []slack.Block{
+			headerSection,
+		},
+	}
+
+	var modalRequest slack.ModalViewRequest
+	modalRequest.Type = slack.ViewType("modal")
+	modalRequest.Title = titleText
+	modalRequest.Close = closeText
+	modalRequest.Submit = submitText
+	modalRequest.Blocks = blocks
+	return modalRequest
+}
+
 // This was taken from the slash example
 // https://github.com/slack-go/slack/blob/master/examples/slash/slash.go
 func verifySigningSecret(r *http.Request) error {
@@ -104,7 +129,7 @@ func handleSlash(w http.ResponseWriter, r *http.Request) {
 	}
 
 	switch s.Command {
-	case "/humboldttest":
+	case "/slash":
 		api := slack.New("YOUR_TOKEN_HERE")
 		modalRequest := generateModalRequest()
 		_, err = api.OpenView(s.TriggerID, modalRequest)
@@ -134,21 +159,30 @@ func handleModal(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	// Note there might be a better way to get this info, but I figured this structure out from looking at the json response
-	firstName := i.View.State.Values["First Name"]["firstName"].Value
-	lastName := i.View.State.Values["Last Name"]["lastName"].Value
-
-	msg := fmt.Sprintf("Hello %s %s, nice to meet you!", firstName, lastName)
-
 	api := slack.New("YOUR_TOKEN_HERE")
-	_, _, err = api.PostMessage(i.User.ID,
-		slack.MsgOptionText(msg, false),
-		slack.MsgOptionAttachments())
 	if err != nil {
 		fmt.Printf(err.Error())
 		w.WriteHeader(http.StatusUnauthorized)
 		return
 	}
+
+	// update modal sample
+	switch i.Type {
+	//update when interaction type is view_submission
+	case slack.InteractionTypeViewSubmission:
+		//you can use any modal you want to show to users just like creating modal.
+		updateModal := updateModal()
+		// You must set one of external_id or view_id and you can use hash for avoiding race condition.
+		// More details: https://api.slack.com/surfaces/modals/using#updating_apis
+		_, err := api.UpdateView(updateModal, "", i.View.Hash, i.View.ID)
+		// Wait for a few seconds to see result this code is necesarry due to slack server modal is going to be closed after the update
+		time.Sleep(time.Second * 2)
+		if err != nil {
+			fmt.Printf("Error updating view: %s", err)
+			w.WriteHeader(http.StatusInternalServerError)
+			return
+		}
+	}
 }
 
 func main() {
diff --git a/examples/pagination/pagination.go b/examples/pagination/pagination.go
new file mode 100644
index 0000000..2659101
--- /dev/null
+++ b/examples/pagination/pagination.go
@@ -0,0 +1,59 @@
+package main
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"time"
+
+	"github.com/slack-go/slack"
+)
+
+func getAllUserUIDs(ctx context.Context, client *slack.Client, pageSize int) ([]string, error) {
+	var uids []string
+	var err error
+
+	pages := 0
+	pager := client.GetUsersPaginated(slack.GetUsersOptionLimit(pageSize))
+	for {
+		// Note reassignment of pager to the value returned by Next()
+		pager, err = pager.Next(ctx)
+		if failedErr := pager.Failure(err); failedErr != nil {
+			var rateLimited *slack.RateLimitedError
+			if errors.As(failedErr, &rateLimited) && rateLimited.Retryable() {
+				fmt.Println("Rate limited by Slack API; sleeping", rateLimited.RetryAfter)
+				select {
+				case <-ctx.Done():
+					return uids, ctx.Err()
+				case <-time.After(rateLimited.RetryAfter):
+					continue
+				}
+			}
+			return uids, fmt.Errorf("paginating users: %w", failedErr)
+		}
+		if pager.Done(err) {
+			break
+		}
+
+		for _, user := range pager.Users {
+			uids = append(uids, user.ID)
+		}
+
+		pages++
+	}
+
+	fmt.Printf("Pagination complete after %d pages\n", pages)
+
+	return uids, nil
+}
+
+func main() {
+	client := slack.New("YOUR_TOKEN_HERE")
+
+	uids, err := getAllUserUIDs(context.Background(), client, 1000)
+	if err != nil {
+		panic(err)
+	}
+
+	fmt.Printf("Collected %d UIDs\n", len(uids))
+}
diff --git a/examples/team/team.go b/examples/team/team.go
index 8d2fcdb..cba71aa 100644
--- a/examples/team/team.go
+++ b/examples/team/team.go
@@ -9,17 +9,16 @@ import (
 func main() {
 	api := slack.New("YOUR_TOKEN_HERE")
 	//Example for single user
-	billingActive, err := api.GetBillableInfo("U023BECGF")
+	billingActive, err := api.GetBillableInfo(slack.GetBillableInfoParams{User: "U023BECGF"})
 	if err != nil {
 		fmt.Printf("%s\n", err)
 		return
 	}
 	fmt.Printf("ID: U023BECGF, BillingActive: %v\n\n\n", billingActive["U023BECGF"])
 
-	//Example for team
-	billingActiveForTeam, _ := api.GetBillableInfoForTeam()
+	//Example for team. Note: passing empty TeamID just uses the current user team.
+	billingActiveForTeam, _ := api.GetBillableInfo(slack.GetBillableInfoParams{})
 	for id, value := range billingActiveForTeam {
 		fmt.Printf("ID: %v, BillingActive: %v\n", id, value)
 	}
-
 }
diff --git a/examples/tokens/README.md b/examples/tokens/README.md
new file mode 100644
index 0000000..7e8e163
--- /dev/null
+++ b/examples/tokens/README.md
@@ -0,0 +1,10 @@
+# Tokens examples
+
+The refresh token endpoint can be used to update
+your [configuration tokenset](https://api.slack.com/authentication/config-tokens). These tokens may only be used **once
+** before being invalidated, and are only valid for up to **12 hours**.
+
+Once a token has been used, or before it expires, you can use the `RotateTokens()` method to obtain a fresh set to use
+for the next request. Depending on your use-case you may want to store these somewhere for a future run, so they are
+only returned by the method call. If you wish to update the tokens inside the active Slack client, this can be done
+using `UpdateConfigTokens()`.
diff --git a/examples/tokens/tokens.go b/examples/tokens/tokens.go
new file mode 100644
index 0000000..63d46d2
--- /dev/null
+++ b/examples/tokens/tokens.go
@@ -0,0 +1,33 @@
+package tokens
+
+import (
+	"fmt"
+	"github.com/slack-go/slack"
+)
+
+func main() {
+	api := slack.New(
+		"YOUR_TOKEN_HERE",
+		// You may choose to provide your config tokens when creating your Slack client
+		// or when invoking the method calls
+		slack.OptionConfigToken("YOUR_CONFIG_ACCESS_TOKEN_HERE"),
+		slack.OptionConfigRefreshToken("YOUR_REFRESH_TOKEN_HERE"),
+	)
+
+	// Obtain a fresh set of tokens
+	// You may pass your tokens as a parameter here as well, if you didn't do it above
+	freshTokens, err := api.RotateTokens("", "")
+	if err != nil {
+		fmt.Printf("error rotating tokens: %v\n", err)
+		return
+	}
+
+	fmt.Printf("new access token: %s\n", freshTokens.Token)
+	fmt.Printf("new refresh token: %s\n", freshTokens.RefreshToken)
+	fmt.Printf("new tokenset expires at: %d\n", freshTokens.ExpiresAt)
+
+	// Optionally: update the tokens inside the running Slack client
+	// This isn't necessary if you restart the application after storing the tokens elsewhere,
+	// or pass them as parameters to RotateTokens() explicitly
+	api.UpdateConfigTokens(freshTokens)
+}
diff --git a/files.go b/files.go
index 3562844..6844edd 100644
--- a/files.go
+++ b/files.go
@@ -11,7 +11,7 @@ import (
 )
 
 const (
-	// Add here the defaults in the siten
+	// Add here the defaults in the site
 	DEFAULT_FILES_USER        = ""
 	DEFAULT_FILES_CHANNEL     = ""
 	DEFAULT_FILES_TS_FROM     = 0
@@ -129,6 +129,7 @@ type FileUploadParameters struct {
 type GetFilesParameters struct {
 	User          string
 	Channel       string
+	TeamID        string
 	TimestampFrom JSONTime
 	TimestampTo   JSONTime
 	Types         string
@@ -142,6 +143,7 @@ type ListFilesParameters struct {
 	Limit   int
 	User    string
 	Channel string
+	TeamID  string
 	Types   string
 	Cursor  string
 }
@@ -232,12 +234,14 @@ func (api *Client) fileRequest(ctx context.Context, path string, values url.Valu
 	return response, response.Err()
 }
 
-// GetFileInfo retrieves a file and related comments
+// GetFileInfo retrieves a file and related comments.
+// For more details, see GetFileInfoContext documentation.
 func (api *Client) GetFileInfo(fileID string, count, page int) (*File, []Comment, *Paging, error) {
 	return api.GetFileInfoContext(context.Background(), fileID, count, page)
 }
 
-// GetFileInfoContext retrieves a file and related comments with a custom context
+// GetFileInfoContext retrieves a file and related comments with a custom context.
+// Slack API docs: https://api.slack.com/methods/files.info
 func (api *Client) GetFileInfoContext(ctx context.Context, fileID string, count, page int) (*File, []Comment, *Paging, error) {
 	values := url.Values{
 		"token": {api.token},
@@ -253,24 +257,25 @@ func (api *Client) GetFileInfoContext(ctx context.Context, fileID string, count,
 	return &response.File, response.Comments, &response.Paging, nil
 }
 
-// GetFile retreives a given file from its private download URL
+// GetFile retrieves a given file from its private download URL.
 func (api *Client) GetFile(downloadURL string, writer io.Writer) error {
 	return api.GetFileContext(context.Background(), downloadURL, writer)
 }
 
-// GetFileContext retreives a given file from its private download URL with a custom context
-//
+// GetFileContext retrieves a given file from its private download URL with a custom context.
 // For more details, see GetFile documentation.
 func (api *Client) GetFileContext(ctx context.Context, downloadURL string, writer io.Writer) error {
 	return downloadFile(ctx, api.httpclient, api.token, downloadURL, writer, api)
 }
 
-// GetFiles retrieves all files according to the parameters given
+// GetFiles retrieves all files according to the parameters given.
+// For more details, see GetFilesContext documentation.
 func (api *Client) GetFiles(params GetFilesParameters) ([]File, *Paging, error) {
 	return api.GetFilesContext(context.Background(), params)
 }
 
-// GetFilesContext retrieves all files according to the parameters given with a custom context
+// GetFilesContext retrieves all files according to the parameters given with a custom context.
+// Slack API docs: https://api.slack.com/methods/files.list
 func (api *Client) GetFilesContext(ctx context.Context, params GetFilesParameters) ([]File, *Paging, error) {
 	values := url.Values{
 		"token": {api.token},
@@ -281,6 +286,9 @@ func (api *Client) GetFilesContext(ctx context.Context, params GetFilesParameter
 	if params.Channel != DEFAULT_FILES_CHANNEL {
 		values.Add("channel", params.Channel)
 	}
+	if params.TeamID != "" {
+		values.Add("team_id", params.TeamID)
+	}
 	if params.TimestampFrom != DEFAULT_FILES_TS_FROM {
 		values.Add("ts_from", strconv.FormatInt(int64(params.TimestampFrom), 10))
 	}
@@ -308,13 +316,13 @@ func (api *Client) GetFilesContext(ctx context.Context, params GetFilesParameter
 }
 
 // ListFiles retrieves all files according to the parameters given. Uses cursor based pagination.
+// For more details, see ListFilesContext documentation.
 func (api *Client) ListFiles(params ListFilesParameters) ([]File, *ListFilesParameters, error) {
 	return api.ListFilesContext(context.Background(), params)
 }
 
 // ListFilesContext retrieves all files according to the parameters given with a custom context.
-//
-// For more details, see ListFiles documentation.
+// Slack API docs: https://api.slack.com/methods/files.list
 func (api *Client) ListFilesContext(ctx context.Context, params ListFilesParameters) ([]File, *ListFilesParameters, error) {
 	values := url.Values{
 		"token": {api.token},
@@ -326,6 +334,9 @@ func (api *Client) ListFilesContext(ctx context.Context, params ListFilesParamet
 	if params.Channel != DEFAULT_FILES_CHANNEL {
 		values.Add("channel", params.Channel)
 	}
+	if params.TeamID != "" {
+		values.Add("team_id", params.TeamID)
+	}
 	if params.Limit != DEFAULT_FILES_COUNT {
 		values.Add("limit", strconv.Itoa(params.Limit))
 	}
@@ -343,12 +354,16 @@ func (api *Client) ListFilesContext(ctx context.Context, params ListFilesParamet
 	return response.Files, &params, nil
 }
 
-// UploadFile uploads a file
+// UploadFile uploads a file.
+// DEPRECATED: Use UploadFileV2 instead. This will stop functioning on March 11, 2025.
+// For more details, see: https://api.slack.com/methods/files.upload#markdown
 func (api *Client) UploadFile(params FileUploadParameters) (file *File, err error) {
 	return api.UploadFileContext(context.Background(), params)
 }
 
-// UploadFileContext uploads a file and setting a custom context
+// UploadFileContext uploads a file and setting a custom context.
+// DEPRECATED: Use UploadFileV2Context instead. This will stop functioning on March 11, 2025.
+// For more details, see: https://api.slack.com/methods/files.upload#markdown
 func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParameters) (file *File, err error) {
 	// Test if user token is valid. This helps because client.Do doesn't like this for some reason. XXX: More
 	// investigation needed, but for now this will do.
@@ -396,12 +411,14 @@ func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParam
 	return &response.File, response.Err()
 }
 
-// DeleteFileComment deletes a file's comment
+// DeleteFileComment deletes a file's comment.
+// For more details, see DeleteFileCommentContext documentation.
 func (api *Client) DeleteFileComment(commentID, fileID string) error {
 	return api.DeleteFileCommentContext(context.Background(), fileID, commentID)
 }
 
-// DeleteFileCommentContext deletes a file's comment with a custom context
+// DeleteFileCommentContext deletes a file's comment with a custom context.
+// Slack API docs: https://api.slack.com/methods/files.comments.delete
 func (api *Client) DeleteFileCommentContext(ctx context.Context, fileID, commentID string) (err error) {
 	if fileID == "" || commentID == "" {
 		return ErrParametersMissing
@@ -416,12 +433,14 @@ func (api *Client) DeleteFileCommentContext(ctx context.Context, fileID, comment
 	return err
 }
 
-// DeleteFile deletes a file
+// DeleteFile deletes a file.
+// For more details, see DeleteFileContext documentation.
 func (api *Client) DeleteFile(fileID string) error {
 	return api.DeleteFileContext(context.Background(), fileID)
 }
 
-// DeleteFileContext deletes a file with a custom context
+// DeleteFileContext deletes a file with a custom context.
+// Slack API docs: https://api.slack.com/methods/files.delete
 func (api *Client) DeleteFileContext(ctx context.Context, fileID string) (err error) {
 	values := url.Values{
 		"token": {api.token},
@@ -432,12 +451,14 @@ func (api *Client) DeleteFileContext(ctx context.Context, fileID string) (err er
 	return err
 }
 
-// RevokeFilePublicURL disables public/external sharing for a file
+// RevokeFilePublicURL disables public/external sharing for a file.
+// For more details, see RevokeFilePublicURLContext documentation.
 func (api *Client) RevokeFilePublicURL(fileID string) (*File, error) {
 	return api.RevokeFilePublicURLContext(context.Background(), fileID)
 }
 
-// RevokeFilePublicURLContext disables public/external sharing for a file with a custom context
+// RevokeFilePublicURLContext disables public/external sharing for a file with a custom context.
+// Slack API docs: https://api.slack.com/methods/files.revokePublicURL
 func (api *Client) RevokeFilePublicURLContext(ctx context.Context, fileID string) (*File, error) {
 	values := url.Values{
 		"token": {api.token},
@@ -451,12 +472,14 @@ func (api *Client) RevokeFilePublicURLContext(ctx context.Context, fileID string
 	return &response.File, nil
 }
 
-// ShareFilePublicURL enabled public/external sharing for a file
+// ShareFilePublicURL enabled public/external sharing for a file.
+// For more details, see ShareFilePublicURLContext documentation.
 func (api *Client) ShareFilePublicURL(fileID string) (*File, []Comment, *Paging, error) {
 	return api.ShareFilePublicURLContext(context.Background(), fileID)
 }
 
-// ShareFilePublicURLContext enabled public/external sharing for a file with a custom context
+// ShareFilePublicURLContext enabled public/external sharing for a file with a custom context.
+// Slack API docs: https://api.slack.com/methods/files.sharedPublicURL
 func (api *Client) ShareFilePublicURLContext(ctx context.Context, fileID string) (*File, []Comment, *Paging, error) {
 	values := url.Values{
 		"token": {api.token},
@@ -470,7 +493,7 @@ func (api *Client) ShareFilePublicURLContext(ctx context.Context, fileID string)
 	return &response.File, response.Comments, &response.Paging, nil
 }
 
-// getUploadURLExternal gets a URL and fileID from slack which can later be used to upload a file
+// getUploadURLExternal gets a URL and fileID from slack which can later be used to upload a file.
 func (api *Client) getUploadURLExternal(ctx context.Context, params getUploadURLExternalParameters) (*getUploadURLExternalResponse, error) {
 	values := url.Values{
 		"token":    {api.token},
@@ -496,9 +519,8 @@ func (api *Client) getUploadURLExternal(ctx context.Context, params getUploadURL
 func (api *Client) uploadToURL(ctx context.Context, params uploadToURLParameters) (err error) {
 	values := url.Values{}
 	if params.Content != "" {
-		values.Add("content", params.Content)
-		values.Add("token", api.token)
-		err = postForm(ctx, api.httpclient, params.UploadURL, values, nil, api)
+		contentReader := strings.NewReader(params.Content)
+		err = postWithMultipartResponse(ctx, api.httpclient, params.UploadURL, params.Filename, "file", api.token, values, contentReader, nil, api)
 	} else if params.File != "" {
 		err = postLocalWithMultipartResponse(ctx, api.httpclient, params.UploadURL, params.File, "file", api.token, values, nil, api)
 	} else if params.Reader != nil {
@@ -515,11 +537,13 @@ func (api *Client) completeUploadExternal(ctx context.Context, fileID string, pa
 		return nil, err
 	}
 	values := url.Values{
-		"token":      {api.token},
-		"files":      {string(requestBytes)},
-		"channel_id": {params.channel},
+		"token": {api.token},
+		"files": {string(requestBytes)},
 	}
 
+	if params.channel != "" {
+		values.Add("channel_id", params.channel)
+	}
 	if params.initialComment != "" {
 		values.Add("initial_comment", params.initialComment)
 	}
@@ -537,18 +561,18 @@ func (api *Client) completeUploadExternal(ctx context.Context, fileID string, pa
 	return response, nil
 }
 
-// UploadFileV2 uploads file to a given slack channel using 3 steps -
-//  1. Get an upload URL using files.getUploadURLExternal API
-//  2. Send the file as a post to the URL provided by slack
-//  3. Complete the upload and share it to the specified channel using files.completeUploadExternal
+// UploadFileV2 uploads file to a given slack channel using 3 steps.
+// For more details, see UploadFileV2Context documentation.
 func (api *Client) UploadFileV2(params UploadFileV2Parameters) (*FileSummary, error) {
 	return api.UploadFileV2Context(context.Background(), params)
 }
 
-// UploadFileV2 uploads file to a given slack channel using 3 steps with a custom context -
+// UploadFileV2Context uploads file to a given slack channel using 3 steps -
 //  1. Get an upload URL using files.getUploadURLExternal API
 //  2. Send the file as a post to the URL provided by slack
 //  3. Complete the upload and share it to the specified channel using files.completeUploadExternal
+//
+// Slack Docs: https://api.slack.com/messaging/files#uploading_files
 func (api *Client) UploadFileV2Context(ctx context.Context, params UploadFileV2Parameters) (file *FileSummary, err error) {
 	if params.Filename == "" {
 		return nil, fmt.Errorf("file.upload.v2: filename cannot be empty")
@@ -556,9 +580,7 @@ func (api *Client) UploadFileV2Context(ctx context.Context, params UploadFileV2P
 	if params.FileSize == 0 {
 		return nil, fmt.Errorf("file.upload.v2: file size cannot be 0")
 	}
-	if params.Channel == "" {
-		return nil, fmt.Errorf("file.upload.v2: channel cannot be empty")
-	}
+
 	u, err := api.getUploadURLExternal(ctx, getUploadURLExternalParameters{
 		altText:     params.AltTxt,
 		fileName:    params.Filename,
diff --git a/interactions.go b/interactions.go
index e362caa..8c64147 100644
--- a/interactions.go
+++ b/interactions.go
@@ -33,29 +33,30 @@ const (
 
 // InteractionCallback is sent from slack when a user interactions with a button or dialog.
 type InteractionCallback struct {
-	Type            InteractionType         `json:"type"`
-	Token           string                  `json:"token"`
-	CallbackID      string                  `json:"callback_id"`
-	ResponseURL     string                  `json:"response_url"`
-	TriggerID       string                  `json:"trigger_id"`
-	ActionTs        string                  `json:"action_ts"`
-	Team            Team                    `json:"team"`
-	Channel         Channel                 `json:"channel"`
-	User            User                    `json:"user"`
-	OriginalMessage Message                 `json:"original_message"`
-	Message         Message                 `json:"message"`
-	Name            string                  `json:"name"`
-	Value           string                  `json:"value"`
-	MessageTs       string                  `json:"message_ts"`
-	AttachmentID    string                  `json:"attachment_id"`
-	ActionCallback  ActionCallbacks         `json:"actions"`
-	View            View                    `json:"view"`
-	ActionID        string                  `json:"action_id"`
-	APIAppID        string                  `json:"api_app_id"`
-	BlockID         string                  `json:"block_id"`
-	Container       Container               `json:"container"`
-	Enterprise      Enterprise              `json:"enterprise"`
-	WorkflowStep    InteractionWorkflowStep `json:"workflow_step"`
+	Type                InteractionType         `json:"type"`
+	Token               string                  `json:"token"`
+	CallbackID          string                  `json:"callback_id"`
+	ResponseURL         string                  `json:"response_url"`
+	TriggerID           string                  `json:"trigger_id"`
+	ActionTs            string                  `json:"action_ts"`
+	Team                Team                    `json:"team"`
+	Channel             Channel                 `json:"channel"`
+	User                User                    `json:"user"`
+	OriginalMessage     Message                 `json:"original_message"`
+	Message             Message                 `json:"message"`
+	Name                string                  `json:"name"`
+	Value               string                  `json:"value"`
+	MessageTs           string                  `json:"message_ts"`
+	AttachmentID        string                  `json:"attachment_id"`
+	ActionCallback      ActionCallbacks         `json:"actions"`
+	View                View                    `json:"view"`
+	ActionID            string                  `json:"action_id"`
+	APIAppID            string                  `json:"api_app_id"`
+	BlockID             string                  `json:"block_id"`
+	Container           Container               `json:"container"`
+	Enterprise          Enterprise              `json:"enterprise"`
+	IsEnterpriseInstall bool                    `json:"is_enterprise_install"`
+	WorkflowStep        InteractionWorkflowStep `json:"workflow_step"`
 	DialogSubmissionCallback
 	ViewSubmissionCallback
 	ViewClosedCallback
diff --git a/manifests.go b/manifests.go
new file mode 100644
index 0000000..0a972a2
--- /dev/null
+++ b/manifests.go
@@ -0,0 +1,297 @@
+package slack
+
+import (
+	"context"
+	"encoding/json"
+	"net/url"
+)
+
+// Manifest is an application manifest schema
+type Manifest struct {
+	Metadata    ManifestMetadata `json:"_metadata,omitempty" yaml:"_metadata,omitempty"`
+	Display     Display          `json:"display_information" yaml:"display_information"`
+	Settings    Settings         `json:"settings,omitempty" yaml:"settings,omitempty"`
+	Features    Features         `json:"features,omitempty" yaml:"features,omitempty"`
+	OAuthConfig OAuthConfig      `json:"oauth_config,omitempty" yaml:"oauth_config,omitempty"`
+}
+
+// CreateManifest creates an app from an app manifest.
+// For more details, see CreateManifestContext documentation.
+func (api *Client) CreateManifest(manifest *Manifest, token string) (*ManifestResponse, error) {
+	return api.CreateManifestContext(context.Background(), manifest, token)
+}
+
+// CreateManifestContext creates an app from an app manifest with a custom context.
+// Slack API docs: https://api.slack.com/methods/apps.manifest.create
+func (api *Client) CreateManifestContext(ctx context.Context, manifest *Manifest, token string) (*ManifestResponse, error) {
+	if token == "" {
+		token = api.configToken
+	}
+
+	jsonBytes, err := json.Marshal(manifest)
+	if err != nil {
+		return nil, err
+	}
+
+	values := url.Values{
+		"token":    {token},
+		"manifest": {string(jsonBytes)},
+	}
+
+	response := &ManifestResponse{}
+	err = api.postMethod(ctx, "apps.manifest.create", values, response)
+	if err != nil {
+		return nil, err
+	}
+
+	return response, response.Err()
+}
+
+// DeleteManifest permanently deletes an app created through app manifests.
+// For more details, see DeleteManifestContext documentation.
+func (api *Client) DeleteManifest(token string, appId string) (*SlackResponse, error) {
+	return api.DeleteManifestContext(context.Background(), token, appId)
+}
+
+// DeleteManifestContext permanently deletes an app created through app manifests with a custom context.
+// Slack API docs: https://api.slack.com/methods/apps.manifest.delete
+func (api *Client) DeleteManifestContext(ctx context.Context, token string, appId string) (*SlackResponse, error) {
+	if token == "" {
+		token = api.configToken
+	}
+
+	values := url.Values{
+		"token":  {token},
+		"app_id": {appId},
+	}
+
+	response := &SlackResponse{}
+	err := api.postMethod(ctx, "apps.manifest.delete", values, response)
+	if err != nil {
+		return nil, err
+	}
+
+	return response, response.Err()
+}
+
+// ExportManifest exports an app manifest from an existing app.
+// For more details, see ExportManifestContext documentation.
+func (api *Client) ExportManifest(token string, appId string) (*Manifest, error) {
+	return api.ExportManifestContext(context.Background(), token, appId)
+}
+
+// ExportManifestContext exports an app manifest from an existing app with a custom context.
+// Slack API docs: https://api.slack.com/methods/apps.manifest.export
+func (api *Client) ExportManifestContext(ctx context.Context, token string, appId string) (*Manifest, error) {
+	if token == "" {
+		token = api.configToken
+	}
+
+	values := url.Values{
+		"token":  {token},
+		"app_id": {appId},
+	}
+
+	response := &ExportManifestResponse{}
+	err := api.postMethod(ctx, "apps.manifest.export", values, response)
+	if err != nil {
+		return nil, err
+	}
+
+	return &response.Manifest, response.Err()
+}
+
+// UpdateManifest updates an app from an app manifest.
+// For more details, see UpdateManifestContext documentation.
+func (api *Client) UpdateManifest(manifest *Manifest, token string, appId string) (*UpdateManifestResponse, error) {
+	return api.UpdateManifestContext(context.Background(), manifest, token, appId)
+}
+
+// UpdateManifestContext updates an app from an app manifest with a custom context.
+// Slack API docs: https://api.slack.com/methods/apps.manifest.update
+func (api *Client) UpdateManifestContext(ctx context.Context, manifest *Manifest, token string, appId string) (*UpdateManifestResponse, error) {
+	if token == "" {
+		token = api.configToken
+	}
+
+	jsonBytes, err := json.Marshal(manifest)
+	if err != nil {
+		return nil, err
+	}
+
+	values := url.Values{
+		"token":    {token},
+		"app_id":   {appId},
+		"manifest": {string(jsonBytes)},
+	}
+
+	response := &UpdateManifestResponse{}
+	err = api.postMethod(ctx, "apps.manifest.update", values, response)
+	if err != nil {
+		return nil, err
+	}
+
+	return response, response.Err()
+}
+
+// ValidateManifest sends a request to apps.manifest.validate to validate your app manifest.
+// For more details, see ValidateManifestContext documentation.
+func (api *Client) ValidateManifest(manifest *Manifest, token string, appId string) (*ManifestResponse, error) {
+	return api.ValidateManifestContext(context.Background(), manifest, token, appId)
+}
+
+// ValidateManifestContext sends a request to apps.manifest.validate to validate your app manifest with a custom context.
+// Slack API docs: https://api.slack.com/methods/apps.manifest.validate
+func (api *Client) ValidateManifestContext(ctx context.Context, manifest *Manifest, token string, appId string) (*ManifestResponse, error) {
+	if token == "" {
+		token = api.configToken
+	}
+
+	// Marshal manifest into string
+	jsonBytes, err := json.Marshal(manifest)
+	if err != nil {
+		return nil, err
+	}
+
+	values := url.Values{
+		"token":    {token},
+		"manifest": {string(jsonBytes)},
+	}
+
+	if appId != "" {
+		values.Add("app_id", appId)
+	}
+
+	response := &ManifestResponse{}
+	err = api.postMethod(ctx, "apps.manifest.validate", values, response)
+	if err != nil {
+		return nil, err
+	}
+
+	return response, response.Err()
+}
+
+// ManifestMetadata is a group of settings that describe the manifest
+type ManifestMetadata struct {
+	MajorVersion int `json:"major_version,omitempty" yaml:"major_version,omitempty"`
+	MinorVersion int `json:"minor_version,omitempty" yaml:"minor_version,omitempty"`
+}
+
+// Display is a group of settings that describe parts of an app's appearance within Slack
+type Display struct {
+	Name            string `json:"name" yaml:"name"`
+	Description     string `json:"description,omitempty" yaml:"description,omitempty"`
+	LongDescription string `json:"long_description,omitempty" yaml:"long_description,omitempty"`
+	BackgroundColor string `json:"background_color,omitempty" yaml:"background_color,omitempty"`
+}
+
+// Settings is a group of settings corresponding to the Settings section of the app config pages.
+type Settings struct {
+	AllowedIPAddressRanges []string           `json:"allowed_ip_address_ranges,omitempty" yaml:"allowed_ip_address_ranges,omitempty"`
+	EventSubscriptions     EventSubscriptions `json:"event_subscriptions,omitempty" yaml:"event_subscriptions,omitempty"`
+	Interactivity          Interactivity      `json:"interactivity,omitempty" yaml:"interactivity,omitempty"`
+	OrgDeployEnabled       bool               `json:"org_deploy_enabled,omitempty" yaml:"org_deploy_enabled,omitempty"`
+	SocketModeEnabled      bool               `json:"socket_mode_enabled,omitempty" yaml:"socket_mode_enabled,omitempty"`
+}
+
+// EventSubscriptions is a group of settings that describe the Events API configuration
+type EventSubscriptions struct {
+	RequestUrl string   `json:"request_url,omitempty" yaml:"request_url,omitempty"`
+	BotEvents  []string `json:"bot_events,omitempty" yaml:"bot_events,omitempty"`
+	UserEvents []string `json:"user_events,omitempty" yaml:"user_events,omitempty"`
+}
+
+// Interactivity is a group of settings that describe the interactivity configuration
+type Interactivity struct {
+	IsEnabled             bool   `json:"is_enabled" yaml:"is_enabled"`
+	RequestUrl            string `json:"request_url,omitempty" yaml:"request_url,omitempty"`
+	MessageMenuOptionsUrl string `json:"message_menu_options_url,omitempty" yaml:"message_menu_options_url,omitempty"`
+}
+
+// Features is a group of settings corresponding to the Features section of the app config pages
+type Features struct {
+	AppHome       AppHome                `json:"app_home,omitempty" yaml:"app_home,omitempty"`
+	BotUser       BotUser                `json:"bot_user,omitempty" yaml:"bot_user,omitempty"`
+	Shortcuts     []Shortcut             `json:"shortcuts,omitempty" yaml:"shortcuts,omitempty"`
+	SlashCommands []ManifestSlashCommand `json:"slash_commands,omitempty" yaml:"slash_commands,omitempty"`
+	WorkflowSteps []WorkflowStep         `json:"workflow_steps,omitempty" yaml:"workflow_steps,omitempty"`
+}
+
+// AppHome is a group of settings that describe the App Home configuration
+type AppHome struct {
+	HomeTabEnabled             bool `json:"home_tab_enabled,omitempty" yaml:"home_tab_enabled,omitempty"`
+	MessagesTabEnabled         bool `json:"messages_tab_enabled,omitempty" yaml:"messages_tab_enabled,omitempty"`
+	MessagesTabReadOnlyEnabled bool `json:"messages_tab_read_only_enabled,omitempty" yaml:"messages_tab_read_only_enabled,omitempty"`
+}
+
+// BotUser is a group of settings that describe bot user configuration
+type BotUser struct {
+	DisplayName  string `json:"display_name" yaml:"display_name"`
+	AlwaysOnline bool   `json:"always_online,omitempty" yaml:"always_online,omitempty"`
+}
+
+// Shortcut is a group of settings that describes shortcut configuration
+type Shortcut struct {
+	Name        string       `json:"name" yaml:"name"`
+	CallbackID  string       `json:"callback_id" yaml:"callback_id"`
+	Description string       `json:"description" yaml:"description"`
+	Type        ShortcutType `json:"type" yaml:"type"`
+}
+
+// ShortcutType is a new string type for the available types of shortcuts
+type ShortcutType string
+
+const (
+	MessageShortcut ShortcutType = "message"
+	GlobalShortcut  ShortcutType = "global"
+)
+
+// ManifestSlashCommand is a group of settings that describes slash command configuration
+type ManifestSlashCommand struct {
+	Command      string `json:"command" yaml:"command"`
+	Description  string `json:"description" yaml:"description"`
+	ShouldEscape bool   `json:"should_escape,omitempty" yaml:"should_escape,omitempty"`
+	Url          string `json:"url,omitempty" yaml:"url,omitempty"`
+	UsageHint    string `json:"usage_hint,omitempty" yaml:"usage_hint,omitempty"`
+}
+
+// WorkflowStep is a group of settings that describes workflow steps configuration
+type WorkflowStep struct {
+	Name       string `json:"name" yaml:"name"`
+	CallbackID string `json:"callback_id" yaml:"callback_id"`
+}
+
+// OAuthConfig is a group of settings that describe OAuth configuration for the app
+type OAuthConfig struct {
+	RedirectUrls []string    `json:"redirect_urls,omitempty" yaml:"redirect_urls,omitempty"`
+	Scopes       OAuthScopes `json:"scopes,omitempty" yaml:"scopes,omitempty"`
+}
+
+// OAuthScopes is a group of settings that describe permission scopes configuration
+type OAuthScopes struct {
+	Bot  []string `json:"bot,omitempty" yaml:"bot,omitempty"`
+	User []string `json:"user,omitempty" yaml:"user,omitempty"`
+}
+
+// ManifestResponse is the response returned by the API for apps.manifest.x endpoints
+type ManifestResponse struct {
+	Errors []ManifestValidationError `json:"errors,omitempty"`
+	SlackResponse
+}
+
+// ManifestValidationError is an error message returned for invalid manifests
+type ManifestValidationError struct {
+	Message string `json:"message"`
+	Pointer string `json:"pointer"`
+}
+
+type ExportManifestResponse struct {
+	Manifest Manifest `json:"manifest,omitempty"`
+	SlackResponse
+}
+
+type UpdateManifestResponse struct {
+	AppId              string `json:"app_id,omitempty"`
+	PermissionsUpdated bool   `json:"permissions_updated,omitempty"`
+	ManifestResponse
+}
diff --git a/oauth.go b/oauth.go
index 94b6546..0c77eca 100644
--- a/oauth.go
+++ b/oauth.go
@@ -33,17 +33,18 @@ type OAuthResponse struct {
 
 // OAuthV2Response ...
 type OAuthV2Response struct {
-	AccessToken     string                       `json:"access_token"`
-	TokenType       string                       `json:"token_type"`
-	Scope           string                       `json:"scope"`
-	BotUserID       string                       `json:"bot_user_id"`
-	AppID           string                       `json:"app_id"`
-	Team            OAuthV2ResponseTeam          `json:"team"`
-	IncomingWebhook OAuthResponseIncomingWebhook `json:"incoming_webhook"`
-	Enterprise      OAuthV2ResponseEnterprise    `json:"enterprise"`
-	AuthedUser      OAuthV2ResponseAuthedUser    `json:"authed_user"`
-	RefreshToken    string                       `json:"refresh_token"`
-	ExpiresIn       int                          `json:"expires_in"`
+	AccessToken         string                       `json:"access_token"`
+	TokenType           string                       `json:"token_type"`
+	Scope               string                       `json:"scope"`
+	BotUserID           string                       `json:"bot_user_id"`
+	AppID               string                       `json:"app_id"`
+	Team                OAuthV2ResponseTeam          `json:"team"`
+	IncomingWebhook     OAuthResponseIncomingWebhook `json:"incoming_webhook"`
+	Enterprise          OAuthV2ResponseEnterprise    `json:"enterprise"`
+	IsEnterpriseInstall bool                         `json:"is_enterprise_install"`
+	AuthedUser          OAuthV2ResponseAuthedUser    `json:"authed_user"`
+	RefreshToken        string                       `json:"refresh_token"`
+	ExpiresIn           int                          `json:"expires_in"`
 	SlackResponse
 }
 
@@ -69,12 +70,23 @@ type OAuthV2ResponseAuthedUser struct {
 	TokenType    string `json:"token_type"`
 }
 
-// GetOAuthToken retrieves an AccessToken
+// OpenIDConnectResponse ...
+type OpenIDConnectResponse struct {
+	Ok          bool   `json:"ok"`
+	AccessToken string `json:"access_token"`
+	TokenType   string `json:"token_type"`
+	IdToken     string `json:"id_token"`
+	SlackResponse
+}
+
+// GetOAuthToken retrieves an AccessToken.
+// For more details, see GetOAuthTokenContext documentation.
 func GetOAuthToken(client httpClient, clientID, clientSecret, code, redirectURI string) (accessToken string, scope string, err error) {
 	return GetOAuthTokenContext(context.Background(), client, clientID, clientSecret, code, redirectURI)
 }
 
-// GetOAuthTokenContext retrieves an AccessToken with a custom context
+// GetOAuthTokenContext retrieves an AccessToken with a custom context.
+// For more details, see GetOAuthResponseContext documentation.
 func GetOAuthTokenContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string) (accessToken string, scope string, err error) {
 	response, err := GetOAuthResponseContext(ctx, client, clientID, clientSecret, code, redirectURI)
 	if err != nil {
@@ -84,11 +96,13 @@ func GetOAuthTokenContext(ctx context.Context, client httpClient, clientID, clie
 }
 
 // GetBotOAuthToken retrieves top-level and bot AccessToken - https://api.slack.com/legacy/oauth#bot_user_access_tokens
+// For more details, see GetBotOAuthTokenContext documentation.
 func GetBotOAuthToken(client httpClient, clientID, clientSecret, code, redirectURI string) (accessToken string, scope string, bot OAuthResponseBot, err error) {
 	return GetBotOAuthTokenContext(context.Background(), client, clientID, clientSecret, code, redirectURI)
 }
 
-// GetBotOAuthTokenContext retrieves top-level and bot AccessToken with a custom context
+// GetBotOAuthTokenContext retrieves top-level and bot AccessToken with a custom context.
+// For more details, see GetOAuthResponseContext documentation.
 func GetBotOAuthTokenContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string) (accessToken string, scope string, bot OAuthResponseBot, err error) {
 	response, err := GetOAuthResponseContext(ctx, client, clientID, clientSecret, code, redirectURI)
 	if err != nil {
@@ -97,12 +111,14 @@ func GetBotOAuthTokenContext(ctx context.Context, client httpClient, clientID, c
 	return response.AccessToken, response.Scope, response.Bot, nil
 }
 
-// GetOAuthResponse retrieves OAuth response
+// GetOAuthResponse retrieves OAuth response.
+// For more details, see GetOAuthResponseContext documentation.
 func GetOAuthResponse(client httpClient, clientID, clientSecret, code, redirectURI string) (resp *OAuthResponse, err error) {
 	return GetOAuthResponseContext(context.Background(), client, clientID, clientSecret, code, redirectURI)
 }
 
-// GetOAuthResponseContext retrieves OAuth response with custom context
+// GetOAuthResponseContext retrieves OAuth response with custom context.
+// Slack API docs: https://api.slack.com/methods/oauth.access
 func GetOAuthResponseContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string) (resp *OAuthResponse, err error) {
 	values := url.Values{
 		"client_id":     {clientID},
@@ -117,12 +133,14 @@ func GetOAuthResponseContext(ctx context.Context, client httpClient, clientID, c
 	return response, response.Err()
 }
 
-// GetOAuthV2Response gets a V2 OAuth access token response - https://api.slack.com/methods/oauth.v2.access
+// GetOAuthV2Response gets a V2 OAuth access token response.
+// For more details, see GetOAuthV2ResponseContext documentation.
 func GetOAuthV2Response(client httpClient, clientID, clientSecret, code, redirectURI string) (resp *OAuthV2Response, err error) {
 	return GetOAuthV2ResponseContext(context.Background(), client, clientID, clientSecret, code, redirectURI)
 }
 
-// GetOAuthV2ResponseContext with a context, gets a V2 OAuth access token response
+// GetOAuthV2ResponseContext with a context, gets a V2 OAuth access token response.
+// Slack API docs: https://api.slack.com/methods/oauth.v2.access
 func GetOAuthV2ResponseContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string) (resp *OAuthV2Response, err error) {
 	values := url.Values{
 		"client_id":     {clientID},
@@ -137,12 +155,14 @@ func GetOAuthV2ResponseContext(ctx context.Context, client httpClient, clientID,
 	return response, response.Err()
 }
 
-// RefreshOAuthV2AccessContext with a context, gets a V2 OAuth access token response
+// RefreshOAuthV2Token with a context, gets a V2 OAuth access token response.
+// For more details, see RefreshOAuthV2TokenContext documentation.
 func RefreshOAuthV2Token(client httpClient, clientID, clientSecret, refreshToken string) (resp *OAuthV2Response, err error) {
 	return RefreshOAuthV2TokenContext(context.Background(), client, clientID, clientSecret, refreshToken)
 }
 
-// RefreshOAuthV2AccessContext with a context, gets a V2 OAuth access token response
+// RefreshOAuthV2TokenContext with a context, gets a V2 OAuth access token response.
+// Slack API docs: https://api.slack.com/methods/oauth.v2.access
 func RefreshOAuthV2TokenContext(ctx context.Context, client httpClient, clientID, clientSecret, refreshToken string) (resp *OAuthV2Response, err error) {
 	values := url.Values{
 		"client_id":     {clientID},
@@ -156,3 +176,25 @@ func RefreshOAuthV2TokenContext(ctx context.Context, client httpClient, clientID
 	}
 	return response, response.Err()
 }
+
+// GetOpenIDConnectToken exchanges a temporary OAuth verifier code for an access token for Sign in with Slack.
+// For more details, see GetOpenIDConnectTokenContext documentation.
+func GetOpenIDConnectToken(client httpClient, clientID, clientSecret, code, redirectURI string) (resp *OpenIDConnectResponse, err error) {
+	return GetOpenIDConnectTokenContext(context.Background(), client, clientID, clientSecret, code, redirectURI)
+}
+
+// GetOpenIDConnectTokenContext with a context, gets an access token for Sign in with Slack.
+// Slack API docs: https://api.slack.com/methods/openid.connect.token
+func GetOpenIDConnectTokenContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string) (resp *OpenIDConnectResponse, err error) {
+	values := url.Values{
+		"client_id":     {clientID},
+		"client_secret": {clientSecret},
+		"code":          {code},
+		"redirect_uri":  {redirectURI},
+	}
+	response := &OpenIDConnectResponse{}
+	if err = postForm(ctx, client, APIURL+"openid.connect.token", values, response, discard{}); err != nil {
+		return nil, err
+	}
+	return response, response.Err()
+}
diff --git a/pins.go b/pins.go
index ef97c8d..5e6cf0c 100644
--- a/pins.go
+++ b/pins.go
@@ -12,12 +12,14 @@ type listPinsResponseFull struct {
 	SlackResponse
 }
 
-// AddPin pins an item in a channel
+// AddPin pins an item in a channel.
+// For more details, see AddPinContext documentation.
 func (api *Client) AddPin(channel string, item ItemRef) error {
 	return api.AddPinContext(context.Background(), channel, item)
 }
 
-// AddPinContext pins an item in a channel with a custom context
+// AddPinContext pins an item in a channel with a custom context.
+// Slack API docs: https://api.slack.com/methods/pins.add
 func (api *Client) AddPinContext(ctx context.Context, channel string, item ItemRef) error {
 	values := url.Values{
 		"channel": {channel},
@@ -41,12 +43,14 @@ func (api *Client) AddPinContext(ctx context.Context, channel string, item ItemR
 	return response.Err()
 }
 
-// RemovePin un-pins an item from a channel
+// RemovePin un-pins an item from a channel.
+// For more details, see RemovePinContext documentation.
 func (api *Client) RemovePin(channel string, item ItemRef) error {
 	return api.RemovePinContext(context.Background(), channel, item)
 }
 
-// RemovePinContext un-pins an item from a channel with a custom context
+// RemovePinContext un-pins an item from a channel with a custom context.
+// Slack API docs: https://api.slack.com/methods/pins.remove
 func (api *Client) RemovePinContext(ctx context.Context, channel string, item ItemRef) error {
 	values := url.Values{
 		"channel": {channel},
@@ -71,11 +75,13 @@ func (api *Client) RemovePinContext(ctx context.Context, channel string, item It
 }
 
 // ListPins returns information about the items a user reacted to.
+// For more details, see ListPinsContext documentation.
 func (api *Client) ListPins(channel string) ([]Item, *Paging, error) {
 	return api.ListPinsContext(context.Background(), channel)
 }
 
 // ListPinsContext returns information about the items a user reacted to with a custom context.
+// Slack API docs: https://api.slack.com/methods/pins.list
 func (api *Client) ListPinsContext(ctx context.Context, channel string) ([]Item, *Paging, error) {
 	values := url.Values{
 		"channel": {channel},
diff --git a/reactions.go b/reactions.go
index 2a9bd42..240f5ba 100644
--- a/reactions.go
+++ b/reactions.go
@@ -67,10 +67,11 @@ const (
 
 // ListReactionsParameters is the inputs to find all reactions by a user.
 type ListReactionsParameters struct {
-	User  string
-	Count int
-	Page  int
-	Full  bool
+	User   string
+	TeamID string
+	Count  int
+	Page   int
+	Full   bool
 }
 
 // NewListReactionsParameters initializes the inputs to find all reactions
@@ -128,11 +129,13 @@ func (res listReactionsResponseFull) extractReactedItems() []ReactedItem {
 }
 
 // AddReaction adds a reaction emoji to a message, file or file comment.
+// For more details, see AddReactionContext documentation.
 func (api *Client) AddReaction(name string, item ItemRef) error {
 	return api.AddReactionContext(context.Background(), name, item)
 }
 
 // AddReactionContext adds a reaction emoji to a message, file or file comment with a custom context.
+// Slack API docs: https://api.slack.com/methods/reactions.add
 func (api *Client) AddReactionContext(ctx context.Context, name string, item ItemRef) error {
 	values := url.Values{
 		"token": {api.token},
@@ -162,11 +165,13 @@ func (api *Client) AddReactionContext(ctx context.Context, name string, item Ite
 }
 
 // RemoveReaction removes a reaction emoji from a message, file or file comment.
+// For more details, see RemoveReactionContext documentation.
 func (api *Client) RemoveReaction(name string, item ItemRef) error {
 	return api.RemoveReactionContext(context.Background(), name, item)
 }
 
 // RemoveReactionContext removes a reaction emoji from a message, file or file comment with a custom context.
+// Slack API docs: https://api.slack.com/methods/reactions.remove
 func (api *Client) RemoveReactionContext(ctx context.Context, name string, item ItemRef) error {
 	values := url.Values{
 		"token": {api.token},
@@ -196,11 +201,13 @@ func (api *Client) RemoveReactionContext(ctx context.Context, name string, item
 }
 
 // GetReactions returns details about the reactions on an item.
+// For more details, see GetReactionsContext documentation.
 func (api *Client) GetReactions(item ItemRef, params GetReactionsParameters) ([]ItemReaction, error) {
 	return api.GetReactionsContext(context.Background(), item, params)
 }
 
-// GetReactionsContext returns details about the reactions on an item with a custom context
+// GetReactionsContext returns details about the reactions on an item with a custom context.
+// Slack API docs: https://api.slack.com/methods/reactions.get
 func (api *Client) GetReactionsContext(ctx context.Context, item ItemRef, params GetReactionsParameters) ([]ItemReaction, error) {
 	values := url.Values{
 		"token": {api.token},
@@ -234,11 +241,13 @@ func (api *Client) GetReactionsContext(ctx context.Context, item ItemRef, params
 }
 
 // ListReactions returns information about the items a user reacted to.
+// For more details, see ListReactionsContext documentation.
 func (api *Client) ListReactions(params ListReactionsParameters) ([]ReactedItem, *Paging, error) {
 	return api.ListReactionsContext(context.Background(), params)
 }
 
 // ListReactionsContext returns information about the items a user reacted to with a custom context.
+// Slack API docs: https://api.slack.com/methods/reactions.list
 func (api *Client) ListReactionsContext(ctx context.Context, params ListReactionsParameters) ([]ReactedItem, *Paging, error) {
 	values := url.Values{
 		"token": {api.token},
@@ -246,6 +255,9 @@ func (api *Client) ListReactionsContext(ctx context.Context, params ListReaction
 	if params.User != DEFAULT_REACTIONS_USER {
 		values.Add("user", params.User)
 	}
+	if params.TeamID != "" {
+		values.Add("team_id", params.TeamID)
+	}
 	if params.Count != DEFAULT_REACTIONS_COUNT {
 		values.Add("count", strconv.Itoa(params.Count))
 	}
diff --git a/reminders.go b/reminders.go
index 53d67c0..e025bc9 100644
--- a/reminders.go
+++ b/reminders.go
@@ -41,23 +41,18 @@ func (api *Client) doReminders(ctx context.Context, path string, values url.Valu
 
 	// create an array of pointers to reminders
 	var reminders = make([]*Reminder, 0, len(response.Reminders))
-	for _, reminder := range response.Reminders {
-		reminders = append(reminders, reminder)
-	}
-
+	reminders = append(reminders, response.Reminders...)
 	return reminders, response.Err()
 }
 
 // ListReminders lists all the reminders created by or for the authenticated user
-//
-// See https://api.slack.com/methods/reminders.list
+// For more details, see ListRemindersContext documentation.
 func (api *Client) ListReminders() ([]*Reminder, error) {
 	return api.ListRemindersContext(context.Background())
 }
 
-// ListRemindersContext lists all the reminders created by or for the authenticated user with a custom context
-//
-// For more details, see ListReminders documentation.
+// ListRemindersContext lists all the reminders created by or for the authenticated user with a custom context.
+// Slack API docs: https://api.slack.com/methods/reminders.list
 func (api *Client) ListRemindersContext(ctx context.Context) ([]*Reminder, error) {
 	values := url.Values{
 		"token": {api.token},
@@ -66,17 +61,14 @@ func (api *Client) ListRemindersContext(ctx context.Context) ([]*Reminder, error
 }
 
 // AddChannelReminder adds a reminder for a channel.
-//
-// See https://api.slack.com/methods/reminders.add (NOTE: the ability to set
-// reminders on a channel is currently undocumented but has been tested to
-// work)
+// For more details, see AddChannelReminderContext documentation.
 func (api *Client) AddChannelReminder(channelID, text, time string) (*Reminder, error) {
 	return api.AddChannelReminderContext(context.Background(), channelID, text, time)
 }
 
 // AddChannelReminderContext adds a reminder for a channel with a custom context
-//
-// For more details, see AddChannelReminder documentation.
+// NOTE: the ability to set reminders on a channel is currently undocumented but has been tested to work.
+// Slack API docs: https://api.slack.com/methods/reminders.add
 func (api *Client) AddChannelReminderContext(ctx context.Context, channelID, text, time string) (*Reminder, error) {
 	values := url.Values{
 		"token":   {api.token},
@@ -88,17 +80,13 @@ func (api *Client) AddChannelReminderContext(ctx context.Context, channelID, tex
 }
 
 // AddUserReminder adds a reminder for a user.
-//
-// See https://api.slack.com/methods/reminders.add (NOTE: the ability to set
-// reminders on a channel is currently undocumented but has been tested to
-// work)
+// For more details, see AddUserReminderContext documentation.
 func (api *Client) AddUserReminder(userID, text, time string) (*Reminder, error) {
 	return api.AddUserReminderContext(context.Background(), userID, text, time)
 }
 
 // AddUserReminderContext adds a reminder for a user with a custom context
-//
-// For more details, see AddUserReminder documentation.
+// Slack API docs: https://api.slack.com/methods/reminders.add
 func (api *Client) AddUserReminderContext(ctx context.Context, userID, text, time string) (*Reminder, error) {
 	values := url.Values{
 		"token": {api.token},
@@ -110,15 +98,13 @@ func (api *Client) AddUserReminderContext(ctx context.Context, userID, text, tim
 }
 
 // DeleteReminder deletes an existing reminder.
-//
-// See https://api.slack.com/methods/reminders.delete
+// For more details, see DeleteReminderContext documentation.
 func (api *Client) DeleteReminder(id string) error {
 	return api.DeleteReminderContext(context.Background(), id)
 }
 
 // DeleteReminderContext deletes an existing reminder with a custom context
-//
-// For more details, see DeleteReminder documentation.
+// Slack API docs: https://api.slack.com/methods/reminders.delete
 func (api *Client) DeleteReminderContext(ctx context.Context, id string) error {
 	values := url.Values{
 		"token":    {api.token},
diff --git a/remotefiles.go b/remotefiles.go
index 8a908a8..0467d7e 100644
--- a/remotefiles.go
+++ b/remotefiles.go
@@ -97,14 +97,13 @@ func (api *Client) remoteFileRequest(ctx context.Context, path string, values ur
 }
 
 // AddRemoteFile adds a remote file. Unlike regular files, remote files must be explicitly shared.
-// For more details:
-// https://api.slack.com/methods/files.remote.add
+// For more details see the AddRemoteFileContext documentation.
 func (api *Client) AddRemoteFile(params RemoteFileParameters) (*RemoteFile, error) {
 	return api.AddRemoteFileContext(context.Background(), params)
 }
 
 // AddRemoteFileContext adds a remote file and setting a custom context
-// For more details see the AddRemoteFile documentation.
+// Slack API docs: https://api.slack.com/methods/files.remote.add
 func (api *Client) AddRemoteFileContext(ctx context.Context, params RemoteFileParameters) (remotefile *RemoteFile, err error) {
 	if params.ExternalID == "" || params.ExternalURL == "" || params.Title == "" {
 		return nil, ErrParametersMissing
@@ -138,14 +137,13 @@ func (api *Client) AddRemoteFileContext(ctx context.Context, params RemoteFilePa
 }
 
 // ListRemoteFiles retrieves all remote files according to the parameters given. Uses cursor based pagination.
-// For more details:
-// https://api.slack.com/methods/files.remote.list
+// For more details see the ListRemoteFilesContext documentation.
 func (api *Client) ListRemoteFiles(params ListRemoteFilesParameters) ([]RemoteFile, error) {
 	return api.ListRemoteFilesContext(context.Background(), params)
 }
 
 // ListRemoteFilesContext retrieves all remote files according to the parameters given with a custom context. Uses cursor based pagination.
-// For more details see the ListRemoteFiles documentation.
+// Slack API docs: https://api.slack.com/methods/files.remote.list
 func (api *Client) ListRemoteFilesContext(ctx context.Context, params ListRemoteFilesParameters) ([]RemoteFile, error) {
 	values := url.Values{
 		"token": {api.token},
@@ -177,14 +175,13 @@ func (api *Client) ListRemoteFilesContext(ctx context.Context, params ListRemote
 }
 
 // GetRemoteFileInfo retrieves the complete remote file information.
-// For more details:
-// https://api.slack.com/methods/files.remote.info
+// For more details see the GetRemoteFileInfoContext documentation.
 func (api *Client) GetRemoteFileInfo(externalID, fileID string) (remotefile *RemoteFile, err error) {
 	return api.GetRemoteFileInfoContext(context.Background(), externalID, fileID)
 }
 
 // GetRemoteFileInfoContext retrieves the complete remote file information given with a custom context.
-// For more details see the GetRemoteFileInfo documentation.
+// Slack API docs: https://api.slack.com/methods/files.remote.info
 func (api *Client) GetRemoteFileInfoContext(ctx context.Context, externalID, fileID string) (remotefile *RemoteFile, err error) {
 	if fileID == "" && externalID == "" {
 		return nil, fmt.Errorf("either externalID or fileID is required")
@@ -208,15 +205,14 @@ func (api *Client) GetRemoteFileInfoContext(ctx context.Context, externalID, fil
 	return &response.RemoteFile, err
 }
 
-// ShareRemoteFile shares a remote file to channels
-// For more details:
-// https://api.slack.com/methods/files.remote.share
+// ShareRemoteFile shares a remote file to channels.
+// For more details see the ShareRemoteFileContext documentation.
 func (api *Client) ShareRemoteFile(channels []string, externalID, fileID string) (file *RemoteFile, err error) {
 	return api.ShareRemoteFileContext(context.Background(), channels, externalID, fileID)
 }
 
 // ShareRemoteFileContext shares a remote file to channels with a custom context.
-// For more details see the ShareRemoteFile documentation.
+// Slack API docs: https://api.slack.com/methods/files.remote.share
 func (api *Client) ShareRemoteFileContext(ctx context.Context, channels []string, externalID, fileID string) (file *RemoteFile, err error) {
 	if channels == nil || len(channels) == 0 {
 		return nil, ErrParametersMissing
@@ -241,15 +237,14 @@ func (api *Client) ShareRemoteFileContext(ctx context.Context, channels []string
 	return &response.RemoteFile, err
 }
 
-// UpdateRemoteFile updates a remote file
-// For more details:
-// https://api.slack.com/methods/files.remote.update
+// UpdateRemoteFile updates a remote file.
+// For more details see the UpdateRemoteFileContext documentation.
 func (api *Client) UpdateRemoteFile(fileID string, params RemoteFileParameters) (remotefile *RemoteFile, err error) {
 	return api.UpdateRemoteFileContext(context.Background(), fileID, params)
 }
 
-// UpdateRemoteFileContext updates a remote file with a custom context
-// For more details see the UpdateRemoteFile documentation.
+// UpdateRemoteFileContext updates a remote file with a custom context.
+// Slack API docs: https://api.slack.com/methods/files.remote.update
 func (api *Client) UpdateRemoteFileContext(ctx context.Context, fileID string, params RemoteFileParameters) (remotefile *RemoteFile, err error) {
 	response := &remoteFileResponseFull{}
 	values := url.Values{
@@ -287,14 +282,13 @@ func (api *Client) UpdateRemoteFileContext(ctx context.Context, fileID string, p
 }
 
 // RemoveRemoteFile removes a remote file.
-// For more details:
-// https://api.slack.com/methods/files.remote.remove
+// For more information see the RemoveRemoteFileContext documentation.
 func (api *Client) RemoveRemoteFile(externalID, fileID string) (err error) {
 	return api.RemoveRemoteFileContext(context.Background(), externalID, fileID)
 }
 
 // RemoveRemoteFileContext removes a remote file with a custom context
-// For more information see the RemoveRemoteFiles documentation.
+// Slack API docs: https://api.slack.com/methods/files.remote.remove
 func (api *Client) RemoveRemoteFileContext(ctx context.Context, externalID, fileID string) (err error) {
 	if fileID == "" && externalID == "" {
 		return fmt.Errorf("either externalID or fileID is required")
diff --git a/search.go b/search.go
index de6b40a..d27497a 100644
--- a/search.go
+++ b/search.go
@@ -15,6 +15,7 @@ const (
 )
 
 type SearchParameters struct {
+	TeamID        string
 	Sort          string
 	SortDirection string
 	Highlight     bool
@@ -93,6 +94,9 @@ func (api *Client) _search(ctx context.Context, path, query string, params Searc
 		"token": {api.token},
 		"query": {query},
 	}
+	if params.TeamID != "" {
+		values.Add("team_id", params.TeamID)
+	}
 	if params.Sort != DEFAULT_SEARCH_SORT {
 		values.Add("sort", params.Sort)
 	}
diff --git a/slack.go b/slack.go
index ea3aab6..756106f 100644
--- a/slack.go
+++ b/slack.go
@@ -57,12 +57,14 @@ type authTestResponseFull struct {
 type ParamOption func(*url.Values)
 
 type Client struct {
-	token         string
-	appLevelToken string
-	endpoint      string
-	debug         bool
-	log           ilogger
-	httpclient    httpClient
+	token              string
+	appLevelToken      string
+	configToken        string
+	configRefreshToken string
+	endpoint           string
+	debug              bool
+	log                ilogger
+	httpclient         httpClient
 }
 
 // Option defines an option for a Client
@@ -99,6 +101,16 @@ func OptionAppLevelToken(token string) func(*Client) {
 	return func(c *Client) { c.appLevelToken = token }
 }
 
+// OptionConfigToken sets a configuration token for the client.
+func OptionConfigToken(token string) func(*Client) {
+	return func(c *Client) { c.configToken = token }
+}
+
+// OptionConfigRefreshToken sets a configuration refresh token for the client.
+func OptionConfigRefreshToken(token string) func(*Client) {
+	return func(c *Client) { c.configRefreshToken = token }
+}
+
 // New builds a slack client from the provided token and options.
 func New(token string, options ...Option) *Client {
 	s := &Client{
diff --git a/slackevents/inner_events.go b/slackevents/inner_events.go
index fb1ab13..bc58426 100644
--- a/slackevents/inner_events.go
+++ b/slackevents/inner_events.go
@@ -28,6 +28,9 @@ type AppMentionEvent struct {
 
 	// BotID is filled out when a bot triggers the app_mention event
 	BotID string `json:"bot_id,omitempty"`
+
+	// When the app is mentioned in the edited message
+	Edited *Edited `json:"edited,omitempty"`
 }
 
 // AppHomeOpenedEvent Your Slack app home was opened.
@@ -256,6 +259,9 @@ type MessageEvent struct {
 	PreviousMessage *MessageEvent `json:"previous_message,omitempty"`
 	Edited          *Edited       `json:"edited,omitempty"`
 
+	// Deleted Message
+	DeletedTimeStamp string `json:"deleted_ts,omitempty"`
+
 	// Message Subtypes
 	SubType string `json:"subtype,omitempty"`
 
@@ -267,6 +273,7 @@ type MessageEvent struct {
 	Upload bool   `json:"upload"`
 	Files  []File `json:"files"`
 
+	Blocks      slack.Blocks       `json:"blocks,omitempty"`
 	Attachments []slack.Attachment `json:"attachments,omitempty"`
 
 	// Root is the message that was broadcast to the channel when the SubType is
@@ -544,6 +551,104 @@ type UserProfileChangedEvent struct {
 	EventTs string      `json:"event_ts"`
 }
 
+// SharedChannelInviteApprovedEvent is sent if your invitation has been approved
+type SharedChannelInviteApprovedEvent struct {
+	Type            string              `json:"type"`
+	Invite          *SharedInvite       `json:"invite"`
+	Channel         *slack.Conversation `json:"channel"`
+	ApprovingTeamID string              `json:"approving_team_id"`
+	TeamsInChannel  []*SlackEventTeam   `json:"teams_in_channel"`
+	ApprovingUser   *SlackEventUser     `json:"approving_user"`
+	EventTs         string              `json:"event_ts"`
+}
+
+// SharedChannelInviteAcceptedEvent is sent if external org accepts a Slack Connect channel invite
+type SharedChannelInviteAcceptedEvent struct {
+	Type                string            `json:"type"`
+	ApprovalRequired    bool              `json:"approval_required"`
+	Invite              *SharedInvite     `json:"invite"`
+	Channel             *SharedChannel    `json:"channel"`
+	TeamsInChannel      []*SlackEventTeam `json:"teams_in_channel"`
+	AcceptingUser       *SlackEventUser   `json:"accepting_user"`
+	EventTs             string            `json:"event_ts"`
+	RequiresSponsorship bool              `json:"requires_sponsorship,omitempty"`
+}
+
+// SharedChannelInviteDeclinedEvent is sent if external or internal org declines the Slack Connect invite
+type SharedChannelInviteDeclinedEvent struct {
+	Type            string            `json:"type"`
+	Invite          *SharedInvite     `json:"invite"`
+	Channel         *SharedChannel    `json:"channel"`
+	DecliningTeamID string            `json:"declining_team_id"`
+	TeamsInChannel  []*SlackEventTeam `json:"teams_in_channel"`
+	DecliningUser   *SlackEventUser   `json:"declining_user"`
+	EventTs         string            `json:"event_ts"`
+}
+
+// SharedChannelInviteReceivedEvent is sent if a bot or app is invited to a Slack Connect channel
+type SharedChannelInviteReceivedEvent struct {
+	Type    string         `json:"type"`
+	Invite  *SharedInvite  `json:"invite"`
+	Channel *SharedChannel `json:"channel"`
+	EventTs string         `json:"event_ts"`
+}
+
+// SlackEventTeam is a struct for teams in ShareChannel events
+type SlackEventTeam struct {
+	ID                  string          `json:"id"`
+	Name                string          `json:"name"`
+	Icon                *SlackEventIcon `json:"icon,omitempty"`
+	AvatarBaseURL       string          `json:"avatar_base_url,omitempty"`
+	IsVerified          bool            `json:"is_verified"`
+	Domain              string          `json:"domain"`
+	DateCreated         int             `json:"date_created"`
+	RequiresSponsorship bool            `json:"requires_sponsorship,omitempty"`
+	// TeamID              string          `json:"team_id,omitempty"`
+}
+
+// SlackEventIcon is a struct for icons in ShareChannel events
+type SlackEventIcon struct {
+	ImageDefault bool   `json:"image_default,omitempty"`
+	Image34      string `json:"image_34,omitempty"`
+	Image44      string `json:"image_44,omitempty"`
+	Image68      string `json:"image_68,omitempty"`
+	Image88      string `json:"image_88,omitempty"`
+	Image102     string `json:"image_102,omitempty"`
+	Image132     string `json:"image_132,omitempty"`
+	Image230     string `json:"image_230,omitempty"`
+}
+
+// SlackEventUser is a struct for users in ShareChannel events
+type SlackEventUser struct {
+	ID                     string             `json:"id"`
+	TeamID                 string             `json:"team_id"`
+	Name                   string             `json:"name"`
+	Updated                int                `json:"updated,omitempty"`
+	Profile                *slack.UserProfile `json:"profile,omitempty"`
+	WhoCanShareContactCard string             `json:"who_can_share_contact_card,omitempty"`
+}
+
+// SharedChannel is a struct for shared channels in ShareChannel events
+type SharedChannel struct {
+	ID        string `json:"id"`
+	IsPrivate bool   `json:"is_private"`
+	IsIm      bool   `json:"is_im"`
+	Name      string `json:"name,omitempty"`
+}
+
+// SharedInvite is a struct for shared invites in ShareChannel events
+type SharedInvite struct {
+	ID                string          `json:"id"`
+	DateCreated       int             `json:"date_created"`
+	DateInvalid       int             `json:"date_invalid"`
+	InvitingTeam      *SlackEventTeam `json:"inviting_team,omitempty"`
+	InvitingUser      *SlackEventUser `json:"inviting_user,omitempty"`
+	RecipientEmail    string          `json:"recipient_email,omitempty"`
+	RecipientUserID   string          `json:"recipient_user_id,omitempty"`
+	IsSponsored       bool            `json:"is_sponsored,omitempty"`
+	IsExternalLimited bool            `json:"is_external_limited,omitempty"`
+}
+
 type EventsAPIType string
 
 const (
@@ -607,6 +712,14 @@ const (
 	ReactionRemoved = EventsAPIType("reaction_removed")
 	// TeamJoin A new user joined the workspace
 	TeamJoin = EventsAPIType("team_join")
+	// Slack connect app or bot invite received
+	SharedChannelInviteReceived = EventsAPIType("shared_channel_invite_received")
+	// Slack connect channel invite approved
+	SharedChannelInviteApproved = EventsAPIType("shared_channel_invite_approved")
+	// Slack connect channel invite declined
+	SharedChannelInviteDeclined = EventsAPIType("shared_channel_invite_declined")
+	// Slack connect channel invite accepted by an end user
+	SharedChannelInviteAccepted = EventsAPIType("shared_channel_invite_accepted")
 	// TokensRevoked APP's API tokes are revoked
 	TokensRevoked = EventsAPIType("tokens_revoked")
 	// EmojiChanged A custom emoji has been added or changed
@@ -631,43 +744,47 @@ const (
 // implementations. The structs should be instances of the unmarshalling
 // target for the matching event type.
 var EventsAPIInnerEventMapping = map[EventsAPIType]interface{}{
-	AppMention:             AppMentionEvent{},
-	AppHomeOpened:          AppHomeOpenedEvent{},
-	AppUninstalled:         AppUninstalledEvent{},
-	ChannelCreated:         ChannelCreatedEvent{},
-	ChannelDeleted:         ChannelDeletedEvent{},
-	ChannelArchive:         ChannelArchiveEvent{},
-	ChannelUnarchive:       ChannelUnarchiveEvent{},
-	ChannelLeft:            ChannelLeftEvent{},
-	ChannelRename:          ChannelRenameEvent{},
-	ChannelIDChanged:       ChannelIDChangedEvent{},
-	FileChange:             FileChangeEvent{},
-	FileDeleted:            FileDeletedEvent{},
-	FileShared:             FileSharedEvent{},
-	FileUnshared:           FileUnsharedEvent{},
-	GroupDeleted:           GroupDeletedEvent{},
-	GroupArchive:           GroupArchiveEvent{},
-	GroupUnarchive:         GroupUnarchiveEvent{},
-	GroupLeft:              GroupLeftEvent{},
-	GroupRename:            GroupRenameEvent{},
-	GridMigrationFinished:  GridMigrationFinishedEvent{},
-	GridMigrationStarted:   GridMigrationStartedEvent{},
-	LinkShared:             LinkSharedEvent{},
-	Message:                MessageEvent{},
-	MemberJoinedChannel:    MemberJoinedChannelEvent{},
-	MemberLeftChannel:      MemberLeftChannelEvent{},
-	PinAdded:               PinAddedEvent{},
-	PinRemoved:             PinRemovedEvent{},
-	ReactionAdded:          ReactionAddedEvent{},
-	ReactionRemoved:        ReactionRemovedEvent{},
-	TeamJoin:               TeamJoinEvent{},
-	TokensRevoked:          TokensRevokedEvent{},
-	EmojiChanged:           EmojiChangedEvent{},
-	WorkflowStepExecute:    WorkflowStepExecuteEvent{},
-	MessageMetadataPosted:  MessageMetadataPostedEvent{},
-	MessageMetadataUpdated: MessageMetadataUpdatedEvent{},
-	MessageMetadataDeleted: MessageMetadataDeletedEvent{},
-	TeamAccessGranted:      TeamAccessGrantedEvent{},
-	TeamAccessRevoked:      TeamAccessRevokedEvent{},
-	UserProfileChanged:     UserProfileChangedEvent{},
+	AppMention:                  AppMentionEvent{},
+	AppHomeOpened:               AppHomeOpenedEvent{},
+	AppUninstalled:              AppUninstalledEvent{},
+	ChannelCreated:              ChannelCreatedEvent{},
+	ChannelDeleted:              ChannelDeletedEvent{},
+	ChannelArchive:              ChannelArchiveEvent{},
+	ChannelUnarchive:            ChannelUnarchiveEvent{},
+	ChannelLeft:                 ChannelLeftEvent{},
+	ChannelRename:               ChannelRenameEvent{},
+	ChannelIDChanged:            ChannelIDChangedEvent{},
+	FileChange:                  FileChangeEvent{},
+	FileDeleted:                 FileDeletedEvent{},
+	FileShared:                  FileSharedEvent{},
+	FileUnshared:                FileUnsharedEvent{},
+	GroupDeleted:                GroupDeletedEvent{},
+	GroupArchive:                GroupArchiveEvent{},
+	GroupUnarchive:              GroupUnarchiveEvent{},
+	GroupLeft:                   GroupLeftEvent{},
+	GroupRename:                 GroupRenameEvent{},
+	GridMigrationFinished:       GridMigrationFinishedEvent{},
+	GridMigrationStarted:        GridMigrationStartedEvent{},
+	LinkShared:                  LinkSharedEvent{},
+	Message:                     MessageEvent{},
+	MemberJoinedChannel:         MemberJoinedChannelEvent{},
+	MemberLeftChannel:           MemberLeftChannelEvent{},
+	PinAdded:                    PinAddedEvent{},
+	PinRemoved:                  PinRemovedEvent{},
+	ReactionAdded:               ReactionAddedEvent{},
+	ReactionRemoved:             ReactionRemovedEvent{},
+	SharedChannelInviteApproved: SharedChannelInviteApprovedEvent{},
+	SharedChannelInviteAccepted: SharedChannelInviteAcceptedEvent{},
+	SharedChannelInviteDeclined: SharedChannelInviteDeclinedEvent{},
+	SharedChannelInviteReceived: SharedChannelInviteReceivedEvent{},
+	TeamJoin:                    TeamJoinEvent{},
+	TokensRevoked:               TokensRevokedEvent{},
+	EmojiChanged:                EmojiChangedEvent{},
+	WorkflowStepExecute:         WorkflowStepExecuteEvent{},
+	MessageMetadataPosted:       MessageMetadataPostedEvent{},
+	MessageMetadataUpdated:      MessageMetadataUpdatedEvent{},
+	MessageMetadataDeleted:      MessageMetadataDeletedEvent{},
+	TeamAccessGranted:           TeamAccessGrantedEvent{},
+	TeamAccessRevoked:           TeamAccessRevokedEvent{},
+	UserProfileChanged:          UserProfileChangedEvent{},
 }
diff --git a/slackevents/parsers.go b/slackevents/parsers.go
index 96ba568..4daac8b 100644
--- a/slackevents/parsers.go
+++ b/slackevents/parsers.go
@@ -117,7 +117,7 @@ func parseInnerEvent(e *EventsAPICallbackEvent) (EventsAPIEvent, error) {
 			e.EnterpriseID,
 			nil,
 			EventsAPIInnerEvent{},
-		}, fmt.Errorf("Inner Event does not exist! %s", iE.Type)
+		}, fmt.Errorf("inner Event does not exist! %s", iE.Type)
 	}
 	t := reflect.TypeOf(v)
 	recvEvent := reflect.New(t).Interface()
@@ -192,7 +192,7 @@ func ParseEvent(rawEvent json.RawMessage, opts ...Option) (EventsAPIEvent, error
 	}
 
 	if !cfg.TokenVerified {
-		return EventsAPIEvent{}, errors.New("Invalid verification token")
+		return EventsAPIEvent{}, errors.New("invalid verification token")
 	}
 
 	if e.Type == CallbackEvent {
diff --git a/slash.go b/slash.go
index b2c5094..fd46abf 100644
--- a/slash.go
+++ b/slash.go
@@ -1,25 +1,29 @@
 package slack
 
 import (
+	"encoding/json"
+	"fmt"
 	"net/http"
+	"strconv"
 )
 
 // SlashCommand contains information about a request of the slash command
 type SlashCommand struct {
-	Token          string `json:"token"`
-	TeamID         string `json:"team_id"`
-	TeamDomain     string `json:"team_domain"`
-	EnterpriseID   string `json:"enterprise_id,omitempty"`
-	EnterpriseName string `json:"enterprise_name,omitempty"`
-	ChannelID      string `json:"channel_id"`
-	ChannelName    string `json:"channel_name"`
-	UserID         string `json:"user_id"`
-	UserName       string `json:"user_name"`
-	Command        string `json:"command"`
-	Text           string `json:"text"`
-	ResponseURL    string `json:"response_url"`
-	TriggerID      string `json:"trigger_id"`
-	APIAppID       string `json:"api_app_id"`
+	Token               string `json:"token"`
+	TeamID              string `json:"team_id"`
+	TeamDomain          string `json:"team_domain"`
+	EnterpriseID        string `json:"enterprise_id,omitempty"`
+	EnterpriseName      string `json:"enterprise_name,omitempty"`
+	IsEnterpriseInstall bool   `json:"is_enterprise_install"`
+	ChannelID           string `json:"channel_id"`
+	ChannelName         string `json:"channel_name"`
+	UserID              string `json:"user_id"`
+	UserName            string `json:"user_name"`
+	Command             string `json:"command"`
+	Text                string `json:"text"`
+	ResponseURL         string `json:"response_url"`
+	TriggerID           string `json:"trigger_id"`
+	APIAppID            string `json:"api_app_id"`
 }
 
 // SlashCommandParse will parse the request of the slash command
@@ -32,6 +36,7 @@ func SlashCommandParse(r *http.Request) (s SlashCommand, err error) {
 	s.TeamDomain = r.PostForm.Get("team_domain")
 	s.EnterpriseID = r.PostForm.Get("enterprise_id")
 	s.EnterpriseName = r.PostForm.Get("enterprise_name")
+	s.IsEnterpriseInstall = r.PostForm.Get("is_enterprise_install") == "true"
 	s.ChannelID = r.PostForm.Get("channel_id")
 	s.ChannelName = r.PostForm.Get("channel_name")
 	s.UserID = r.PostForm.Get("user_id")
@@ -53,3 +58,34 @@ func (s SlashCommand) ValidateToken(verificationTokens ...string) bool {
 	}
 	return false
 }
+
+// UnmarshalJSON handles is_enterprise_install being either a boolean or a
+// string when parsing JSON from various payloads
+func (s *SlashCommand) UnmarshalJSON(data []byte) error {
+	type SlashCommandCopy SlashCommand
+	scopy := &struct {
+		*SlashCommandCopy
+		IsEnterpriseInstall interface{} `json:"is_enterprise_install"`
+	}{
+		SlashCommandCopy: (*SlashCommandCopy)(s),
+	}
+
+	if err := json.Unmarshal(data, scopy); err != nil {
+		return err
+	}
+
+	switch rawValue := scopy.IsEnterpriseInstall.(type) {
+	case string:
+		b, err := strconv.ParseBool(rawValue)
+		if err != nil {
+			return fmt.Errorf("parsing boolean for is_enterprise_install: %w", err)
+		}
+		s.IsEnterpriseInstall = b
+	case bool:
+		s.IsEnterpriseInstall = rawValue
+	default:
+		return fmt.Errorf("wrong data type for is_enterprise_install: %T", scopy.IsEnterpriseInstall)
+	}
+
+	return nil
+}
diff --git a/socketmode/deadman.go b/socketmode/deadman.go
deleted file mode 100644
index 7aeea76..0000000
--- a/socketmode/deadman.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package socketmode
-
-import "time"
-
-type deadmanTimer struct {
-	timeout time.Duration
-	timer   *time.Timer
-}
-
-func newDeadmanTimer(timeout time.Duration) *deadmanTimer {
-	return &deadmanTimer{
-		timeout: timeout,
-		timer:   time.NewTimer(timeout),
-	}
-}
-
-func (smc *deadmanTimer) Elapsed() <-chan time.Time {
-	return smc.timer.C
-}
-
-func (smc *deadmanTimer) Reset() {
-	// Note that this is the correct way to Reset a non-expired timer
-	if !smc.timer.Stop() {
-		select {
-		case <-smc.timer.C:
-		default:
-		}
-	}
-
-	smc.timer.Reset(smc.timeout)
-}
diff --git a/socketmode/socket_mode_managed_conn.go b/socketmode/socket_mode_managed_conn.go
index 5a98f1e..b94456f 100644
--- a/socketmode/socket_mode_managed_conn.go
+++ b/socketmode/socket_mode_managed_conn.go
@@ -1,6 +1,7 @@
 package socketmode
 
 import (
+	"bytes"
 	"context"
 	"encoding/json"
 	"errors"
@@ -53,13 +54,14 @@ func (smc *Client) RunContext(ctx context.Context) error {
 }
 
 func (smc *Client) run(ctx context.Context, connectionCount int) error {
-	messages := make(chan json.RawMessage)
-	defer close(messages)
-
-	deadmanTimer := newDeadmanTimer(smc.maxPingInterval)
+	messages := make(chan json.RawMessage, 1)
 
+	pingChan := make(chan time.Time, 1)
 	pingHandler := func(_ string) error {
-		deadmanTimer.Reset()
+		select {
+		case pingChan <- time.Now():
+		default:
+		}
 
 		return nil
 	}
@@ -81,20 +83,24 @@ func (smc *Client) run(ctx context.Context, connectionCount int) error {
 	ctx, cancel := context.WithCancel(ctx)
 	defer cancel()
 
-	smc.Events <- newEvent(EventTypeConnected, &ConnectedEvent{
+	smc.sendEvent(ctx, newEvent(EventTypeConnected, &ConnectedEvent{
 		ConnectionCount: connectionCount,
 		Info:            info,
-	})
+	}))
 
 	smc.Debugf("WebSocket connection succeeded on try %d", connectionCount)
 
 	// We're now connected so we can set up listeners
 
-	var (
-		wg           sync.WaitGroup
-		firstErr     error
-		firstErrOnce sync.Once
-	)
+	wg := new(sync.WaitGroup)
+	// sendErr relies on the buffer of 1 here
+	errc := make(chan error, 1)
+	sendErr := func(err error) {
+		select {
+		case errc <- err:
+		default:
+		}
+	}
 
 	wg.Add(1)
 	go func() {
@@ -103,9 +109,7 @@ func (smc *Client) run(ctx context.Context, connectionCount int) error {
 
 		// The response sender sends Socket Mode responses over the WebSocket conn
 		if err := smc.runResponseSender(ctx, conn); err != nil {
-			firstErrOnce.Do(func() {
-				firstErr = err
-			})
+			sendErr(err)
 		}
 	}()
 
@@ -116,56 +120,79 @@ func (smc *Client) run(ctx context.Context, connectionCount int) error {
 
 		// The handler reads Socket Mode requests, and enqueues responses for sending by the response sender
 		if err := smc.runRequestHandler(ctx, messages); err != nil {
-			firstErrOnce.Do(func() {
-				firstErr = err
-			})
+			sendErr(err)
 		}
 	}()
 
-	// Need to wait for runMessageReceiver to avoid panic described in https://github.com/slack-go/slack/issues/1125
-	wg.Add(1)
 	go func() {
-		defer wg.Done()
 		defer cancel()
+		// We close messages here as it is the producer for the channel.
+		defer close(messages)
 
 		// The receiver reads WebSocket messages, and enqueues parsed Socket Mode requests to be handled by
 		// the request handler
 		if err := smc.runMessageReceiver(ctx, conn, messages); err != nil {
-			firstErrOnce.Do(func() {
-				firstErr = err
-			})
+			sendErr(err)
 		}
 	}()
 
 	wg.Add(1)
-	go func() {
+	go func(pingInterval time.Duration) {
 		defer wg.Done()
-
-		select {
-		case <-ctx.Done():
+		defer func() {
 			// Detect when the connection is dead and try close connection.
-			if err = conn.Close(); err != nil {
+			if err := conn.Close(); err != nil {
 				smc.Debugf("Failed to close connection: %v", err)
 			}
-		case <-deadmanTimer.Elapsed():
-			firstErrOnce.Do(func() {
-				firstErr = errors.New("ping timeout: Slack did not send us WebSocket PING for more than Client.maxInterval")
-			})
+		}()
+
+		done := ctx.Done()
+		var lastPing time.Time
+
+		// More efficient than constantly resetting a timer w/ Stop+Reset
+		ticker := time.NewTicker(pingInterval)
+		defer ticker.Stop()
 
-			cancel()
+		for {
+			select {
+			case <-done:
+				return
+
+			case lastPing = <-pingChan:
+				// This case gets the time of the last ping.
+				// If this case never fires then the pingHandler was never called
+				// in which case lastPing is the zero time.Time value, and will 'fail'
+				// the next tick, causing us to exit.
+
+			case now := <-ticker.C:
+				// Our last ping is older than our interval
+				if now.Sub(lastPing) > pingInterval {
+					sendErr(errors.New("ping timeout: Slack did not send us WebSocket PING for more than Client.maxInterval"))
+
+					cancel()
+					return
+				}
+			}
 		}
-	}()
+	}(smc.maxPingInterval)
 
 	wg.Wait()
 
-	if firstErr == context.Canceled {
-		return firstErr
+	select {
+	case err = <-errc:
+		// Get buffered error
+	default:
+		// Or nothing if they all exited nil
+	}
+
+	if errors.Is(err, context.Canceled) {
+		return err
 	}
 
 	// wg.Wait() finishes only after any of the above go routines finishes and cancels the
 	// context, allowing the other threads to shut down gracefully.
-	// Also, we can expect firstErr to be not nil, as goroutines can finish only on error.
-	smc.Debugf("Reconnecting due to %v", firstErr)
+	// Also, we can expect our (first)err to be not nil, as goroutines can finish only on error.
+	smc.Debugf("Reconnecting due to %v", err)
 
 	return nil
 }
@@ -193,10 +220,10 @@ func (smc *Client) connect(ctx context.Context, connectionCount int, additionalP
 		)
 
 		// send connecting event
-		smc.Events <- newEvent(EventTypeConnecting, &slack.ConnectingEvent{
+		smc.sendEvent(ctx, newEvent(EventTypeConnecting, &slack.ConnectingEvent{
 			Attempt:         boff.Attempts() + 1,
 			ConnectionCount: connectionCount,
-		})
+		}))
 
 		// attempt to start the connection
 		info, conn, err := smc.openAndDial(ctx, additionalPingHandler)
@@ -212,26 +239,32 @@ func (smc *Client) connect(ctx context.Context, connectionCount int, additionalP
 		default:
 		}
 
-		switch actual := err.(type) {
-		case slack.StatusCodeError:
-			if actual.Code == http.StatusNotFound {
-				smc.Debugf("invalid auth when connecting with Socket Mode: %s", err)
-				smc.Events <- newEvent(EventTypeInvalidAuth, &slack.InvalidAuthEvent{})
-				return nil, nil, err
-			}
-		case *slack.RateLimitedError:
-			backoff = actual.RetryAfter
-		default:
+		var (
+			actual  slack.StatusCodeError
+			rlError *slack.RateLimitedError
+		)
+
+		if errors.As(err, &actual) && actual.Code == http.StatusNotFound {
+			smc.Debugf("invalid auth when connecting with Socket Mode: %s", err)
+			smc.sendEvent(ctx, newEvent(EventTypeInvalidAuth, &slack.InvalidAuthEvent{}))
+
+			return nil, nil, err
+		} else if errors.As(err, &rlError) {
+			backoff = rlError.RetryAfter
 		}
 
+		// If we check for errors.Is(err, context.Canceled) here and
+		// return early then we don't send the Event below that some users
+		// may already rely on; ie a behavior change.
+
 		backoff = timex.Max(backoff, boff.Duration())
 		// any other errors are treated as recoverable and we try again after
 		// sending the event along the Events channel
-		smc.Events <- newEvent(EventTypeConnectionError, &slack.ConnectionErrorEvent{
+		smc.sendEvent(ctx, newEvent(EventTypeConnectionError, &slack.ConnectionErrorEvent{
 			Attempt:  boff.Attempts(),
 			Backoff:  backoff,
 			ErrorObj: err,
-		})
+		}))
 
 		// get time we should wait before attempting to connect again
 		smc.Debugf("reconnection %d failed: %s reconnecting in %v\n", boff.Attempts(), err, backoff)
@@ -239,9 +272,11 @@ func (smc *Client) connect(ctx context.Context, connectionCount int, additionalP
 		// wait for one of the following to occur,
 		// backoff duration has elapsed, disconnectCh is signalled, or
 		// the smc finishes disconnecting.
+		timer := time.NewTimer(backoff)
 		select {
-		case <-time.After(backoff): // retry after the backoff.
+		case <-timer.C: // retry after the backoff.
 		case <-ctx.Done():
+			timer.Stop()
 			return nil, nil, ctx.Err()
 		}
 	}
@@ -276,12 +311,13 @@ func (smc *Client) openAndDial(ctx context.Context, additionalPingHandler func(s
 		smc.Debugf("Failed to dial to the websocket: %s", err)
 		return nil, nil, err
 	}
+	if additionalPingHandler == nil {
+		additionalPingHandler = func(_ string) error { return nil }
+	}
 
 	conn.SetPingHandler(func(appData string) error {
-		if additionalPingHandler != nil {
-			if err := additionalPingHandler(appData); err != nil {
-				return err
-			}
+		if err := additionalPingHandler(appData); err != nil {
+			return err
 		}
 
 		smc.handlePing(conn, appData)
@@ -312,10 +348,10 @@ func (smc *Client) runResponseSender(ctx context.Context, conn *websocket.Conn)
 			smc.Debugf("Sending Socket Mode response with envelope ID %q: %v", res.EnvelopeID, res)
 
 			if err := unsafeWriteSocketModeResponse(conn, res); err != nil {
-				smc.Events <- newEvent(EventTypeErrorWriteFailed, &ErrorWriteFailed{
+				smc.sendEvent(ctx, newEvent(EventTypeErrorWriteFailed, &ErrorWriteFailed{
 					Cause:    err,
 					Response: res,
-				})
+				}))
 			}
 
 			smc.Debugf("Finished sending Socket Mode response with envelope ID %q", res.EnvelopeID)
@@ -332,16 +368,22 @@ func (smc *Client) runRequestHandler(ctx context.Context, websocket chan json.Ra
 		select {
 		case <-ctx.Done():
 			return ctx.Err()
-		case message := <-websocket:
+		case message, ok := <-websocket:
+			if !ok {
+				// The producer closed the channel because it encountered an error (or panic),
+				// we need only return.
+				return nil
+			}
+
 			smc.Debugf("Received WebSocket message: %s", message)
 
 			// listen for incoming messages that need to be parsed
 			evt, err := smc.parseEvent(message)
 			if err != nil {
-				smc.Events <- newEvent(EventTypeErrorBadMessage, &ErrorBadMessage{
+				smc.sendEvent(ctx, newEvent(EventTypeErrorBadMessage, &ErrorBadMessage{
 					Cause:   err,
 					Message: message,
-				})
+				}))
 			} else if evt != nil {
 				if evt.Type == EventTypeDisconnect {
 					// We treat the `disconnect` request from Slack as an error internally,
@@ -349,7 +391,7 @@ func (smc *Client) runRequestHandler(ctx context.Context, websocket chan json.Ra
 					return errorRequestedDisconnect{}
 				}
 
-				smc.Events <- *evt
+				smc.sendEvent(ctx, *evt)
 			}
 		}
 	}
@@ -385,11 +427,7 @@ func unsafeWriteSocketModeResponse(conn *websocket.Conn, res *Response) error {
 	// Remove write deadline regardless of WriteJSON succeeds or not
 	defer conn.SetWriteDeadline(time.Time{})
 
-	if err := conn.WriteJSON(res); err != nil {
-		return err
-	}
-
-	return nil
+	return conn.WriteJSON(res)
 }
 
 func newEvent(tpe EventType, data interface{}, req ...*Request) Event {
@@ -407,29 +445,54 @@ func newEvent(tpe EventType, data interface{}, req ...*Request) Event {
 // This tells Slack that the we have received the request denoted by the envelope ID,
 // by sending back the envelope ID over the WebSocket connection.
 func (smc *Client) Ack(req Request, payload ...interface{}) {
-	res := Response{
-		EnvelopeID: req.EnvelopeID,
-	}
-
+	var pld interface{}
 	if len(payload) > 0 {
-		res.Payload = payload[0]
+		pld = payload[0]
 	}
 
-	smc.Send(res)
+	smc.AckCtx(context.TODO(), req.EnvelopeID, pld)
+}
+
+// AckCtx acknowledges the Socket Mode request envelope ID with the payload.
+//
+// This tells Slack that the we have received the request denoted by the request (envelope) ID,
+// by sending back the ID over the WebSocket connection.
+func (smc *Client) AckCtx(ctx context.Context, reqID string, payload interface{}) error {
+	return smc.SendCtx(ctx, Response{
+		EnvelopeID: reqID,
+		Payload:    payload,
+	})
 }
 
 // Send sends the Socket Mode response over a WebSocket connection.
 // This is usually used for acknowledging requests, but if you need more control over Client.Ack().
 // It's normally recommended to use Client.Ack() instead of this.
 func (smc *Client) Send(res Response) {
-	js, err := json.Marshal(res)
-	if err != nil {
-		panic(err)
+	smc.SendCtx(context.TODO(), res)
+}
+
+// SendCtx sends the Socket Mode response over a WebSocket connection.
+// This is usually used for acknowledging requests, but if you need more control
+// it's normally recommended to use Client.AckCtx() instead of this.
+func (smc *Client) SendCtx(ctx context.Context, res Response) error {
+	if smc.debug {
+		js, err := json.Marshal(res)
+
+		// Log the error so users of `Send` don't see it entirely disappear as that method
+		// does not return an error and used to panic on failure (with or without debug)
+		smc.Debugf("Scheduling Socket Mode response (error: %v) for envelope ID %s: %s", err, res.EnvelopeID, js)
+		if err != nil {
+			return err
+		}
 	}
 
-	smc.Debugf("Scheduling Socket Mode response for envelope ID %s: %s", res.EnvelopeID, js)
+	select {
+	case <-ctx.Done():
+		return ctx.Err()
+	case smc.socketModeResponses <- &res:
+	}
 
-	smc.socketModeResponses <- &res
+	return nil
 }
 
 // receiveMessagesInto attempts to receive an event from the WebSocket connection for Socket Mode.
@@ -439,75 +502,58 @@ func (smc *Client) receiveMessagesInto(ctx context.Context, conn *websocket.Conn
 	smc.Debugf("Starting to receive message")
 	defer smc.Debugf("Finished to receive message")
 
-	var err error
-
 	event := json.RawMessage{}
-
-	readJsonErr := make(chan error)
-	go func() {
-		select {
-		case readJsonErr <- conn.ReadJSON(&event):
-			// if conn.ReadJSON method returns after ctx.Done(), no one will read the unbuffered channel
-			// so, to avoid goroutines leak we need to check for ctx.Done() here too.
-			// need to say here, that conn.ReadJSON will really return after ctx.Done(), because in that case
-			// conn is closed
-			break
-		case <-ctx.Done():
-			// just need to listen ctx.Done, nothing has to be done here
-			break
-		}
-	}()
-
-	select {
-	case err = <-readJsonErr:
-		// we have awaited response from conn.ReadJSON, so, we can handle error now
-		break
-	case <-ctx.Done():
-		// context cancellation signal got, closing connection and returning
-		cerr := conn.Close()
-		if cerr != nil {
-			smc.Debugf("Failed to close connection: %v", cerr)
+	err := conn.ReadJSON(&event)
+	if err != nil {
+		// check if the connection was closed.
+		// This version of the gorilla/websocket package also does a type assertion
+		// on the error, rather than unwrapping it, so we'll do the unwrapping then pass
+		// the unwrapped error
+		var wsErr *websocket.CloseError
+		if errors.As(err, &wsErr) && websocket.IsUnexpectedCloseError(wsErr) {
+			return err
 		}
 
-		return ctx.Err()
-	}
-
-	// after select above, the err will be set to result from conn.ReadJSON, handling
-
-	// check if the connection was closed.
-	if websocket.IsUnexpectedCloseError(err) {
-		return err
-	}
+		if errors.Is(err, io.ErrUnexpectedEOF) {
+			// EOF's don't seem to signify a failed connection so instead we ignore
+			// them here and detect a failed connection upon attempting to send a
+			// 'PING' message
 
-	switch {
-	case err == io.ErrUnexpectedEOF:
-		// EOF's don't seem to signify a failed connection so instead we ignore
-		// them here and detect a failed connection upon attempting to send a
-		// 'PING' message
+			// Unlike RTM, we don't ping from the our end as there seem to have no client ping.
+			// We just continue to the next loop so that we `smc.disconnected` should be received if
+			// this EOF error was actually due to disconnection.
 
-		// Unlike RTM, we don't ping from the our end as there seem to have no client ping.
-		// We just continue to the next loop so that we `smc.disconnected` should be received if
-		// this EOF error was actually due to disconnection.
+			return nil
+		}
 
-		return nil
-	case err != nil:
 		// All other errors from ReadJSON come from NextReader, and should
 		// kill the read loop and force a reconnect.
-		smc.Events <- newEvent(EventTypeIncomingError, &slack.IncomingEventError{
+		// TODO: Unless it's a JSON unmarshal-type error in which case maybe reconnecting isn't needed...
+		smc.sendEvent(ctx, newEvent(EventTypeIncomingError, &slack.IncomingEventError{
 			ErrorObj: err,
-		})
+		}))
 
 		return err
-	case len(event) == 0:
-		smc.Debugln("Received empty event")
-	default:
-		select {
-		case sink <- event:
-		case <-ctx.Done():
-			smc.Debugln("cancelled while attempting to send raw event")
+	}
 
-			return ctx.Err()
+	if smc.debug {
+		buf := &bytes.Buffer{}
+		d := json.NewEncoder(buf)
+		d.SetIndent("", "  ")
+		if err := d.Encode(event); err != nil {
+			smc.Debugln("Failed encoding decoded json:", err)
 		}
+		reencoded := buf.String()
+
+		smc.Debugln("Incoming WebSocket message:", reencoded)
+	}
+
+	select {
+	case sink <- event:
+	case <-ctx.Done():
+		smc.Debugln("cancelled while attempting to send raw event")
+
+		return ctx.Err()
 	}
 
 	return nil
@@ -520,7 +566,7 @@ func (smc *Client) parseEvent(wsMsg json.RawMessage) (*Event, error) {
 	req := &Request{}
 	err := json.Unmarshal(wsMsg, req)
 	if err != nil {
-		return nil, fmt.Errorf("unmarshalling WebSocket message: %v", err)
+		return nil, fmt.Errorf("unmarshalling WebSocket message: %w", err)
 	}
 
 	var evt Event
@@ -536,7 +582,7 @@ func (smc *Client) parseEvent(wsMsg json.RawMessage) (*Event, error) {
 
 		eventsAPIEvent, err := slackevents.ParseEvent(payloadEvent, slackevents.OptionNoVerifyToken())
 		if err != nil {
-			return nil, fmt.Errorf("parsing Events API event: %v", err)
+			return nil, fmt.Errorf("parsing Events API event: %w", err)
 		}
 
 		evt = newEvent(EventTypeEventsAPI, eventsAPIEvent, req)
@@ -549,7 +595,7 @@ func (smc *Client) parseEvent(wsMsg json.RawMessage) (*Event, error) {
 		var cmd slack.SlashCommand
 
 		if err := json.Unmarshal(req.Payload, &cmd); err != nil {
-			return nil, fmt.Errorf("parsing slash command: %v", err)
+			return nil, fmt.Errorf("parsing slash command: %w", err)
 		}
 
 		evt = newEvent(EventTypeSlashCommand, cmd, req)
@@ -563,7 +609,7 @@ func (smc *Client) parseEvent(wsMsg json.RawMessage) (*Event, error) {
 		var callback slack.InteractionCallback
 
 		if err := json.Unmarshal(req.Payload, &callback); err != nil {
-			return nil, fmt.Errorf("parsing interaction callback: %v", err)
+			return nil, fmt.Errorf("parsing interaction callback: %w", err)
 		}
 
 		evt = newEvent(EventTypeInteractive, callback, req)
diff --git a/socketmode/socketmode.go b/socketmode/socketmode.go
index 1871e67..6ca8f48 100644
--- a/socketmode/socketmode.go
+++ b/socketmode/socketmode.go
@@ -119,3 +119,15 @@ func New(api *slack.Client, options ...Option) *Client {
 
 	return result
 }
+
+// sendEvent safely sends an event into the Clients Events channel
+// and blocks until buffer space is had, or the context is canceled.
+// This prevents deadlocking in the event that Events buffer is full,
+// other goroutines are waiting, and/or timing allows receivers to exit
+// before all senders are finished.
+func (smc *Client) sendEvent(ctx context.Context, event Event) {
+	select {
+	case smc.Events <- event:
+	case <-ctx.Done():
+	}
+}
diff --git a/stars.go b/stars.go
index 6e0ebbe..5192685 100644
--- a/stars.go
+++ b/stars.go
@@ -36,12 +36,14 @@ func NewStarsParameters() StarsParameters {
 	}
 }
 
-// AddStar stars an item in a channel
+// AddStar stars an item in a channel.
+// For more information see the AddStarContext documentation.
 func (api *Client) AddStar(channel string, item ItemRef) error {
 	return api.AddStarContext(context.Background(), channel, item)
 }
 
-// AddStarContext stars an item in a channel with a custom context
+// AddStarContext stars an item in a channel with a custom context.
+// Slack API docs: https://api.slack.com/methods/stars.add
 func (api *Client) AddStarContext(ctx context.Context, channel string, item ItemRef) error {
 	values := url.Values{
 		"channel": {channel},
@@ -65,12 +67,14 @@ func (api *Client) AddStarContext(ctx context.Context, channel string, item Item
 	return response.Err()
 }
 
-// RemoveStar removes a starred item from a channel
+// RemoveStar removes a starred item from a channel.
+// For more information see the RemoveStarContext documentation.
 func (api *Client) RemoveStar(channel string, item ItemRef) error {
 	return api.RemoveStarContext(context.Background(), channel, item)
 }
 
-// RemoveStarContext removes a starred item from a channel with a custom context
+// RemoveStarContext removes a starred item from a channel with a custom context.
+// Slack API docs: https://api.slack.com/methods/stars.remove
 func (api *Client) RemoveStarContext(ctx context.Context, channel string, item ItemRef) error {
 	values := url.Values{
 		"channel": {channel},
@@ -94,12 +98,14 @@ func (api *Client) RemoveStarContext(ctx context.Context, channel string, item I
 	return response.Err()
 }
 
-// ListStars returns information about the stars a user added
+// ListStars returns information about the stars a user added.
+// For more information see the ListStarsContext documentation.
 func (api *Client) ListStars(params StarsParameters) ([]Item, *Paging, error) {
 	return api.ListStarsContext(context.Background(), params)
 }
 
-// ListStarsContext returns information about the stars a user added with a custom context
+// ListStarsContext returns information about the stars a user added with a custom context.
+// Slack API docs: https://api.slack.com/methods/stars.list
 func (api *Client) ListStarsContext(ctx context.Context, params StarsParameters) ([]Item, *Paging, error) {
 	values := url.Values{
 		"token": {api.token},
@@ -147,7 +153,6 @@ func (api *Client) GetStarred(params StarsParameters) ([]StarredItem, *Paging, e
 }
 
 // GetStarredContext returns a list of StarredItem items with a custom context
-//
 // For more details see GetStarred
 func (api *Client) GetStarredContext(ctx context.Context, params StarsParameters) ([]StarredItem, *Paging, error) {
 	items, paging, err := api.ListStarsContext(ctx, params)
diff --git a/team.go b/team.go
index d21a1b6..4e890c2 100644
--- a/team.go
+++ b/team.go
@@ -74,8 +74,9 @@ type BillingActive struct {
 
 // AccessLogParameters contains all the parameters necessary (including the optional ones) for a GetAccessLogs() request
 type AccessLogParameters struct {
-	Count int
-	Page  int
+	TeamID string
+	Count  int
+	Page   int
 }
 
 // NewAccessLogParameters provides an instance of AccessLogParameters with all the sane default values set
@@ -124,12 +125,14 @@ func (api *Client) teamProfileRequest(ctx context.Context, client httpClient, pa
 	return response, response.Err()
 }
 
-// GetTeamInfo gets the Team Information of the user
+// GetTeamInfo gets the Team Information of the user.
+// For more information see the GetTeamInfoContext documentation.
 func (api *Client) GetTeamInfo() (*TeamInfo, error) {
 	return api.GetTeamInfoContext(context.Background())
 }
 
-// GetOtherTeamInfoContext gets Team information for any team with a custom context
+// GetOtherTeamInfoContext gets Team information for any team with a custom context.
+// Slack API docs: https://api.slack.com/methods/team.info
 func (api *Client) GetOtherTeamInfoContext(ctx context.Context, team string) (*TeamInfo, error) {
 	if team == "" {
 		return api.GetTeamInfoContext(ctx)
@@ -145,12 +148,14 @@ func (api *Client) GetOtherTeamInfoContext(ctx context.Context, team string) (*T
 	return &response.Team, nil
 }
 
-// GetOtherTeamInfo gets Team information for any team
+// GetOtherTeamInfo gets Team information for any team.
+// For more information see the GetOtherTeamInfoContext documentation.
 func (api *Client) GetOtherTeamInfo(team string) (*TeamInfo, error) {
 	return api.GetOtherTeamInfoContext(context.Background(), team)
 }
 
-// GetTeamInfoContext gets the Team Information of the user with a custom context
+// GetTeamInfoContext gets the Team Information of the user with a custom context.
+// Slack API docs: https://api.slack.com/methods/team.info
 func (api *Client) GetTeamInfoContext(ctx context.Context) (*TeamInfo, error) {
 	values := url.Values{
 		"token": {api.token},
@@ -163,35 +168,44 @@ func (api *Client) GetTeamInfoContext(ctx context.Context) (*TeamInfo, error) {
 	return &response.Team, nil
 }
 
-// GetTeamProfile gets the Team Profile settings of the user
-func (api *Client) GetTeamProfile() (*TeamProfile, error) {
-	return api.GetTeamProfileContext(context.Background())
+// GetTeamProfile gets the Team Profile settings of the user.
+// For more information see the GetTeamProfileContext documentation.
+func (api *Client) GetTeamProfile(teamID ...string) (*TeamProfile, error) {
+	return api.GetTeamProfileContext(context.Background(), teamID...)
 }
 
-// GetTeamProfileContext gets the Team Profile settings of the user with a custom context
-func (api *Client) GetTeamProfileContext(ctx context.Context) (*TeamProfile, error) {
+// GetTeamProfileContext gets the Team Profile settings of the user with a custom context.
+// Slack API docs: https://api.slack.com/methods/team.profile.get
+func (api *Client) GetTeamProfileContext(ctx context.Context, teamID ...string) (*TeamProfile, error) {
 	values := url.Values{
 		"token": {api.token},
 	}
+	if len(teamID) > 0 {
+		values["team_id"] = teamID
+	}
 
 	response, err := api.teamProfileRequest(ctx, api.httpclient, "team.profile.get", values)
 	if err != nil {
 		return nil, err
 	}
 	return &response.Profile, nil
-
 }
 
-// GetAccessLogs retrieves a page of logins according to the parameters given
+// GetAccessLogs retrieves a page of logins according to the parameters given.
+// For more information see the GetAccessLogsContext documentation.
 func (api *Client) GetAccessLogs(params AccessLogParameters) ([]Login, *Paging, error) {
 	return api.GetAccessLogsContext(context.Background(), params)
 }
 
-// GetAccessLogsContext retrieves a page of logins according to the parameters given with a custom context
+// GetAccessLogsContext retrieves a page of logins according to the parameters given with a custom context.
+// Slack API docs: https://api.slack.com/methods/team.accessLogs
 func (api *Client) GetAccessLogsContext(ctx context.Context, params AccessLogParameters) ([]Login, *Paging, error) {
 	values := url.Values{
 		"token": {api.token},
 	}
+	if params.TeamID != "" {
+		values.Add("team_id", params.TeamID)
+	}
 	if params.Count != DEFAULT_LOGINS_COUNT {
 		values.Add("count", strconv.Itoa(params.Count))
 	}
@@ -206,30 +220,30 @@ func (api *Client) GetAccessLogsContext(ctx context.Context, params AccessLogPar
 	return response.Logins, &response.Paging, nil
 }
 
-// GetBillableInfo ...
-func (api *Client) GetBillableInfo(user string) (map[string]BillingActive, error) {
-	return api.GetBillableInfoContext(context.Background(), user)
+type GetBillableInfoParams struct {
+	User   string
+	TeamID string
 }
 
-// GetBillableInfoContext ...
-func (api *Client) GetBillableInfoContext(ctx context.Context, user string) (map[string]BillingActive, error) {
+// GetBillableInfo gets the billable users information of the team.
+// For more information see the GetBillableInfoContext documentation.
+func (api *Client) GetBillableInfo(params GetBillableInfoParams) (map[string]BillingActive, error) {
+	return api.GetBillableInfoContext(context.Background(), params)
+}
+
+// GetBillableInfoContext gets the billable users information of the team with a custom context.
+// Slack API docs: https://api.slack.com/methods/team.billableInfo
+func (api *Client) GetBillableInfoContext(ctx context.Context, params GetBillableInfoParams) (map[string]BillingActive, error) {
 	values := url.Values{
 		"token": {api.token},
-		"user":  {user},
 	}
 
-	return api.billableInfoRequest(ctx, "team.billableInfo", values)
-}
-
-// GetBillableInfoForTeam returns the billing_active status of all users on the team.
-func (api *Client) GetBillableInfoForTeam() (map[string]BillingActive, error) {
-	return api.GetBillableInfoForTeamContext(context.Background())
-}
+	if params.TeamID != "" {
+		values.Add("team_id", params.TeamID)
+	}
 
-// GetBillableInfoForTeamContext returns the billing_active status of all users on the team with a custom context
-func (api *Client) GetBillableInfoForTeamContext(ctx context.Context) (map[string]BillingActive, error) {
-	values := url.Values{
-		"token": {api.token},
+	if params.User != "" {
+		values.Add("user", params.User)
 	}
 
 	return api.billableInfoRequest(ctx, "team.billableInfo", values)
diff --git a/tokens.go b/tokens.go
new file mode 100644
index 0000000..49bbde9
--- /dev/null
+++ b/tokens.go
@@ -0,0 +1,52 @@
+package slack
+
+import (
+	"context"
+	"net/url"
+)
+
+// RotateTokens exchanges a refresh token for a new app configuration token.
+// For more information see the RotateTokensContext documentation.
+func (api *Client) RotateTokens(configToken string, refreshToken string) (*TokenResponse, error) {
+	return api.RotateTokensContext(context.Background(), configToken, refreshToken)
+}
+
+// RotateTokensContext exchanges a refresh token for a new app configuration token with a custom context.
+// Slack API docs: https://api.slack.com/methods/tooling.tokens.rotate
+func (api *Client) RotateTokensContext(ctx context.Context, configToken string, refreshToken string) (*TokenResponse, error) {
+	if configToken == "" {
+		configToken = api.configToken
+	}
+
+	if refreshToken == "" {
+		refreshToken = api.configRefreshToken
+	}
+
+	values := url.Values{
+		"refresh_token": {refreshToken},
+	}
+
+	response := &TokenResponse{}
+	err := api.getMethod(ctx, "tooling.tokens.rotate", configToken, values, response)
+	if err != nil {
+		return nil, err
+	}
+
+	return response, response.Err()
+}
+
+// UpdateConfigTokens replaces the configuration tokens in the client with those returned by the API
+func (api *Client) UpdateConfigTokens(response *TokenResponse) {
+	api.configToken = response.Token
+	api.configRefreshToken = response.RefreshToken
+}
+
+type TokenResponse struct {
+	Token        string `json:"token,omitempty"`
+	RefreshToken string `json:"refresh_token,omitempty"`
+	TeamId       string `json:"team_id,omitempty"`
+	UserId       string `json:"user_id,omitempty"`
+	IssuedAt     uint64 `json:"iat,omitempty"`
+	ExpiresAt    uint64 `json:"exp,omitempty"`
+	SlackResponse
+}
diff --git a/usergroups.go b/usergroups.go
index c5d7a17..0c2ec4c 100644
--- a/usergroups.go
+++ b/usergroups.go
@@ -50,18 +50,24 @@ func (api *Client) userGroupRequest(ctx context.Context, path string, values url
 	return response, response.Err()
 }
 
-// CreateUserGroup creates a new user group
+// CreateUserGroup creates a new user group.
+// For more information see the CreateUserGroupContext documentation.
 func (api *Client) CreateUserGroup(userGroup UserGroup) (UserGroup, error) {
 	return api.CreateUserGroupContext(context.Background(), userGroup)
 }
 
-// CreateUserGroupContext creates a new user group with a custom context
+// CreateUserGroupContext creates a new user group with a custom context.
+// Slack API docs: https://api.slack.com/methods/usergroups.create
 func (api *Client) CreateUserGroupContext(ctx context.Context, userGroup UserGroup) (UserGroup, error) {
 	values := url.Values{
 		"token": {api.token},
 		"name":  {userGroup.Name},
 	}
 
+	if userGroup.TeamID != "" {
+		values["team_id"] = []string{userGroup.TeamID}
+	}
+
 	if userGroup.Handle != "" {
 		values["handle"] = []string{userGroup.Handle}
 	}
@@ -81,12 +87,14 @@ func (api *Client) CreateUserGroupContext(ctx context.Context, userGroup UserGro
 	return response.UserGroup, nil
 }
 
-// DisableUserGroup disables an existing user group
+// DisableUserGroup disables an existing user group.
+// For more information see the DisableUserGroupContext documentation.
 func (api *Client) DisableUserGroup(userGroup string) (UserGroup, error) {
 	return api.DisableUserGroupContext(context.Background(), userGroup)
 }
 
-// DisableUserGroupContext disables an existing user group with a custom context
+// DisableUserGroupContext disables an existing user group with a custom context.
+// Slack API docs: https://api.slack.com/methods/usergroups.disable
 func (api *Client) DisableUserGroupContext(ctx context.Context, userGroup string) (UserGroup, error) {
 	values := url.Values{
 		"token":     {api.token},
@@ -100,12 +108,14 @@ func (api *Client) DisableUserGroupContext(ctx context.Context, userGroup string
 	return response.UserGroup, nil
 }
 
-// EnableUserGroup enables an existing user group
+// EnableUserGroup enables an existing user group.
+// For more information see the EnableUserGroupContext documentation.
 func (api *Client) EnableUserGroup(userGroup string) (UserGroup, error) {
 	return api.EnableUserGroupContext(context.Background(), userGroup)
 }
 
-// EnableUserGroupContext enables an existing user group with a custom context
+// EnableUserGroupContext enables an existing user group with a custom context.
+// Slack API docs: https://api.slack.com/methods/usergroups.enable
 func (api *Client) EnableUserGroupContext(ctx context.Context, userGroup string) (UserGroup, error) {
 	values := url.Values{
 		"token":     {api.token},
@@ -122,6 +132,12 @@ func (api *Client) EnableUserGroupContext(ctx context.Context, userGroup string)
 // GetUserGroupsOption options for the GetUserGroups method call.
 type GetUserGroupsOption func(*GetUserGroupsParams)
 
+func GetUserGroupsOptionWithTeamID(teamID string) GetUserGroupsOption {
+	return func(params *GetUserGroupsParams) {
+		params.TeamID = teamID
+	}
+}
+
 // GetUserGroupsOptionIncludeCount include the number of users in each User Group (default: false)
 func GetUserGroupsOptionIncludeCount(b bool) GetUserGroupsOption {
 	return func(params *GetUserGroupsParams) {
@@ -145,17 +161,20 @@ func GetUserGroupsOptionIncludeUsers(b bool) GetUserGroupsOption {
 
 // GetUserGroupsParams contains arguments for GetUserGroups method call
 type GetUserGroupsParams struct {
+	TeamID          string
 	IncludeCount    bool
 	IncludeDisabled bool
 	IncludeUsers    bool
 }
 
-// GetUserGroups returns a list of user groups for the team
+// GetUserGroups returns a list of user groups for the team.
+// For more information see the GetUserGroupsContext documentation.
 func (api *Client) GetUserGroups(options ...GetUserGroupsOption) ([]UserGroup, error) {
 	return api.GetUserGroupsContext(context.Background(), options...)
 }
 
-// GetUserGroupsContext returns a list of user groups for the team with a custom context
+// GetUserGroupsContext returns a list of user groups for the team with a custom context.
+// Slack API docs: https://api.slack.com/methods/usergroups.list
 func (api *Client) GetUserGroupsContext(ctx context.Context, options ...GetUserGroupsOption) ([]UserGroup, error) {
 	params := GetUserGroupsParams{}
 
@@ -166,6 +185,9 @@ func (api *Client) GetUserGroupsContext(ctx context.Context, options ...GetUserG
 	values := url.Values{
 		"token": {api.token},
 	}
+	if params.TeamID != "" {
+		values.Add("team_id", params.TeamID)
+	}
 	if params.IncludeCount {
 		values.Add("include_count", "true")
 	}
@@ -222,12 +244,14 @@ type UpdateUserGroupsParams struct {
 	Channels    *[]string
 }
 
-// UpdateUserGroup will update an existing user group
+// UpdateUserGroup will update an existing user group.
+// For more information see the UpdateUserGroupContext documentation.
 func (api *Client) UpdateUserGroup(userGroupID string, options ...UpdateUserGroupsOption) (UserGroup, error) {
 	return api.UpdateUserGroupContext(context.Background(), userGroupID, options...)
 }
 
-// UpdateUserGroupContext will update an existing user group with a custom context
+// UpdateUserGroupContext will update an existing user group with a custom context.
+// Slack API docs: https://api.slack.com/methods/usergroups.update
 func (api *Client) UpdateUserGroupContext(ctx context.Context, userGroupID string, options ...UpdateUserGroupsOption) (UserGroup, error) {
 	params := UpdateUserGroupsParams{}
 
@@ -263,12 +287,14 @@ func (api *Client) UpdateUserGroupContext(ctx context.Context, userGroupID strin
 	return response.UserGroup, nil
 }
 
-// GetUserGroupMembers will retrieve the current list of users in a group
+// GetUserGroupMembers will retrieve the current list of users in a group.
+// For more information see the GetUserGroupMembersContext documentation.
 func (api *Client) GetUserGroupMembers(userGroup string) ([]string, error) {
 	return api.GetUserGroupMembersContext(context.Background(), userGroup)
 }
 
-// GetUserGroupMembersContext will retrieve the current list of users in a group with a custom context
+// GetUserGroupMembersContext will retrieve the current list of users in a group with a custom context.
+// Slack API docs: https://api.slack.com/methods/usergroups.users.list
 func (api *Client) GetUserGroupMembersContext(ctx context.Context, userGroup string) ([]string, error) {
 	values := url.Values{
 		"token":     {api.token},
@@ -282,12 +308,14 @@ func (api *Client) GetUserGroupMembersContext(ctx context.Context, userGroup str
 	return response.Users, nil
 }
 
-// UpdateUserGroupMembers will update the members of an existing user group
+// UpdateUserGroupMembers will update the members of an existing user group.
+// For more information see the UpdateUserGroupMembersContext documentation.
 func (api *Client) UpdateUserGroupMembers(userGroup string, members string) (UserGroup, error) {
 	return api.UpdateUserGroupMembersContext(context.Background(), userGroup, members)
 }
 
-// UpdateUserGroupMembersContext will update the members of an existing user group with a custom context
+// UpdateUserGroupMembersContext will update the members of an existing user group with a custom context.
+// Slack API docs: https://api.slack.com/methods/usergroups.update
 func (api *Client) UpdateUserGroupMembersContext(ctx context.Context, userGroup string, members string) (UserGroup, error) {
 	values := url.Values{
 		"token":     {api.token},
diff --git a/users.go b/users.go
index a3b4c06..b51b172 100644
--- a/users.go
+++ b/users.go
@@ -17,31 +17,32 @@ const (
 
 // UserProfile contains all the information details of a given user
 type UserProfile struct {
-	FirstName              string                              `json:"first_name"`
-	LastName               string                              `json:"last_name"`
+	FirstName              string                              `json:"first_name,omitempty"`
+	LastName               string                              `json:"last_name,omitempty"`
 	RealName               string                              `json:"real_name"`
 	RealNameNormalized     string                              `json:"real_name_normalized"`
 	DisplayName            string                              `json:"display_name"`
 	DisplayNameNormalized  string                              `json:"display_name_normalized"`
-	Email                  string                              `json:"email"`
-	Skype                  string                              `json:"skype"`
-	Phone                  string                              `json:"phone"`
+	AvatarHash             string                              `json:"avatar_hash"`
+	Email                  string                              `json:"email,omitempty"`
+	Skype                  string                              `json:"skyp,omitempty"`
+	Phone                  string                              `json:"phone,omitempty"`
 	Image24                string                              `json:"image_24"`
 	Image32                string                              `json:"image_32"`
 	Image48                string                              `json:"image_48"`
 	Image72                string                              `json:"image_72"`
 	Image192               string                              `json:"image_192"`
 	Image512               string                              `json:"image_512"`
-	ImageOriginal          string                              `json:"image_original"`
-	Title                  string                              `json:"title"`
+	ImageOriginal          string                              `json:"image_original,omitempty"`
+	Title                  string                              `json:"title,omitempty"`
 	BotID                  string                              `json:"bot_id,omitempty"`
 	ApiAppID               string                              `json:"api_app_id,omitempty"`
 	StatusText             string                              `json:"status_text,omitempty"`
 	StatusEmoji            string                              `json:"status_emoji,omitempty"`
 	StatusEmojiDisplayInfo []UserProfileStatusEmojiDisplayInfo `json:"status_emoji_display_info,omitempty"`
-	StatusExpiration       int                                 `json:"status_expiration"`
+	StatusExpiration       int                                 `json:"status_expiration,omitempty"`
 	Team                   string                              `json:"team"`
-	Fields                 UserProfileCustomFields             `json:"fields"`
+	Fields                 UserProfileCustomFields             `json:"fields,omitempty"`
 }
 
 type UserProfileStatusEmojiDisplayInfo struct {
@@ -226,11 +227,13 @@ func (api *Client) userRequest(ctx context.Context, path string, values url.Valu
 }
 
 // GetUserPresence will retrieve the current presence status of given user.
+// For more information see the GetUserPresenceContext documentation.
 func (api *Client) GetUserPresence(user string) (*UserPresence, error) {
 	return api.GetUserPresenceContext(context.Background(), user)
 }
 
 // GetUserPresenceContext will retrieve the current presence status of given user with a custom context.
+// Slack API docs: https://api.slack.com/methods/users.getPresence
 func (api *Client) GetUserPresenceContext(ctx context.Context, user string) (*UserPresence, error) {
 	values := url.Values{
 		"token": {api.token},
@@ -244,12 +247,14 @@ func (api *Client) GetUserPresenceContext(ctx context.Context, user string) (*Us
 	return &response.UserPresence, nil
 }
 
-// GetUserInfo will retrieve the complete user information
+// GetUserInfo will retrieve the complete user information.
+// For more information see the GetUserInfoContext documentation.
 func (api *Client) GetUserInfo(user string) (*User, error) {
 	return api.GetUserInfoContext(context.Background(), user)
 }
 
-// GetUserInfoContext will retrieve the complete user information with a custom context
+// GetUserInfoContext will retrieve the complete user information with a custom context.
+// Slack API docs: https://api.slack.com/methods/users.info
 func (api *Client) GetUserInfoContext(ctx context.Context, user string) (*User, error) {
 	values := url.Values{
 		"token":          {api.token},
@@ -264,12 +269,14 @@ func (api *Client) GetUserInfoContext(ctx context.Context, user string) (*User,
 	return &response.User, nil
 }
 
-// GetUsersInfo will retrieve the complete multi-users information
+// GetUsersInfo will retrieve the complete multi-users information.
+// For more information see the GetUsersInfoContext documentation.
 func (api *Client) GetUsersInfo(users ...string) (*[]User, error) {
 	return api.GetUsersInfoContext(context.Background(), users...)
 }
 
-// GetUsersInfoContext will retrieve the complete multi-users information with a custom context
+// GetUsersInfoContext will retrieve the complete multi-users information with a custom context.
+// Slack API docs: https://api.slack.com/methods/users.info
 func (api *Client) GetUsersInfoContext(ctx context.Context, users ...string) (*[]User, error) {
 	values := url.Values{
 		"token":          {api.token},
@@ -406,12 +413,14 @@ func (api *Client) GetUsersContext(ctx context.Context, options ...GetUsersOptio
 	return results, p.Failure(err)
 }
 
-// GetUserByEmail will retrieve the complete user information by email
+// GetUserByEmail will retrieve the complete user information by email.
+// For more information see the GetUserByEmailContext documentation.
 func (api *Client) GetUserByEmail(email string) (*User, error) {
 	return api.GetUserByEmailContext(context.Background(), email)
 }
 
-// GetUserByEmailContext will retrieve the complete user information by email with a custom context
+// GetUserByEmailContext will retrieve the complete user information by email with a custom context.
+// Slack API docs: https://api.slack.com/methods/users.lookupByEmail
 func (api *Client) GetUserByEmailContext(ctx context.Context, email string) (*User, error) {
 	values := url.Values{
 		"token": {api.token},
@@ -424,12 +433,14 @@ func (api *Client) GetUserByEmailContext(ctx context.Context, email string) (*Us
 	return &response.User, nil
 }
 
-// SetUserAsActive marks the currently authenticated user as active
+// SetUserAsActive marks the currently authenticated user as active.
+// For more information see the SetUserAsActiveContext documentation.
 func (api *Client) SetUserAsActive() error {
 	return api.SetUserAsActiveContext(context.Background())
 }
 
-// SetUserAsActiveContext marks the currently authenticated user as active with a custom context
+// SetUserAsActiveContext marks the currently authenticated user as active with a custom context.
+// Slack API docs: https://api.slack.com/methods/users.setActive
 func (api *Client) SetUserAsActiveContext(ctx context.Context) (err error) {
 	values := url.Values{
 		"token": {api.token},
@@ -439,12 +450,14 @@ func (api *Client) SetUserAsActiveContext(ctx context.Context) (err error) {
 	return err
 }
 
-// SetUserPresence changes the currently authenticated user presence
+// SetUserPresence changes the currently authenticated user presence.
+// For more information see the SetUserPresenceContext documentation.
 func (api *Client) SetUserPresence(presence string) error {
 	return api.SetUserPresenceContext(context.Background(), presence)
 }
 
-// SetUserPresenceContext changes the currently authenticated user presence with a custom context
+// SetUserPresenceContext changes the currently authenticated user presence with a custom context.
+// Slack API docs: https://api.slack.com/methods/users.setPresence
 func (api *Client) SetUserPresenceContext(ctx context.Context, presence string) error {
 	values := url.Values{
 		"token":    {api.token},
@@ -455,12 +468,14 @@ func (api *Client) SetUserPresenceContext(ctx context.Context, presence string)
 	return err
 }
 
-// GetUserIdentity will retrieve user info available per identity scopes
+// GetUserIdentity will retrieve user info available per identity scopes.
+// For more information see the GetUserIdentityContext documentation.
 func (api *Client) GetUserIdentity() (*UserIdentityResponse, error) {
 	return api.GetUserIdentityContext(context.Background())
 }
 
-// GetUserIdentityContext will retrieve user info available per identity scopes with a custom context
+// GetUserIdentityContext will retrieve user info available per identity scopes with a custom context.
+// Slack API docs: https://api.slack.com/methods/users.identity
 func (api *Client) GetUserIdentityContext(ctx context.Context) (response *UserIdentityResponse, err error) {
 	values := url.Values{
 		"token": {api.token},
@@ -479,12 +494,14 @@ func (api *Client) GetUserIdentityContext(ctx context.Context) (response *UserId
 	return response, nil
 }
 
-// SetUserPhoto changes the currently authenticated user's profile image
+// SetUserPhoto changes the currently authenticated user's profile image.
+// For more information see the SetUserPhotoContext documentation.
 func (api *Client) SetUserPhoto(image string, params UserSetPhotoParams) error {
 	return api.SetUserPhotoContext(context.Background(), image, params)
 }
 
-// SetUserPhotoContext changes the currently authenticated user's profile image using a custom context
+// SetUserPhotoContext changes the currently authenticated user's profile image using a custom context.
+// Slack API docs: https://api.slack.com/methods/users.setPhoto
 func (api *Client) SetUserPhotoContext(ctx context.Context, image string, params UserSetPhotoParams) (err error) {
 	response := &SlackResponse{}
 	values := url.Values{}
@@ -506,12 +523,14 @@ func (api *Client) SetUserPhotoContext(ctx context.Context, image string, params
 	return response.Err()
 }
 
-// DeleteUserPhoto deletes the current authenticated user's profile image
+// DeleteUserPhoto deletes the current authenticated user's profile image.
+// For more information see the DeleteUserPhotoContext documentation.
 func (api *Client) DeleteUserPhoto() error {
 	return api.DeleteUserPhotoContext(context.Background())
 }
 
-// DeleteUserPhotoContext deletes the current authenticated user's profile image with a custom context
+// DeleteUserPhotoContext deletes the current authenticated user's profile image with a custom context.
+// Slack API docs: https://api.slack.com/methods/users.deletePhoto
 func (api *Client) DeleteUserPhotoContext(ctx context.Context) (err error) {
 	response := &SlackResponse{}
 	values := url.Values{
@@ -527,13 +546,13 @@ func (api *Client) DeleteUserPhotoContext(ctx context.Context) (err error) {
 }
 
 // SetUserRealName changes the currently authenticated user's realName
-//
-// For more information see SetUserRealNameContextWithUser
+// For more information see the SetUserRealNameContextWithUser documentation.
 func (api *Client) SetUserRealName(realName string) error {
 	return api.SetUserRealNameContextWithUser(context.Background(), "", realName)
 }
 
-// SetUserRealNameContextWithUser will set a real name for the provided user with a custom context
+// SetUserRealNameContextWithUser will set a real name for the provided user with a custom context.
+// Slack API docs: https://api.slack.com/methods/users.profile.set
 func (api *Client) SetUserRealNameContextWithUser(ctx context.Context, user, realName string) error {
 	profile, err := json.Marshal(
 		&struct {
@@ -565,20 +584,22 @@ func (api *Client) SetUserRealNameContextWithUser(ctx context.Context, user, rea
 	return response.Err()
 }
 
-// SetUserCustomFields sets Custom Profile fields on the provided users account.  Due to the non-repeating elements
-// within the request, a map fields is required.  The key in the map signifies the field that will be updated.
-//
-// Note: You may need to change the way the custom field is populated within the Profile section of the Admin Console from
-// SCIM or User Entered to API.
-//
-// See GetTeamProfile for information to retrieve possible fields for your account.
+// SetUserCustomFields sets Custom Profile fields on the provided users account.
+// For more information see the SetUserCustomFieldsContext documentation.
 func (api *Client) SetUserCustomFields(userID string, customFields map[string]UserProfileCustomField) error {
 	return api.SetUserCustomFieldsContext(context.Background(), userID, customFields)
 }
 
-// SetUserCustomFieldsContext will set a users custom profile field with context.
+// SetUserCustomFieldsContext sets Custom Profile fields on the provided users account.
+// Due to the non-repeating elements within the request, a map fields is required.
+// The key in the map signifies the field that will be updated.
+//
+// Note: You may need to change the way the custom field is populated within the Profile section of the Admin Console
+// from SCIM or User Entered to API.
 //
-// For more information see SetUserCustomFields
+// See GetTeamProfile for information to retrieve possible fields for your account.
+//
+// Slack API docs: https://api.slack.com/methods/users.profile.set
 func (api *Client) SetUserCustomFieldsContext(ctx context.Context, userID string, customFields map[string]UserProfileCustomField) error {
 
 	// Convert data to data type with custom marshall / unmarshall
@@ -614,32 +635,30 @@ func (api *Client) SetUserCustomFieldsContext(ctx context.Context, userID string
 
 }
 
-// SetUserCustomStatus will set a custom status and emoji for the currently
-// authenticated user. If statusEmoji is "" and statusText is not, the Slack API
-// will automatically set it to ":speech_balloon:". Otherwise, if both are ""
-// the Slack API will unset the custom status/emoji. If statusExpiration is set to 0
-// the status will not expire.
+// SetUserCustomStatus will set a custom status and emoji for the currently authenticated user.
+// For more information see the SetUserCustomStatusContext documentation.
 func (api *Client) SetUserCustomStatus(statusText, statusEmoji string, statusExpiration int64) error {
 	return api.SetUserCustomStatusContextWithUser(context.Background(), "", statusText, statusEmoji, statusExpiration)
 }
 
-// SetUserCustomStatusContext will set a custom status and emoji for the currently authenticated user with a custom context
-//
-// For more information see SetUserCustomStatus
+// SetUserCustomStatusContext will set a custom status and emoji for the currently authenticated user with a custom context.
+// For more information see the SetUserCustomStatusContextWithUser documentation.
 func (api *Client) SetUserCustomStatusContext(ctx context.Context, statusText, statusEmoji string, statusExpiration int64) error {
 	return api.SetUserCustomStatusContextWithUser(ctx, "", statusText, statusEmoji, statusExpiration)
 }
 
 // SetUserCustomStatusWithUser will set a custom status and emoji for the provided user.
-//
-// For more information see SetUserCustomStatus
+// For more information see the SetUserCustomStatusContextWithUser documentation.
 func (api *Client) SetUserCustomStatusWithUser(user, statusText, statusEmoji string, statusExpiration int64) error {
 	return api.SetUserCustomStatusContextWithUser(context.Background(), user, statusText, statusEmoji, statusExpiration)
 }
 
-// SetUserCustomStatusContextWithUser will set a custom status and emoji for the provided user with a custom context
+// SetUserCustomStatusContextWithUser will set a custom status and emoji for the currently authenticated user.
+// If statusEmoji is "" and statusText is not, the Slack API will automatically set it to ":speech_balloon:".
+// Otherwise, if both are "" the Slack API will unset the custom status/emoji. If statusExpiration is set to 0
+// the status will not expire.
 //
-// For more information see SetUserCustomStatus
+// Slack API docs: https://api.slack.com/methods/users.profile.set
 func (api *Client) SetUserCustomStatusContextWithUser(ctx context.Context, user, statusText, statusEmoji string, statusExpiration int64) error {
 	// XXX(theckman): this anonymous struct is for making requests to the Slack
 	// API for setting and unsetting a User's Custom Status/Emoji. To change
@@ -704,6 +723,7 @@ type GetUserProfileParameters struct {
 }
 
 // GetUserProfile retrieves a user's profile information.
+// For more information see the GetUserProfileContext documentation.
 func (api *Client) GetUserProfile(params *GetUserProfileParameters) (*UserProfile, error) {
 	return api.GetUserProfileContext(context.Background(), params)
 }
@@ -714,6 +734,7 @@ type getUserProfileResponse struct {
 }
 
 // GetUserProfileContext retrieves a user's profile information with a context.
+// Slack API docs: https://api.slack.com/methods/users.profile.get
 func (api *Client) GetUserProfileContext(ctx context.Context, params *GetUserProfileParameters) (*UserProfile, error) {
 	values := url.Values{"token": {api.token}}
 
diff --git a/views.go b/views.go
index e6a9617..0822d2c 100644
--- a/views.go
+++ b/views.go
@@ -155,6 +155,7 @@ type ViewResponse struct {
 }
 
 // OpenView opens a view for a user.
+// For more information see the OpenViewContext documentation.
 func (api *Client) OpenView(triggerID string, view ModalViewRequest) (*ViewResponse, error) {
 	return api.OpenViewContext(context.Background(), triggerID, view)
 }
@@ -177,6 +178,7 @@ func ValidateUniqueBlockID(view ModalViewRequest) bool {
 }
 
 // OpenViewContext opens a view for a user with a custom context.
+// Slack API docs: https://api.slack.com/methods/views.open
 func (api *Client) OpenViewContext(
 	ctx context.Context,
 	triggerID string,
@@ -208,11 +210,13 @@ func (api *Client) OpenViewContext(
 }
 
 // PublishView publishes a static view for a user.
+// For more information see the PublishViewContext documentation.
 func (api *Client) PublishView(userID string, view HomeTabViewRequest, hash string) (*ViewResponse, error) {
 	return api.PublishViewContext(context.Background(), userID, view, hash)
 }
 
 // PublishViewContext publishes a static view for a user with a custom context.
+// Slack API docs: https://api.slack.com/methods/views.publish
 func (api *Client) PublishViewContext(
 	ctx context.Context,
 	userID string,
@@ -241,11 +245,13 @@ func (api *Client) PublishViewContext(
 }
 
 // PushView pushes a view onto the stack of a root view.
+// For more information see the PushViewContext documentation.
 func (api *Client) PushView(triggerID string, view ModalViewRequest) (*ViewResponse, error) {
 	return api.PushViewContext(context.Background(), triggerID, view)
 }
 
-// PublishViewContext pushes a view onto the stack of a root view with a custom context.
+// PushViewContext pushes a view onto the stack of a root view with a custom context.
+// Slack API docs: https://api.slack.com/methods/views.push
 func (api *Client) PushViewContext(
 	ctx context.Context,
 	triggerID string,
@@ -272,11 +278,13 @@ func (api *Client) PushViewContext(
 }
 
 // UpdateView updates an existing view.
+// For more information see the UpdateViewContext documentation.
 func (api *Client) UpdateView(view ModalViewRequest, externalID, hash, viewID string) (*ViewResponse, error) {
 	return api.UpdateViewContext(context.Background(), view, externalID, hash, viewID)
 }
 
 // UpdateViewContext updates an existing view with a custom context.
+// Slack API docs: https://api.slack.com/methods/views.update
 func (api *Client) UpdateViewContext(
 	ctx context.Context,
 	view ModalViewRequest,
diff --git a/webhooks.go b/webhooks.go
index e323353..8fc149d 100644
--- a/webhooks.go
+++ b/webhooks.go
@@ -24,6 +24,8 @@ type WebhookMessage struct {
 	ReplaceOriginal bool         `json:"replace_original"`
 	DeleteOriginal  bool         `json:"delete_original"`
 	ReplyBroadcast  bool         `json:"reply_broadcast,omitempty"`
+	UnfurlLinks     bool         `json:"unfurl_links,omitempty"`
+	UnfurlMedia     bool         `json:"unfurl_media,omitempty"`
 }
 
 func PostWebhook(url string, msg *WebhookMessage) error {
diff --git a/workflow_step.go b/workflow_step.go
index bcc892c..747a5dc 100644
--- a/workflow_step.go
+++ b/workflow_step.go
@@ -44,12 +44,15 @@ func NewConfigurationModalRequest(blocks Blocks, privateMetaData string, externa
 	}
 }
 
+// SaveWorkflowStepConfiguration opens a configuration modal for a workflow step.
+// For more information see the SaveWorkflowStepConfigurationContext documentation.
 func (api *Client) SaveWorkflowStepConfiguration(workflowStepEditID string, inputs *WorkflowStepInputs, outputs *[]WorkflowStepOutput) error {
 	return api.SaveWorkflowStepConfigurationContext(context.Background(), workflowStepEditID, inputs, outputs)
 }
 
+// SaveWorkflowStepConfigurationContext saves the configuration of a workflow step with a custom context.
+// Slack API docs: https://api.slack.com/methods/workflows.updateStep
 func (api *Client) SaveWorkflowStepConfigurationContext(ctx context.Context, workflowStepEditID string, inputs *WorkflowStepInputs, outputs *[]WorkflowStepOutput) error {
-	// More information: https://api.slack.com/methods/workflows.updateStep
 	wscr := WorkflowStepCompleteResponse{
 		WorkflowStepEditID: workflowStepEditID,
 		Inputs:             inputs,


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 7906afb..cbf91c6 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -15,6 +15,8 @@ jobs:
           - '1.18'
           - '1.19'
           - '1.20'
+          - '1.21'
+          - '1.22'
     name: test go-${{ matrix.go }}
     steps:
       - uses: actions/checkout@v3
diff --git a/block_element_test.go b/block_element_test.go
index 0c47aff..d532fb7 100644
--- a/block_element_test.go
+++ b/block_element_test.go
@@ -43,6 +43,16 @@ func TestWithStyleForButtonElement(t *testing.T) {
 
 }
 
+func TestWithURLForButtonElement(t *testing.T) {
+
+	btnTxt := NewTextBlockObject("plain_text", "Next 2 Results", false, false)
+	btnElement := NewButtonBlockElement("test", "click_me_123", btnTxt)
+
+	btnElement.WithURL("https://foo.bar")
+	assert.Equal(t, btnElement.URL, "https://foo.bar")
+
+}
+
 func TestNewOptionsSelectBlockElement(t *testing.T) {
 
 	testOptionText := NewTextBlockObject("plain_text", "Option One", false, false)
@@ -148,6 +158,12 @@ func TestNewPlainTextInputBlockElement(t *testing.T) {
 
 }
 
+func TestNewRichTextInputBlockElement(t *testing.T) {
+	richTextInputElement := NewRichTextInputBlockElement(nil, "test")
+	assert.Equal(t, string(richTextInputElement.Type), "rich_text_input")
+	assert.Equal(t, richTextInputElement.ActionID, "test")
+}
+
 func TestNewEmailTextInputBlockElement(t *testing.T) {
 	emailTextInputElement := NewEmailTextInputBlockElement(nil, "example@example.com")
 
@@ -215,3 +231,19 @@ func TestNewNumberInputBlockElement(t *testing.T) {
 	assert.Equal(t, numberInputElement.IsDecimalAllowed, true)
 
 }
+
+func TestNewFileInputBlockElement(t *testing.T) {
+
+	fileInputElement := NewFileInputBlockElement("test")
+
+	assert.Equal(t, string(fileInputElement.Type), "file_input")
+	assert.Equal(t, fileInputElement.ActionID, "test")
+
+	fileInputElement.WithFileTypes("jpg", "png")
+	assert.Equal(t, len(fileInputElement.FileTypes), 2)
+	assert.Contains(t, fileInputElement.FileTypes, "jpg")
+	assert.Contains(t, fileInputElement.FileTypes, "png")
+
+	fileInputElement.WithMaxFiles(10)
+	assert.Equal(t, fileInputElement.MaxFiles, 10)
+}
diff --git a/block_object_test.go b/block_object_test.go
index 9889fae..1f4874a 100644
--- a/block_object_test.go
+++ b/block_object_test.go
@@ -2,6 +2,7 @@ package slack
 
 import (
 	"errors"
+	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -126,6 +127,24 @@ func TestValidateTextBlockObject(t *testing.T) {
 			},
 			expected: errors.New("emoji cannot be true in mrkdown"),
 		},
+		{
+			input: TextBlockObject{
+				Type:     "mrkdwn",
+				Text:     "",
+				Emoji:    false,
+				Verbatim: false,
+			},
+			expected: errors.New("text must have a minimum length of 1"),
+		},
+		{
+			input: TextBlockObject{
+				Type:     "mrkdwn",
+				Text:     strings.Repeat("a", 3001),
+				Emoji:    false,
+				Verbatim: false,
+			},
+			expected: errors.New("text cannot be longer than 3000 characters"),
+		},
 	}
 
 	for _, test := range tests {
diff --git a/block_rich_text_test.go b/block_rich_text_test.go
index 270dde9..a9f04b7 100644
--- a/block_rich_text_test.go
+++ b/block_rich_text_test.go
@@ -27,6 +27,60 @@ const (
     }
   ]
 }`
+
+	richTextQuotePayload = `{
+		"type": "rich_text",
+		"block_id": "G7G",
+		"elements": [
+			{
+				"type": "rich_text_section",
+				"elements": [
+					{
+						"type": "text",
+						"text": "Holy moly\n\n"
+					}
+				]
+			},
+			{
+				"type": "rich_text_preformatted",
+				"elements": [
+					{
+						"type": "text",
+						"text": "Preformatted\n\n"
+					}
+				],
+				"border": 2
+			},
+			{
+				"type": "rich_text_quote",
+				"elements": [
+					{
+						"type": "text",
+						"text": "Quote\n\n"
+					}
+				]
+			},
+			{
+				"type": "rich_text_quote",
+				"elements": [
+					{
+						"type": "text",
+						"text": "Another quote"
+					}
+				]
+			},
+			{
+				"type": "rich_text_preformatted",
+				"elements": [
+					{
+						"type": "text",
+						"text": "Another preformatted\n\n"
+					}
+				],
+				"border": 42
+			}
+		]
+	}`
 )
 
 func TestRichTextBlock_UnmarshalJSON(t *testing.T) {
@@ -36,11 +90,12 @@ func TestRichTextBlock_UnmarshalJSON(t *testing.T) {
 		err      error
 	}{
 		{
-			[]byte(`{"elements":[{"type":"rich_text_unknown"},{"type":"rich_text_section"}]}`),
+			[]byte(`{"elements":[{"type":"rich_text_unknown"},{"type":"rich_text_section"},{"type":"rich_text_list"}]}`),
 			RichTextBlock{
 				Elements: []RichTextElement{
 					&RichTextUnknown{Type: RTEUnknown, Raw: `{"type":"rich_text_unknown"}`},
 					&RichTextSection{Type: RTESection, Elements: []RichTextSectionElement{}},
+					&RichTextList{Type: RTEList, Elements: []RichTextElement{}},
 				},
 			},
 			nil,
@@ -54,6 +109,38 @@ func TestRichTextBlock_UnmarshalJSON(t *testing.T) {
 			},
 			nil,
 		},
+		{
+			[]byte(dummyPayload),
+			RichTextBlock{
+				Type:    MBTRichText,
+				BlockID: "FaYCD",
+				Elements: []RichTextElement{
+					&RichTextSection{
+						Type: RTESection,
+						Elements: []RichTextSectionElement{
+							&RichTextSectionChannelElement{Type: RTSEChannel, ChannelID: "C012345678"},
+							&RichTextSectionTextElement{Type: RTSEText, Text: "dummy_text"},
+						},
+					},
+				},
+			},
+			nil,
+		},
+		{
+			[]byte(richTextQuotePayload),
+			RichTextBlock{
+				Type:    MBTRichText,
+				BlockID: "G7G",
+				Elements: []RichTextElement{
+					&RichTextSection{Type: RTESection, Elements: []RichTextSectionElement{&RichTextSectionTextElement{Type: RTSEText, Text: "Holy moly\n\n"}}},
+					&RichTextPreformatted{RichTextSection: RichTextSection{Type: RTEPreformatted, Elements: []RichTextSectionElement{&RichTextSectionTextElement{Type: RTSEText, Text: "Preformatted\n\n"}}}, Border: 2},
+					&RichTextQuote{Type: RTEQuote, Elements: []RichTextSectionElement{&RichTextSectionTextElement{Type: RTSEText, Text: "Quote\n\n"}}},
+					&RichTextQuote{Type: RTEQuote, Elements: []RichTextSectionElement{&RichTextSectionTextElement{Type: RTSEText, Text: "Another quote"}}},
+					&RichTextPreformatted{RichTextSection: RichTextSection{Type: RTEPreformatted, Elements: []RichTextSectionElement{&RichTextSectionTextElement{Type: RTSEText, Text: "Another preformatted\n\n"}}}, Border: 42},
+				},
+			},
+			nil,
+		},
 	}
 	for _, tc := range cases {
 		var actual RichTextBlock
@@ -62,10 +149,10 @@ func TestRichTextBlock_UnmarshalJSON(t *testing.T) {
 			if tc.err == nil {
 				t.Errorf("unexpected error: %s", err)
 			}
-			t.Errorf("expected error is %s, but got %s", tc.err, err)
+			t.Errorf("expected error is %v, but got %v", tc.err, err)
 		}
 		if tc.err != nil {
-			t.Errorf("expected to raise an error %s", tc.err)
+			t.Errorf("expected to raise an error %v", tc.err)
 		}
 		if diff := deep.Equal(actual, tc.expected); diff != nil {
 			t.Errorf("actual value does not match expected one\n%s", diff)
@@ -117,3 +204,160 @@ func TestRichTextSection_UnmarshalJSON(t *testing.T) {
 		}
 	}
 }
+
+func TestRichTextList_UnmarshalJSON(t *testing.T) {
+	cases := []struct {
+		raw      []byte
+		expected RichTextList
+		err      error
+	}{
+		{
+			[]byte(`{"style":"ordered","elements":[{"type":"rich_text_unknown","value":10},{"type":"rich_text_section","elements":[{"type":"text","text":"hi"}]}]}`),
+			RichTextList{
+				Type:  RTEList,
+				Style: RTEListOrdered,
+				Elements: []RichTextElement{
+					&RichTextUnknown{Type: RTEUnknown, Raw: `{"type":"rich_text_unknown","value":10}`},
+					&RichTextSection{
+						Type: RTESection,
+						Elements: []RichTextSectionElement{
+							&RichTextSectionTextElement{Type: RTSEText, Text: "hi"},
+						},
+					},
+				},
+			},
+			nil,
+		},
+		{
+			[]byte(`{"style":"ordered","elements":[{"type":"rich_text_list","style":"bullet","elements":[{"type":"rich_text_section","elements":[{"type":"text","text":"hi"}]}]}]}`),
+			RichTextList{
+				Type:  RTEList,
+				Style: RTEListOrdered,
+				Elements: []RichTextElement{
+					&RichTextList{
+						Type:  RTEList,
+						Style: RTEListBullet,
+						Elements: []RichTextElement{
+							&RichTextSection{
+								Type: RTESection,
+								Elements: []RichTextSectionElement{
+									&RichTextSectionTextElement{Type: RTSEText, Text: "hi"},
+								},
+							},
+						},
+					},
+				},
+			},
+			nil,
+		},
+		{
+			[]byte(`{"type": "rich_text_list","elements":[]}`),
+			RichTextList{
+				Type:     RTEList,
+				Elements: []RichTextElement{},
+			},
+			nil,
+		},
+		{
+			[]byte(`{"type": "rich_text_list","elements":[],"indent":2}`),
+			RichTextList{
+				Type:     RTEList,
+				Indent:   2,
+				Elements: []RichTextElement{},
+			},
+			nil,
+		},
+	}
+	for _, tc := range cases {
+		var actual RichTextList
+		err := json.Unmarshal(tc.raw, &actual)
+		if err != nil {
+			if tc.err == nil {
+				t.Errorf("unexpected error: %s", err)
+			}
+			t.Errorf("expected error is %s, but got %s", tc.err, err)
+		}
+		if tc.err != nil {
+			t.Errorf("expected to raise an error %s", tc.err)
+		}
+		if diff := deep.Equal(actual, tc.expected); diff != nil {
+			t.Errorf("actual value does not match expected one\n%s", diff)
+		}
+	}
+}
+
+func TestRichTextQuote_Marshal(t *testing.T) {
+	t.Run("rich_text_section", func(t *testing.T) {
+		const rawRSE = "{\"type\":\"rich_text_section\",\"elements\":[{\"type\":\"text\",\"text\":\"Some Text\"}]}"
+
+		var got RichTextSection
+		if err := json.Unmarshal([]byte(rawRSE), &got); err != nil {
+			t.Fatal(err)
+		}
+		want := RichTextSection{
+			Type: RTESection,
+			Elements: []RichTextSectionElement{
+				&RichTextSectionTextElement{Type: RTSEText, Text: "Some Text"},
+			},
+		}
+
+		if diff := deep.Equal(got, want); diff != nil {
+			t.Errorf("actual value does not match expected one\n%s", diff)
+		}
+		b, err := json.Marshal(got)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if diff := deep.Equal(string(b), rawRSE); diff != nil {
+			t.Errorf("actual value does not match expected one\n%s", diff)
+		}
+	})
+	t.Run("rich_text_quote", func(t *testing.T) {
+		const rawRTS = "{\"type\":\"rich_text_quote\",\"elements\":[{\"type\":\"text\",\"text\":\"Some text\"}]}"
+
+		var got RichTextQuote
+		if err := json.Unmarshal([]byte(rawRTS), &got); err != nil {
+			t.Fatal(err)
+		}
+		want := RichTextQuote{
+			Type: RTEQuote,
+			Elements: []RichTextSectionElement{
+				&RichTextSectionTextElement{Type: RTSEText, Text: "Some text"},
+			},
+		}
+		if diff := deep.Equal(got, want); diff != nil {
+			t.Errorf("actual value does not match expected one\n%s", diff)
+		}
+		b, err := json.Marshal(got)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if diff := deep.Equal(string(b), rawRTS); diff != nil {
+			t.Errorf("actual value does not match expected one\n%s", diff)
+		}
+	})
+	t.Run("rich_text_preformatted", func(t *testing.T) {
+		const rawRTP = "{\"type\":\"rich_text_preformatted\",\"elements\":[{\"type\":\"text\",\"text\":\"Some other text\"}],\"border\":2}"
+		want := RichTextPreformatted{
+			RichTextSection: RichTextSection{
+				Type:     RTEPreformatted,
+				Elements: []RichTextSectionElement{&RichTextSectionTextElement{Type: RTSEText, Text: "Some other text"}},
+			},
+			Border: 2,
+		}
+		var got RichTextPreformatted
+		if err := json.Unmarshal([]byte(rawRTP), &got); err != nil {
+			t.Fatal(err)
+		}
+		if diff := deep.Equal(got, want); diff != nil {
+			t.Errorf("actual value does not match expected one\n%s", diff)
+		}
+		b, err := json.Marshal(got)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if diff := deep.Equal(string(b), rawRTP); diff != nil {
+			t.Errorf("actual value does not match expected one\n%s", diff)
+		}
+	})
+}
diff --git a/block_video_test.go b/block_video_test.go
new file mode 100644
index 0000000..d4b791f
--- /dev/null
+++ b/block_video_test.go
@@ -0,0 +1,23 @@
+package slack
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNewVideoBlock(t *testing.T) {
+
+	videoTitle := NewTextBlockObject("plain_text", "VideoTitle", false, false)
+	videoBlock := NewVideoBlock(
+		"https://example.com/example.mp4",
+		"https://example.com/thumbnail.png",
+		"alternative text", "blockID", videoTitle)
+
+	assert.Equal(t, string(videoBlock.Type), "video")
+	assert.Equal(t, videoBlock.Title.Type, "plain_text")
+	assert.Equal(t, videoBlock.BlockID, "blockID")
+	assert.Contains(t, videoBlock.Title.Text, "VideoTitle")
+	assert.Contains(t, videoBlock.VideoURL, "example.mp4")
+
+}
diff --git a/bots_test.go b/bots_test.go
index ce7f668..14a509e 100644
--- a/bots_test.go
+++ b/bots_test.go
@@ -29,7 +29,7 @@ func TestGetBotInfo(t *testing.T) {
 	once.Do(startServer)
 	api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
 
-	bot, err := api.GetBotInfo("B02875YLA")
+	bot, err := api.GetBotInfo(GetBotInfoParameters{Bot: "B02875YLA"})
 	if err != nil {
 		t.Errorf("Unexpected error: %s", err)
 		return
diff --git a/chat_test.go b/chat_test.go
index 7a549fa..ee93bdd 100644
--- a/chat_test.go
+++ b/chat_test.go
@@ -1,11 +1,14 @@
 package slack
 
 import (
+	"bytes"
 	"encoding/json"
 	"io/ioutil"
+	"log"
 	"net/http"
 	"net/url"
 	"reflect"
+	"regexp"
 	"testing"
 )
 
@@ -39,7 +42,6 @@ func TestGetPermalink(t *testing.T) {
 	timeStamp := "p135854651500008"
 
 	http.HandleFunc("/chat.getPermalink", func(rw http.ResponseWriter, r *http.Request) {
-
 		if got, want := r.Header.Get("Content-Type"), "application/x-www-form-urlencoded"; got != want {
 			t.Errorf("request uses unexpected content type: got %s, want %s", got, want)
 		}
@@ -183,6 +185,28 @@ func TestPostMessage(t *testing.T) {
 				"user_auth_message": []string{"Please!"},
 			},
 		},
+		"LinkNames true": {
+			endpoint: "/chat.postMessage",
+			opt: []MsgOption{
+				MsgOptionLinkNames(true),
+			},
+			expected: url.Values{
+				"channel":    []string{"CXXX"},
+				"token":      []string{"testing-token"},
+				"link_names": []string{"true"},
+			},
+		},
+		"LinkNames false": {
+			endpoint: "/chat.postMessage",
+			opt: []MsgOption{
+				MsgOptionLinkNames(false),
+			},
+			expected: url.Values{
+				"channel":    []string{"CXXX"},
+				"token":      []string{"testing-token"},
+				"link_names": []string{"false"},
+			},
+		},
 	}
 
 	once.Do(startServer)
@@ -296,3 +320,50 @@ func TestPostMessageWhenMsgOptionDeleteOriginalApplied(t *testing.T) {
 
 	_, _, _ = api.PostMessage("CXXX", MsgOptionDeleteOriginal(responseURL))
 }
+
+func TestSendMessageContextRedactsTokenInDebugLog(t *testing.T) {
+	tests := []struct {
+		name  string
+		token string
+		want  string
+	}{
+		{
+			name:  "regular token",
+			token: "xtest-token-1234-abcd",
+			want:  "xtest-REDACTED",
+		},
+		{
+			name:  "refresh token",
+			token: "xoxe.xtest-token-1234-abcd",
+			want:  "xoxe.xtest-REDACTED",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			once.Do(startServer)
+			buf := bytes.NewBufferString("")
+
+			opts := []Option{
+				OptionAPIURL("http://" + serverAddr + "/"),
+				OptionLog(log.New(buf, "", log.Lshortfile)),
+				OptionDebug(true),
+			}
+			api := New(tt.token, opts...)
+			// Why send the token in the message text too? To test that we're not
+			// redacting substrings in the request which look like a token but aren't.
+			api.SendMessage("CXXX", MsgOptionText(token, false))
+			s := buf.String()
+
+			re := regexp.MustCompile(`token=[\w.-]*`)
+			want := "token=" + tt.want
+			if got := re.FindString(s); got != want {
+				t.Errorf("Logged token in SendMessageContext(): got %q, want %q", got, want)
+			}
+			re = regexp.MustCompile(`text=[\w.-]*`)
+			want = "text=" + token
+			if got := re.FindString(s); got != want {
+				t.Errorf("Logged text in SendMessageContext(): got %q, want %q", got, want)
+			}
+		})
+	}
+}
diff --git a/conversation_test.go b/conversation_test.go
index 85011d5..1c6b8a2 100644
--- a/conversation_test.go
+++ b/conversation_test.go
@@ -287,6 +287,20 @@ func okChannelJsonHandler(rw http.ResponseWriter, r *http.Request) {
 	rw.Write(response)
 }
 
+func okInviteSharedJsonHandler(rw http.ResponseWriter, r *http.Request) {
+	rw.Header().Set("Content-Type", "application/json")
+	response, _ := json.Marshal(struct {
+		SlackResponse
+		InviteID              string `json:"invite_id"`
+		IsLegacySharedChannel bool   `json:"is_legacy_shared_channel"`
+	}{
+		SlackResponse:         SlackResponse{Ok: true},
+		InviteID:              "I01234567",
+		IsLegacySharedChannel: false,
+	})
+	rw.Write(response)
+}
+
 func TestSetTopicOfConversation(t *testing.T) {
 	http.HandleFunc("/conversations.setTopic", okChannelJsonHandler)
 	once.Do(startServer)
@@ -348,6 +362,44 @@ func TestInviteUsersToConversation(t *testing.T) {
 	}
 }
 
+func TestInviteSharedToConversation(t *testing.T) {
+	http.HandleFunc("/conversations.inviteShared", okInviteSharedJsonHandler)
+	once.Do(startServer)
+	api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
+
+	t.Run("user_ids", func(t *testing.T) {
+		userIDs := []string{"UXXXXXXX1", "UXXXXXXX2"}
+		inviteID, isLegacySharedChannel, err := api.InviteSharedUserIDsToConversation("CXXXXXXXX", userIDs...)
+		if err != nil {
+			t.Errorf("Unexpected error: %s", err)
+			return
+		}
+		if inviteID == "" {
+			t.Error("invite id should have a value")
+			return
+		}
+		if isLegacySharedChannel {
+			t.Error("is legacy shared channel should be false")
+		}
+	})
+
+	t.Run("emails", func(t *testing.T) {
+		emails := []string{"nopcoder@slack.com", "nopcoder@example.com"}
+		inviteID, isLegacySharedChannel, err := api.InviteSharedEmailsToConversation("CXXXXXXXX", emails...)
+		if err != nil {
+			t.Errorf("Unexpected error: %s", err)
+			return
+		}
+		if inviteID == "" {
+			t.Error("invite id should have a value")
+			return
+		}
+		if isLegacySharedChannel {
+			t.Error("is legacy shared channel should be false")
+		}
+	})
+}
+
 func TestKickUserFromConversation(t *testing.T) {
 	http.HandleFunc("/conversations.kick", okJSONHandler)
 	once.Do(startServer)
diff --git a/files_test.go b/files_test.go
index f304a4e..59afc1c 100644
--- a/files_test.go
+++ b/files_test.go
@@ -272,4 +272,13 @@ func TestUploadFileV2(t *testing.T) {
 	if _, err := api.UploadFileV2(params); err != nil {
 		t.Errorf("Unexpected error: %s", err)
 	}
+
+	reader = bytes.NewBufferString("test no channel")
+	params = UploadFileV2Parameters{
+		Filename: "test.txt",
+		Reader:   reader,
+		FileSize: 15}
+	if _, err := api.UploadFileV2(params); err != nil {
+		t.Errorf("Unexpected error: %s", err)
+	}
 }
diff --git a/interactions_test.go b/interactions_test.go
index f492b1f..989690e 100644
--- a/interactions_test.go
+++ b/interactions_test.go
@@ -286,14 +286,14 @@ func TestViewSubmissionCallback(t *testing.T) {
 			},
 			State: &ViewState{
 				Values: map[string]map[string]BlockAction{
-					"multi-line": map[string]BlockAction{
-						"ml-value": BlockAction{
+					"multi-line": {
+						"ml-value": {
 							Type:  "plain_text_input",
 							Value: "No onions",
 						},
 					},
-					"target_channel": map[string]BlockAction{
-						"target_select": BlockAction{
+					"target_channel": {
+						"target_select": {
 							Type:  "conversations_select",
 							Value: "C1AB2C3DE",
 						},
diff --git a/manifests_test.go b/manifests_test.go
new file mode 100644
index 0000000..9133e68
--- /dev/null
+++ b/manifests_test.go
@@ -0,0 +1,149 @@
+package slack
+
+import (
+	"encoding/json"
+	"net/http"
+	"reflect"
+	"testing"
+)
+
+func TestCreateManifest(t *testing.T) {
+	http.HandleFunc("/apps.manifest.create", handleCreateManifest)
+	once.Do(startServer)
+
+	api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
+
+	manif := getTestManifest()
+	resp, err := api.CreateManifest(&manif, "token")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+		return
+	}
+
+	if !reflect.DeepEqual(resp, getTestManifestResponse()) {
+		t.Fatal(ErrIncorrectResponse)
+	}
+}
+
+func handleCreateManifest(rw http.ResponseWriter, r *http.Request) {
+	rw.Header().Set("Content-Type", "application/json")
+
+	response, _ := json.Marshal(getTestManifestResponse())
+	rw.Write(response)
+}
+
+func TestDeleteManifest(t *testing.T) {
+	http.HandleFunc("/apps.manifest.delete", handleDeleteManifest)
+	expectedResponse := SlackResponse{Ok: true}
+
+	once.Do(startServer)
+	api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
+
+	resp, err := api.DeleteManifest("token", "app id")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+		return
+	}
+
+	if !reflect.DeepEqual(expectedResponse, *resp) {
+		t.Fatal(ErrIncorrectResponse)
+	}
+}
+
+func handleDeleteManifest(rw http.ResponseWriter, r *http.Request) {
+	rw.Header().Set("Content-Type", "application/json")
+
+	response, _ := json.Marshal(SlackResponse{Ok: true})
+	rw.Write(response)
+}
+
+func TestExportManifest(t *testing.T) {
+	http.HandleFunc("/apps.manifest.export", handleExportManifest)
+	expectedResponse := getTestManifest()
+
+	api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
+
+	resp, err := api.ExportManifest("token", "app id")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+		return
+	}
+
+	if !reflect.DeepEqual(expectedResponse, *resp) {
+		t.Fatal(ErrIncorrectResponse)
+	}
+}
+
+func handleExportManifest(rw http.ResponseWriter, r *http.Request) {
+	rw.Header().Set("Content-Type", "application/json")
+
+	response, _ := json.Marshal(ExportManifestResponse{Manifest: getTestManifest()})
+	rw.Write(response)
+}
+
+func TestUpdateManifest(t *testing.T) {
+	http.HandleFunc("/apps.manifest.update", handleUpdateManifest)
+	expectedResponse := UpdateManifestResponse{AppId: "app id"}
+
+	api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
+
+	manif := getTestManifest()
+	resp, err := api.UpdateManifest(&manif, "token", "app id")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+		return
+	}
+
+	if !reflect.DeepEqual(expectedResponse, *resp) {
+		t.Fatal(ErrIncorrectResponse)
+	}
+}
+
+func handleUpdateManifest(rw http.ResponseWriter, r *http.Request) {
+	rw.Header().Set("Content-Type", "application/json")
+
+	response, _ := json.Marshal(UpdateManifestResponse{AppId: "app id"})
+	rw.Write(response)
+}
+
+func TestValidateManifest(t *testing.T) {
+	http.HandleFunc("/apps.manifest.validate", handleValidateManifest)
+	expectedResponse := ManifestResponse{SlackResponse: SlackResponse{Ok: true}}
+
+	api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
+
+	manif := getTestManifest()
+	resp, err := api.ValidateManifest(&manif, "token", "app id")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+		return
+	}
+
+	if !reflect.DeepEqual(expectedResponse, *resp) {
+		t.Fatal(ErrIncorrectResponse)
+	}
+}
+
+func handleValidateManifest(rw http.ResponseWriter, r *http.Request) {
+	rw.Header().Set("Content-Type", "application/json")
+
+	response, _ := json.Marshal(ManifestResponse{SlackResponse: SlackResponse{Ok: true}})
+	rw.Write(response)
+}
+
+func getTestManifest() Manifest {
+	return Manifest{
+		Display: Display{
+			Name:        "test",
+			Description: "this is a test",
+		},
+	}
+}
+
+func getTestManifestResponse() *ManifestResponse {
+	return &ManifestResponse{
+		SlackResponse: SlackResponse{
+			Ok: true,
+		},
+	}
+}
diff --git a/slackevents/inner_events_test.go b/slackevents/inner_events_test.go
index f21f66b..20b8bff 100644
--- a/slackevents/inner_events_test.go
+++ b/slackevents/inner_events_test.go
@@ -2,7 +2,10 @@ package slackevents
 
 import (
 	"encoding/json"
+	"fmt"
 	"testing"
+
+	"github.com/stretchr/testify/assert"
 )
 
 func TestAppMention(t *testing.T) {
@@ -376,19 +379,31 @@ func TestThreadBroadcastEvent(t *testing.T) {
 
 func TestMemberJoinedChannelEvent(t *testing.T) {
 	rawE := []byte(`
-			{
-				"type": "member_joined_channel",
-				"user": "W06GH7XHN",
-				"channel": "C0698JE0H",
-				"channel_type": "C",
-				"team": "T024BE7LD",
-				"inviter": "U123456789"
+		{
+			"type": "member_joined_channel",
+			"user": "W06GH7XHN",
+			"channel": "C0698JE0H",
+			"channel_type": "C",
+			"team": "T024BE7LD",
+			"inviter": "U123456789"
 		}
 	`)
-	err := json.Unmarshal(rawE, &MemberJoinedChannelEvent{})
+	evt := MemberJoinedChannelEvent{}
+	err := json.Unmarshal(rawE, &evt)
 	if err != nil {
 		t.Error(err)
 	}
+
+	expected := MemberJoinedChannelEvent{
+		Type:        "member_joined_channel",
+		User:        "W06GH7XHN",
+		Channel:     "C0698JE0H",
+		ChannelType: "C",
+		Team:        "T024BE7LD",
+		Inviter:     "U123456789",
+	}
+
+	assert.Equal(t, expected, evt)
 }
 
 func TestMemberLeftChannelEvent(t *testing.T) {
@@ -914,3 +929,572 @@ func TestUserProfileChanged(t *testing.T) {
 		t.Fail()
 	}
 }
+
+func TestSharedChannelInvite(t *testing.T) {
+	rawE := []byte(`
+	{
+		"token": "whatever",
+		"team_id": "whatever",
+		"api_app_id": "whatever",
+		"event": {
+			"type": "shared_channel_invite_received",
+			"invite": {
+				"id": "I028YDERZSQ",
+				"date_created": 1626876000,
+				"date_invalid": 1628085600,
+				"inviting_team": {
+					"id": "T12345678",
+					"name": "Corgis",
+					"icon": {},
+					"is_verified": false,
+					"domain": "corgis",
+					"date_created": 1480946400
+				},
+				"inviting_user": {
+					"id": "U12345678",
+					"team_id": "T12345678",
+					"name": "crus",
+					"updated": 1608081902,
+					"profile": {
+						"real_name": "Corgis Rus",
+						"display_name": "Corgis Rus",
+						"real_name_normalized": "Corgis Rus",
+						"display_name_normalized": "Corgis Rus",
+						"team": "T12345678",
+						"avatar_hash": "gcfh83a4c72k",
+						"email": "corgisrus@slack-corp.com",
+						"image_24": "https://placekitten.com/24/24",
+						"image_32": "https://placekitten.com/32/32",
+						"image_48": "https://placekitten.com/48/48",
+						"image_72": "https://placekitten.com/72/72",
+						"image_192": "https://placekitten.com/192/192",
+						"image_512": "https://placekitten.com/512/512"
+					}
+				},
+				"recipient_user_id": "U87654321"
+			},
+			"channel": {
+				"id": "C12345678",
+				"is_private": false,
+				"is_im": false,
+				"name": "test-slack-connect"
+			},
+			"event_ts": "1626876010.000100"
+		}
+	}
+	`)
+
+	evt := &EventsAPICallbackEvent{}
+	err := json.Unmarshal(rawE, evt)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	parsedEvent, err := parseInnerEvent(evt)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	actual, ok := parsedEvent.InnerEvent.Data.(*SharedChannelInviteReceivedEvent)
+	if !ok {
+		t.Fail()
+	}
+
+	if actual.Invite.ID != "I028YDERZSQ" {
+		t.Fail()
+	}
+
+	if actual.Invite.InvitingTeam.ID != "T12345678" {
+		t.Fail()
+	}
+
+	if actual.Invite.InvitingUser.ID != "U12345678" {
+		t.Fail()
+	}
+
+	if actual.Invite.RecipientUserID != "U87654321" {
+		t.Fail()
+	}
+
+	if actual.Channel.ID != "C12345678" {
+		t.Fail()
+	}
+
+	if parsedEvent.InnerEvent.Type != "shared_channel_invite_received" {
+		t.Fail()
+	}
+
+}
+
+// Test that the shared_channel_invite_accepted event can be unmarshalled
+func TestSharedChannelAccepted(t *testing.T) {
+	rawE := []byte(`
+	{
+		"token": "whatever",
+		"team_id": "whatever",
+		"api_app_id": "whatever",
+		"event": {
+			"type": "shared_channel_invite_accepted",
+			"approval_required": false,
+			"invite": {
+				"id": "I028YDERZSQ",
+				"date_created": 1626876000,
+				"date_invalid": 1628085600,
+				"inviting_team": {
+					"id": "T12345678",
+					"name": "Corgis",
+					"icon": {
+						"image_default": true,
+						"image_34": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-34.png",
+						"image_44": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-44.png",
+						"image_68": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-68.png",
+						"image_88": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-88.png",
+						"image_102": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-102.png",
+						"image_230": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-230.png",
+						"image_132": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-132.png"
+					  },
+					"is_verified": false,
+					"domain": "corgis",
+					"date_created": 1480946400
+				},
+				"inviting_user": {
+					"id": "U12345678",
+					"team_id": "T12345678",
+					"name": "crus",
+					"updated": 1608081902,
+					"profile": {
+						"real_name": "Corgis Rus",
+						"display_name": "Corgis Rus",
+						"real_name_normalized": "Corgis Rus",
+						"display_name_normalized": "Corgis Rus",
+						"team": "T12345678",
+						"avatar_hash": "gcfh83a4c72k",
+						"email": "corgisrus@slack-corp.com",
+						"image_24": "https://placekitten.com/24/24",
+						"image_32": "https://placekitten.com/32/32",
+						"image_48": "https://placekitten.com/48/48",
+						"image_72": "https://placekitten.com/72/72",
+						"image_192": "https://placekitten.com/192/192",
+						"image_512": "https://placekitten.com/512/512"
+					}
+				},
+				"recipient_email": "golden@doodle.com",
+				"recipient_user_id": "U87654321"
+			},
+			"channel": {
+				"id": "C12345678",
+				"is_private": false,
+				"is_im": false,
+				"name": "test-slack-connect"
+			},
+			"teams_in_channel": [
+				{
+				"id": "T12345678",
+				"name": "Corgis",
+				"icon": {
+					"image_default": true,
+					"image_34": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-34.png",
+					"image_44": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-44.png",
+					"image_68": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-68.png",
+					"image_88": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-88.png",
+					"image_102": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-102.png",
+					"image_230": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-230.png",
+					"image_132": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-132.png"
+				  },
+				"is_verified": false,
+				"domain": "corgis",
+				"date_created": 1626789600
+				}
+			],
+			"accepting_user": {
+				"id": "U87654321",
+				"team_id": "T87654321",
+				"name": "golden",
+				"updated": 1624406113,
+				"profile": {
+					"real_name": "Golden Doodle",
+					"display_name": "Golden",
+					"real_name_normalized": "Golden Doodle",
+					"display_name_normalized": "Golden",
+					"team": "T87654321",
+					"avatar_hash": "g717728b118x",
+					"email": "golden@doodle.com",
+					"image_24": "https://placekitten.com/24/24",
+					"image_32": "https://placekitten.com/32/32",
+					"image_48": "https://placekitten.com/48/48",
+					"image_72": "https://placekitten.com/72/72",
+					"image_192": "https://placekitten.com/192/192",
+					"image_512": "https://placekitten.com/512/512"
+				}
+			},
+			"event_ts": "1626877800.000000"
+		}
+	}
+	`)
+
+	evt := &EventsAPICallbackEvent{}
+	err := json.Unmarshal(rawE, evt)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	parsedEvent, err := parseInnerEvent(evt)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	actual, ok := parsedEvent.InnerEvent.Data.(*SharedChannelInviteAcceptedEvent)
+	if !ok {
+		t.Fail()
+	}
+
+	if actual.Invite.ID != "I028YDERZSQ" {
+		t.Fail()
+	}
+
+	if actual.Invite.InvitingTeam.ID != "T12345678" {
+		t.Fail()
+	}
+
+	if actual.Invite.InvitingUser.ID != "U12345678" {
+		t.Fail()
+	}
+
+	if actual.Invite.RecipientUserID != "U87654321" {
+		t.Fail()
+	}
+
+	if actual.Channel.ID != "C12345678" {
+		t.Fail()
+	}
+
+	if actual.Channel.Name != "test-slack-connect" {
+		t.Fail()
+		fmt.Println(actual.Channel.Name + ", does not match the test name.")
+	}
+
+	if actual.AcceptingUser.ID != "U87654321" {
+		t.Fail()
+	}
+
+	if actual.AcceptingUser.Profile.RealName != "Golden Doodle" {
+		t.Fail()
+	}
+
+	if parsedEvent.InnerEvent.Type != "shared_channel_invite_accepted" {
+		t.Fail()
+	}
+
+}
+
+// Test that the shared_channel_invite_declined event can be unmarshalled
+func TestSharedChannelApproved(t *testing.T) {
+	rawE := []byte(`
+	{
+		"token": "whatever",
+		"team_id": "whatever",
+		"api_app_id": "whatever",
+		"event": {
+			"type": "shared_channel_invite_approved",
+			"invite": {
+				"id": "I01354X80CA",
+				"date_created": 1626876000,
+				"date_invalid": 1628085600,
+				"inviting_team": {
+					"id": "T12345678",
+					"name": "Corgis",
+					"icon": {
+						"image_default": true,
+						"image_34": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-34.png",
+						"image_44": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-44.png",
+						"image_68": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-68.png",
+						"image_88": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-88.png",
+						"image_102": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-102.png",
+						"image_230": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-230.png",
+						"image_132": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-132.png"
+					  },
+					"is_verified": false,
+					"domain": "corgis",
+					"date_created": 1480946400
+				},
+				"inviting_user": {
+					"id": "U12345678",
+					"team_id": "T12345678",
+					"name": "crus",
+					"updated": 1608081902,
+					"profile": {
+						"real_name": "Corgis Rus",
+						"display_name": "Corgis Rus",
+						"real_name_normalized": "Corgis Rus",
+						"display_name_normalized": "Corgis Rus",
+						"team": "T12345678",
+						"avatar_hash": "gcfh83a4c72k",
+						"email": "corgisrus@slack-corp.com",
+						"image_24": "https://placekitten.com/24/24",
+						"image_32": "https://placekitten.com/32/32",
+						"image_48": "https://placekitten.com/48/48",
+						"image_72": "https://placekitten.com/72/72",
+						"image_192": "https://placekitten.com/192/192",
+						"image_512": "https://placekitten.com/512/512"
+					}
+				},
+				"recipient_email": "golden@doodle.com",
+				"recipient_user_id": "U87654321"
+			},
+			"channel": {
+				"id": "C12345678",
+				"is_private": false,
+				"is_im": false,
+				"name": "test-slack-connect"
+			},
+			"approving_team_id": "T87654321",
+			"teams_in_channel": [
+				{
+				"id": "T12345678",
+				"name": "Corgis",
+				"icon": {
+					"image_default": true,
+					"image_34": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-34.png",
+					"image_44": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-44.png",
+					"image_68": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-68.png",
+					"image_88": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-88.png",
+					"image_102": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-102.png",
+					"image_230": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-230.png",
+					"image_132": "https://a.slack-edge.com/80588/img/avatars-teams/ava_0011-132.png"
+				  },
+				"is_verified": false,
+				"domain": "corgis",
+				"date_created": 1626789600
+				}
+			],
+			"approving_user": {
+				"id": "U012A3CDE",
+				"team_id": "T87654321",
+				"name": "spengler",
+				"updated": 1624406532,
+				"profile": {
+					"real_name": "Egon Spengler",
+					"display_name": "Egon",
+					"real_name_normalized": "Egon Spengler",
+					"display_name_normalized": "Egon",
+					"team": "T87654321",
+					"avatar_hash": "g216425b1681",
+					"email": "spengler@ghostbusters.example.com",
+					"image_24": "https://placekitten.com/24/24",
+					"image_32": "https://placekitten.com/32/32",
+					"image_48": "https://placekitten.com/48/48",
+					"image_72": "https://placekitten.com/72/72",
+					"image_192": "https://placekitten.com/192/192",
+					"image_512": "https://placekitten.com/512/512"
+				}
+			},
+			"event_ts": "1626881400.000000"
+		}
+	}
+	`)
+
+	evt := &EventsAPICallbackEvent{}
+	err := json.Unmarshal(rawE, evt)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	parsedEvent, err := parseInnerEvent(evt)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	actual, ok := parsedEvent.InnerEvent.Data.(*SharedChannelInviteApprovedEvent)
+	if !ok {
+		t.Fail()
+	}
+
+	if actual.Invite.ID != "I01354X80CA" {
+		t.Fail()
+	}
+
+	if actual.Invite.InvitingTeam.ID != "T12345678" {
+		t.Fail()
+	}
+
+	if actual.Invite.InvitingUser.ID != "U12345678" {
+		t.Fail()
+	}
+
+	if actual.Invite.RecipientUserID != "U87654321" {
+		t.Fail()
+	}
+
+	if actual.Channel.ID != "C12345678" {
+		t.Fail()
+	}
+
+	if actual.ApprovingTeamID != "T87654321" {
+		t.Fail()
+	}
+
+	if actual.ApprovingUser.Name != "spengler" {
+		t.Fail()
+	}
+
+	if actual.ApprovingUser.Profile.RealName != "Egon Spengler" {
+		t.Fail()
+	}
+
+	if actual.TeamsInChannel[0].ID != "T12345678" {
+		t.Fail()
+	}
+
+	if parsedEvent.InnerEvent.Type != "shared_channel_invite_approved" {
+		t.Fail()
+	}
+
+}
+
+func TestSharedChannelDeclined(t *testing.T) {
+	rawE := []byte(`
+	{
+		"token": "whatever",
+		"team_id": "whatever",
+		"api_app_id": "whatever",
+		"event": {
+			"type": "shared_channel_invite_declined",
+			"invite": {
+				"id": "I01354X80CA",
+				"date_created": 1626876000,
+				"date_invalid": 1628085600,
+				"inviting_team": {
+					"id": "T12345678",
+					"name": "Corgis",
+					"icon": {},
+					"is_verified": false,
+					"domain": "corgis",
+					"date_created": 1480946400
+				},
+				"inviting_user": {
+					"id": "U12345678",
+					"team_id": "T12345678",
+					"name": "crus",
+					"updated": 1608081902,
+					"profile": {
+						"real_name": "Corgis Rus",
+						"display_name": "Corgis Rus",
+						"real_name_normalized": "Corgis Rus",
+						"display_name_normalized": "Corgis Rus",
+						"team": "T12345678",
+						"avatar_hash": "gcfh83a4c72k",
+						"email": "corgisrus@slack-corp.com",
+						"image_24": "https://placekitten.com/24/24",
+						"image_32": "https://placekitten.com/32/32",
+						"image_48": "https://placekitten.com/48/48",
+						"image_72": "https://placekitten.com/72/72",
+						"image_192": "https://placekitten.com/192/192",
+						"image_512": "https://placekitten.com/512/512"
+					}
+				},
+				"recipient_email": "golden@doodle.com"
+			},
+			"channel": {
+				"id": "C12345678",
+				"is_private": false,
+				"is_im": false,
+				"name": "test-slack-connect"
+			},
+			"declining_team_id": "T87654321",
+			"teams_in_channel": [
+				{
+					"id": "T12345678",
+					"name": "Corgis",
+					"icon": {},
+					"is_verified": false,
+					"domain": "corgis",
+					"date_created": 1626789600
+				}
+			],
+			"declining_user": {
+				"id": "U012A3CDE",
+				"team_id": "T87654321",
+				"name": "spengler",
+				"updated": 1624406532,
+					"profile": {
+					"real_name": "Egon Spengler",
+					"display_name": "Egon",
+					"real_name_normalized": "Egon Spengler",
+					"display_name_normalized": "Egon",
+					"team": "T87654321",
+					"avatar_hash": "g216425b1681",
+					"email": "spengler@ghostbusters.example.com",
+					"image_24": "https://placekitten.com/24/24",
+					"image_32": "https://placekitten.com/32/32",
+					"image_48": "https://placekitten.com/48/48",
+					"image_72": "https://placekitten.com/72/72",
+					"image_192": "https://placekitten.com/192/192",
+					"image_512": "https://placekitten.com/512/512"
+				}
+			},
+			"event_ts": "1626881400.000000"
+		}
+	}
+	`)
+
+	evt := &EventsAPICallbackEvent{}
+	err := json.Unmarshal(rawE, evt)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	parsedEvent, err := parseInnerEvent(evt)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	actual, ok := parsedEvent.InnerEvent.Data.(*SharedChannelInviteDeclinedEvent)
+	if !ok {
+		t.Fail()
+	}
+
+	if actual.Invite.ID != "I01354X80CA" {
+		t.Fail()
+	}
+
+	if actual.Invite.InvitingTeam.ID != "T12345678" {
+		t.Fail()
+	}
+
+	if actual.Invite.InvitingUser.ID != "U12345678" {
+		t.Fail()
+	}
+
+	if actual.Invite.RecipientEmail != "golden@doodle.com" {
+		t.Fail()
+	}
+
+	if actual.Channel.ID != "C12345678" {
+		t.Fail()
+	}
+
+	if actual.DecliningTeamID != "T87654321" {
+		t.Fail()
+	}
+
+	if actual.DecliningUser.Name != "spengler" {
+		t.Fail()
+	}
+
+	if actual.DecliningUser.Profile.RealName != "Egon Spengler" {
+		t.Fail()
+	}
+
+	if actual.TeamsInChannel[0].ID != "T12345678" {
+		t.Fail()
+	}
+
+	if actual.EventTs != "1626881400.000000" {
+		t.Fail()
+	}
+
+	if parsedEvent.InnerEvent.Type != "shared_channel_invite_declined" {
+		t.Fail()
+	}
+
+}
diff --git a/slacktest/data.go b/slacktest/data.go
index e33b901..31d9a08 100644
--- a/slacktest/data.go
+++ b/slacktest/data.go
@@ -35,11 +35,11 @@ var okWebResponse = slack.SlackResponse{
 	Ok: true,
 }
 
-var defaultOkJSON = fmt.Sprintf(`
+var defaultOkJSON = `
 	{
 		"ok": true
 	}
-	`)
+	`
 
 var defaultChannelsListJSON = fmt.Sprintf(`
 	{
@@ -250,3 +250,9 @@ var renameConversationJSON = fmt.Sprintf(templateConversationJSON, "newName",
 
 var inviteConversationJSON = fmt.Sprintf(templateConversationJSON, defaultConversationName,
 	nowAsJSONTime(), defaultBotID, defaultConversationName, "", "", 0, "", "", 0, 1)
+
+const inviteSharedResponseJSON = `{
+    "ok": true,
+    "invite_id": "I02UKAJ6RJA",
+    "is_legacy_shared_channel": false
+}`
diff --git a/slacktest/handlers.go b/slacktest/handlers.go
index 6ac37c2..b4a0227 100644
--- a/slacktest/handlers.go
+++ b/slacktest/handlers.go
@@ -108,6 +108,11 @@ func inviteConversationHandler(w http.ResponseWriter, r *http.Request) {
 	_, _ = w.Write([]byte(inviteConversationJSON))
 }
 
+// handle conversations.inviteShared
+func inviteSharedConversationHandler(w http.ResponseWriter, r *http.Request) {
+	_, _ = w.Write([]byte(inviteSharedResponseJSON))
+}
+
 // handle groups.list
 func listGroupsHandler(w http.ResponseWriter, _ *http.Request) {
 	_, _ = w.Write([]byte(defaultGroupsListJSON))
diff --git a/slacktest/handlers_test.go b/slacktest/handlers_test.go
index dec4c99..8ccb704 100644
--- a/slacktest/handlers_test.go
+++ b/slacktest/handlers_test.go
@@ -117,7 +117,7 @@ func TestBotInfoHandler(t *testing.T) {
 	go s.Start()
 
 	client := slack.New("ABCDEFG", slack.OptionAPIURL(s.GetAPIURL()))
-	bot, err := client.GetBotInfo(s.BotID)
+	bot, err := client.GetBotInfo(slack.GetBotInfoParameters{Bot: s.BotID})
 	assert.NoError(t, err)
 	assert.Equal(t, s.BotID, bot.ID)
 	assert.Equal(t, s.BotName, bot.Name)
diff --git a/slacktest/server.go b/slacktest/server.go
index 6470310..6d98494 100644
--- a/slacktest/server.go
+++ b/slacktest/server.go
@@ -55,6 +55,7 @@ func NewTestServer(custom ...Binder) *Server {
 	s.Handle("/conversations.setPurpose", setConversationPurposeHandler)
 	s.Handle("/conversations.rename", renameConversationHandler)
 	s.Handle("/conversations.invite", inviteConversationHandler)
+	s.Handle("/conversations.inviteShared", inviteSharedConversationHandler)
 	s.Handle("/users.info", usersInfoHandler)
 	s.Handle("/users.lookupByEmail", usersInfoHandler)
 	s.Handle("/bots.info", botsInfoHandler)
diff --git a/slacktest/server_test.go b/slacktest/server_test.go
index ac42444..45f6208 100644
--- a/slacktest/server_test.go
+++ b/slacktest/server_test.go
@@ -48,7 +48,7 @@ func TestBotDirectMessageBotHandler(t *testing.T) {
 	s := NewTestServer()
 	go s.Start()
 	s.SendDirectMessageToBot("some text")
-	expectedMsg := fmt.Sprintf("some text")
+	expectedMsg := "some text"
 	time.Sleep(2 * time.Second)
 	assert.True(t, s.SawOutgoingMessage(expectedMsg))
 	s.Stop()
diff --git a/slash_test.go b/slash_test.go
index e5c79e9..e068fc4 100644
--- a/slash_test.go
+++ b/slash_test.go
@@ -1,6 +1,7 @@
 package slack
 
 import (
+	"encoding/json"
 	"fmt"
 	"net/http"
 	"net/url"
@@ -99,3 +100,70 @@ func TestSlash_ServeHTTP(t *testing.T) {
 		resp.Body.Close()
 	}
 }
+
+func TestSlash_UnmarshalJSON(t *testing.T) {
+	tests := []struct {
+		body                    string
+		wantIsEnterpriseInstall bool
+		wantToken               string
+		wantUnmarshalError      string
+	}{
+		{
+			body:                    `{"token":"blahblah","is_enterprise_install":"false"}`,
+			wantIsEnterpriseInstall: false,
+			wantToken:               "blahblah",
+			wantUnmarshalError:      "",
+		},
+		{
+			body:                    `{"token":"blahblah","is_enterprise_install":false}`,
+			wantIsEnterpriseInstall: false,
+			wantToken:               "blahblah",
+			wantUnmarshalError:      "",
+		},
+		{
+			body:                    `{"token":"blahblah","is_enterprise_install":"true"}`,
+			wantIsEnterpriseInstall: true,
+			wantToken:               "blahblah",
+			wantUnmarshalError:      "",
+		},
+		{
+			body:                    `{"token":"blahblah","is_enterprise_install":true}`,
+			wantIsEnterpriseInstall: true,
+			wantToken:               "blahblah",
+			wantUnmarshalError:      "",
+		},
+		{
+			body:               `{"token":"blahblah","is_enterprise_install":42}`,
+			wantUnmarshalError: "wrong data type for is_enterprise_install: float64",
+		},
+		{
+			body:               `{"token":"blahblah","is_enterprise_install":"unconvertable to bool"}`,
+			wantUnmarshalError: "parsing boolean for is_enterprise_install: strconv.ParseBool: parsing \"unconvertable to bool\": invalid syntax",
+		},
+	}
+
+	for i, test := range tests {
+		var result SlashCommand
+
+		err := json.Unmarshal([]byte(test.body), &result)
+		if err != nil {
+			if err.Error() != test.wantUnmarshalError {
+				t.Errorf("%d: Got error %v, want error %q", i, err, test.wantUnmarshalError)
+			}
+			continue
+		}
+
+		if test.wantUnmarshalError != "" {
+			t.Errorf("%d: Got no error, want error %q", i, test.wantUnmarshalError)
+			continue
+		}
+
+		if result.IsEnterpriseInstall != test.wantIsEnterpriseInstall {
+			t.Errorf("%d: Got IsEnterpriseInstall %v, want IsEnterpriseInstall %v", i, result.IsEnterpriseInstall, test.wantIsEnterpriseInstall)
+		}
+
+		if result.Token != test.wantToken {
+			t.Errorf("%d: Got Token %v, want Token %v", i, result.Token, test.wantToken)
+		}
+	}
+}
diff --git a/tokens_test.go b/tokens_test.go
new file mode 100644
index 0000000..6211745
--- /dev/null
+++ b/tokens_test.go
@@ -0,0 +1,45 @@
+package slack
+
+import (
+	"encoding/json"
+	"net/http"
+	"reflect"
+	"testing"
+)
+
+func TestRotateTokens(t *testing.T) {
+	http.HandleFunc("/tooling.tokens.rotate", handleRotateToken)
+	expected := getTestTokenResponse()
+
+	once.Do(startServer)
+	api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
+
+	tok, err := api.RotateTokens("expired-config", "old-refresh")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+		return
+	}
+
+	if !reflect.DeepEqual(expected, *tok) {
+		t.Fatal(ErrIncorrectResponse)
+	}
+}
+
+func getTestTokenResponse() TokenResponse {
+	return TokenResponse{
+		Token:         "token",
+		RefreshToken:  "refresh",
+		UserId:        "uid",
+		TeamId:        "tid",
+		IssuedAt:      1,
+		ExpiresAt:     1,
+		SlackResponse: SlackResponse{Ok: true},
+	}
+}
+
+func handleRotateToken(rw http.ResponseWriter, r *http.Request) {
+	rw.Header().Set("Content-Type", "application/json")
+
+	response, _ := json.Marshal(getTestTokenResponse())
+	rw.Write(response)
+}