slack_web_api/lib.rs
1//! # slack-web-api
2//!
3//! Typed async Rust client for **every Slack Web API method** — all 330 of them, including `admin.*` —
4//! built on reqwest 0.13 and rustls (no OpenSSL).
5//!
6//! - One function per Web API method: `chat.postMessage` is [`SlackClient::chat_post_message`],
7//! `conversations.history` is [`SlackClient::conversations_history`], and so on. The full list is in [`api`].
8//! - Request and response types for every method, core objects in [`objects`], and typed Block Kit in [`blocks`].
9//! - One shared connection pool (HTTP/2), a 30 second default timeout, optional retry on HTTP 429,
10//! cursor pagination and file upload.
11//!
12//! This crate only calls the Web API. It does not receive events (Socket Mode, Events API, interactivity).
13//!
14//! ## Send a message
15//!
16//! ```no_run
17//! use slack_web_api::api::ChatPostMessageRequest;
18//! use slack_web_api::SlackClient;
19//!
20//! # async fn run() -> Result<(), slack_web_api::SlackError> {
21//! let client = SlackClient::new("xoxb-your-bot-token");
22//! let res = client
23//! .chat_post_message(&ChatPostMessageRequest::new("C0123456789").text("Hello from Rust"))
24//! .await?;
25//! println!("posted at {:?}", res.ts);
26//! # Ok(())
27//! # }
28//! ```
29//!
30//! Required arguments are the parameters of `new`; every optional argument is a method of the same name.
31//!
32//! ## Send a Block Kit message
33//!
34//! ```no_run
35//! use slack_web_api::api::ChatPostMessageRequest;
36//! use slack_web_api::blocks::{ActionsBlock, Block, ButtonElement, HeaderBlock, SectionBlock, TextObject};
37//! # async fn run(client: slack_web_api::SlackClient) -> Result<(), slack_web_api::SlackError> {
38//! let blocks = vec![
39//! Block::from(HeaderBlock::new(TextObject::plain("Deploy finished"))),
40//! Block::from(SectionBlock::new().text(TextObject::mrkdwn("*api-server* is now on `v2.3.0`"))),
41//! Block::from(ActionsBlock::new(vec![
42//! ButtonElement::new(TextObject::plain("Open")).url("https://example.com").into(),
43//! ])),
44//! ];
45//! client
46//! .chat_post_message(&ChatPostMessageRequest::new("C0123456789").text("Deploy finished").blocks(blocks))
47//! .await?;
48//! # Ok(())
49//! # }
50//! ```
51//!
52//! ## Read channel history with pagination
53//!
54//! ```no_run
55//! use slack_web_api::api::ConversationsHistoryRequest;
56//! # async fn run(client: slack_web_api::SlackClient) -> Result<(), slack_web_api::SlackError> {
57//! let mut pages = client.pages(ConversationsHistoryRequest::new("C0123456789").limit(200));
58//! while let Some(page) = pages.next_page().await {
59//! for message in page?.messages {
60//! println!("{:?}: {:?}", message.user, message.text);
61//! }
62//! }
63//! # Ok(())
64//! # }
65//! ```
66//!
67//! ## Upload a file
68//!
69//! `files.upload` was retired on 2025-11-12. [`SlackClient::upload_files`] runs its replacement
70//! (`files.getUploadURLExternal`, the upload, then `files.completeUploadExternal`).
71//!
72//! ```no_run
73//! use slack_web_api::{FileUpload, UploadDestination};
74//! # async fn run(client: slack_web_api::SlackClient) -> Result<(), slack_web_api::SlackError> {
75//! client
76//! .upload_files(
77//! vec![FileUpload::new("report.csv", "date,count\n2026-10-01,42\n")],
78//! UploadDestination::channel("C0123456789").initial_comment("Daily report"),
79//! )
80//! .await?;
81//! # Ok(())
82//! # }
83//! ```
84//!
85//! ## Handle errors
86//!
87//! ```no_run
88//! use slack_web_api::api::ConversationsInfoRequest;
89//! use slack_web_api::SlackError;
90//! # async fn run(client: slack_web_api::SlackClient) {
91//! match client.conversations_info(&ConversationsInfoRequest::new("C0123456789")).await {
92//! Ok(res) => println!("{:?}", res.channel.and_then(|c| c.name)),
93//! Err(err) if err.api_error() == Some("channel_not_found") => println!("no such channel"),
94//! Err(SlackError::RateLimited { retry_after }) => println!("still rate limited: {retry_after:?}"),
95//! Err(err) => eprintln!("{err}"),
96//! }
97//! # }
98//! ```
99//!
100//! ## Share the client
101//!
102//! [`SlackClient`] is cheap to clone and every clone shares one connection pool, so create it once.
103//! To reuse your application's `reqwest::Client`, or to change the retry count or base URL, use the builder:
104//!
105//! ```no_run
106//! use slack_web_api::SlackClient;
107//!
108//! let http = reqwest::Client::new();
109//! let client = SlackClient::builder().http_client(http).token("xoxb-...").max_retries(5).build();
110//! // Apps installed in many workspaces can switch the token and keep the pool.
111//! let other_workspace = client.with_token("xoxb-other-workspace");
112//! ```
113//!
114//! ## Methods without types
115//!
116//! [`SlackClient::call_raw`] calls any method by name with any parameters and returns the JSON as is.
117//!
118//! ## How requests and responses are handled
119//!
120//! - Requests are sent as `application/x-www-form-urlencoded`, which every method accepts. Lists of strings are
121//! sent comma-separated and objects (blocks, attachments) as JSON.
122//! - The built-in HTTP client has a 30 second request timeout and a 10 second connect timeout. A client
123//! passed with [`SlackClientBuilder::http_client`] keeps its own settings.
124//! - HTTP 429 is returned at once as [`SlackError::RateLimited`] with its `Retry-After`, because only the caller
125//! knows how long a request may wait. With [`SlackClientBuilder::max_retries`] the client waits for `Retry-After`
126//! and resends. Other failures are never retried, so a message is never posted twice.
127//! - Response fields are all optional. Scalars are read leniently, because Slack sometimes returns the same field
128//! as a string in one place and a number in another; unknown fields are skipped.
129
130pub mod api;
131pub mod blocks;
132mod client;
133pub mod de;
134mod error;
135mod form;
136pub mod objects;
137mod response;
138mod upload;
139
140pub use client::{
141 CursorPaginated, NextCursor, Pages, SlackApiMethod, SlackClient, SlackClientBuilder,
142};
143pub use error::{SlackApiError, SlackError};
144pub use form::as_json;
145pub use response::ResponseMetadata;
146pub use upload::{FileUpload, UploadDestination};