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