1 | # Slack Web API
|
2 |
|
3 | The `@slack/web-api` package contains a simple, convenient, and configurable HTTP client for making requests to Slack's
|
4 | [Web API](https://api.slack.com/web). Use it in your app to call any of the over 130
|
5 | [methods](https://api.slack.com/methods), and let it handle formatting, queuing, retrying, pagination, and more.
|
6 |
|
7 | ## Requirements
|
8 |
|
9 | This package supports Node v12.13 and higher. It's highly recommended to use [the latest LTS version of
|
10 | node](https://github.com/nodejs/Release#release-schedule), and the documentation is written using syntax and features
|
11 | from that version.
|
12 |
|
13 | This package also has experimental support for Deno v1.15.2 and higher, though not all features are supported at this
|
14 | time.
|
15 |
|
16 | ## Installation
|
17 |
|
18 | ### Node.js
|
19 |
|
20 | ```shell
|
21 | $ npm install @slack/web-api
|
22 | ```
|
23 |
|
24 | ### Deno
|
25 |
|
26 | ```typescript
|
27 | import { WebClient } from 'https://deno.land/x/slack_web_api/mod.js';
|
28 | ```
|
29 |
|
30 |
|
31 |
|
32 | ## Usage
|
33 |
|
34 | These examples show the most common features of the `WebClient`. You'll find even more extensive [documentation on the
|
35 | package's website](https://slack.dev/node-slack-sdk/web-api).
|
36 |
|
37 |
|
38 |
|
39 | ---
|
40 |
|
41 | ### Initialize the client
|
42 |
|
43 | The package exports a `WebClient` class. All you need to do is instantiate it, and you're ready to go. You'll typically
|
44 | initialize it with a `token`, so that you don't have to provide the token each time you call a method. A token usually
|
45 | begins with `xoxb` or `xoxp`. You get them from each workspace an app is installed onto. The app configuration pages
|
46 | help you get your first token for your development workspace.
|
47 |
|
48 | ```javascript
|
49 | const { WebClient } = require('@slack/web-api');
|
50 |
|
51 | // Read a token from the environment variables
|
52 | const token = process.env.SLACK_TOKEN;
|
53 |
|
54 | // Initialize
|
55 | const web = new WebClient(token);
|
56 | ```
|
57 |
|
58 | <details>
|
59 | <summary markdown="span">
|
60 | <strong><i>Initializing without a token</i></strong>
|
61 | </summary>
|
62 |
|
63 | Alternatively, you can create a client without a token, and use it with multiple workspaces as long as you supply a
|
64 | `token` when you call a method.
|
65 |
|
66 | ```javascript
|
67 | const { WebClient } = require('@slack/web-api');
|
68 |
|
69 | // Initialize a single instance for the whole app
|
70 | const web = new WebClient();
|
71 |
|
72 | // Find a token in storage (database) before making an API method call
|
73 | (async () => {
|
74 | // Some fictitious database
|
75 | const token = await db.findTokenByTeam(teamId, enterpriseId)
|
76 |
|
77 | // Call the method
|
78 | const result = web.auth.test({ token });
|
79 | })();
|
80 | ```
|
81 | </details>
|
82 |
|
83 | ---
|
84 |
|
85 | ### Call a method
|
86 |
|
87 | The client instance has a named method for each of the public methods in the Web API. The most popular one is
|
88 | called `chat.postMessage`, and it's used to send a message to a conversation. For every method, you pass arguments as
|
89 | properties of an options object. This helps with the readability of your code since every argument has a name. All
|
90 | named methods return a `Promise` which resolves with the response data or rejects with an error.
|
91 |
|
92 | ```javascript
|
93 | // Given some known conversation ID (representing a public channel, private channel, DM or group DM)
|
94 | const conversationId = '...';
|
95 |
|
96 | (async () => {
|
97 |
|
98 | // Post a message to the channel, and await the result.
|
99 | // Find more arguments and details of the response: https://api.slack.com/methods/chat.postMessage
|
100 | const result = await web.chat.postMessage({
|
101 | text: 'Hello world!',
|
102 | channel: conversationId,
|
103 | });
|
104 |
|
105 | // The result contains an identifier for the message, `ts`.
|
106 | console.log(`Successfully send message ${result.ts} in conversation ${conversationId}`);
|
107 | })();
|
108 | ```
|
109 |
|
110 | **Tip**: If you're using an editor that supports TypeScript, even if you're not using TypeScript to write your code,
|
111 | you'll get hints for all the arguments each method supports. This helps you save time by reducing the number of
|
112 | times you need to pop out to a webpage to check the reference. There's more information about [using
|
113 | TypeScript](https://slack.dev/node-slack-sdk/typescript) with this package in the documentation website.
|
114 |
|
115 | **Tip**: Use the [Block Kit Builder](https://api.slack.com/tools/block-kit-builder) for a playground
|
116 | where you can prototype your message's look and feel.
|
117 |
|
118 | <details>
|
119 | <summary markdown="span">
|
120 | <strong><i>Using a dynamic method name</i></strong>
|
121 | </summary>
|
122 |
|
123 | If you want to provide the method name as a string so that you can decide which method to call dynamically or to call
|
124 | a method that might not be available in your version of the client, use the `WebClient.apiCall(methodName, [options])`
|
125 | method. The API method call above can also be written as follows:
|
126 |
|
127 | ```javascript
|
128 | const conversationId = '...';
|
129 | (async () => {
|
130 |
|
131 | // Using apiCall() allows the app to call any method and to do it programmatically
|
132 | const response = await web.apiCall('chat.postMessage', {
|
133 | text: 'Hello world!',
|
134 | channel: conversationId,
|
135 | });
|
136 | })();
|
137 | ```
|
138 | </details>
|
139 |
|
140 | ---
|
141 |
|
142 | ### Handle errors
|
143 |
|
144 | Errors can happen for many reasons: maybe the token doesn't have the proper [scopes](https://api.slack.com/scopes) to
|
145 | call a method, maybe its been revoked by a user, or maybe you just used a bad argument. In these cases, the returned
|
146 | `Promise` will reject with an `Error`. You should catch the error and use the information it contains to decide how your
|
147 | app can proceed.
|
148 |
|
149 | Each error contains a `code` property, which you can check against the `ErrorCode` export to understand the kind of
|
150 | error you're dealing with. For example, when Slack responds to your app with an error, that is an
|
151 | `ErrorCode.PlatformError`. These types of errors provide Slack's response body as the `data` property.
|
152 |
|
153 | ```javascript
|
154 | // Import ErrorCode from the package
|
155 | const { WebClient, ErrorCode } = require('@slack/web-api');
|
156 |
|
157 | (async () => {
|
158 |
|
159 | try {
|
160 | // This method call should fail because we're giving it a bogus user ID to lookup.
|
161 | const response = await web.users.info({ user: '...' });
|
162 | } catch (error) {
|
163 | // Check the code property, and when its a PlatformError, log the whole response.
|
164 | if (error.code === ErrorCode.PlatformError) {
|
165 | console.log(error.data);
|
166 | } else {
|
167 | // Some other error, oh no!
|
168 | console.log('Well, that was unexpected.');
|
169 | }
|
170 | }
|
171 | })();
|
172 | ```
|
173 |
|
174 | <details>
|
175 | <summary markdown="span">
|
176 | <strong><i>More error types</i></strong>
|
177 | </summary>
|
178 |
|
179 | There are a few more types of errors that you might encounter, each with one of these `code`s:
|
180 |
|
181 | * `ErrorCode.RequestError`: A request could not be sent. A common reason for this is that your network connection is
|
182 | not available, or `api.slack.com` could not be reached. This error has an `original` property with more details.
|
183 |
|
184 | * `ErrorCode.RateLimitedError`: The Web API cannot fulfill the API method call because your app has made too many
|
185 | requests too quickly. This error has a `retryAfter` property with the number of seconds you should wait before trying
|
186 | again. See [the documentation on rate limit handling](https://slack.dev/node-slack-sdk/web-api/#rate-limits) to
|
187 | understand how the client will automatically deal with these problems for you.
|
188 |
|
189 | * `ErrorCode.HTTPError`: The HTTP response contained an unfamiliar status code. The Web API only responds with `200`
|
190 | (yes, even for errors) or `429` (rate limiting). If you receive this error, it's likely due to a problem with a proxy,
|
191 | a custom TLS configuration, or a custom API URL. This error has the `statusCode`, `statusMessage`, `headers`, and
|
192 | `body` properties containing more details.
|
193 | </details>
|
194 |
|
195 | ---
|
196 |
|
197 | ### Pagination
|
198 |
|
199 | [Many of the Web API's methods](https://api.slack.com/docs/pagination#methods_supporting_cursor-based_pagination) return
|
200 | lists of objects, and are known to be **cursor-paginated**. The result of calling these methods will contain a part of
|
201 | the list, or a page, and also provide you with information on how to continue to the next page on a subsequent API call.
|
202 | Instead of calling many times manually, the `WebClient` can manage to get each page, allowing you to determine when to
|
203 | stop, and help you process the results.
|
204 |
|
205 | The process of retrieving multiple pages from Slack's API can be described as **asynchronous iteration**, which means
|
206 | you're processing items in a collection, but getting each item is an asynchronous operation. Fortunately, JavaScript
|
207 | has this concept built-in, and in newer versions of the language there's a syntax to make it even simpler:
|
208 | [`for await...of`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of).
|
209 |
|
210 | ```javascript
|
211 | (async () => {
|
212 | let result;
|
213 |
|
214 | // Async iteration is similar to a simple for loop.
|
215 | // Use only the first two parameters to get an async iterator.
|
216 | for await (const page of web.paginate('something.list', { name: 'value' })) {
|
217 | // You can inspect each page, find your result, and stop the loop with a `break` statement
|
218 | if (containsTheThing(page.something)) {
|
219 | result = page.something.thing;
|
220 | break;
|
221 | }
|
222 | }
|
223 | });
|
224 | ```
|
225 |
|
226 | The `for await...of` syntax is available in Node v10.0.0 and above. If you're using an older version of Node, see
|
227 | functional iteration below.
|
228 |
|
229 | <details>
|
230 | <summary markdown="span">
|
231 | <strong><i>Using functional iteration</i></strong>
|
232 | </summary>
|
233 |
|
234 | The `.paginate()` method can accept up to two additional parameters. The third parameter, `stopFn`, is a function that
|
235 | is called once for each page of the result, and should return `true` when the app no longer needs to get another page.
|
236 | The fourth parameter is `reducerFn`, which is a function that gets called once for each page of the result, but can
|
237 | be used to aggregate a result. The value it returns is used to call it the next time as the `accumulator`. The first
|
238 | time it gets called, the `accumulator` is undefined.
|
239 |
|
240 | ```javascript
|
241 | (async () => {
|
242 |
|
243 | // The first two parameters are the method name and the options object.
|
244 | const done = await web.paginate('something.list', { name: 'value' },
|
245 | // The third is a function that receives each page and should return true when the next page isn't needed.
|
246 | (page) => { /* ... */ },
|
247 | // The fourth is a reducer function, similar to the callback parameter of Array.prototype.reduce().
|
248 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
|
249 | // The accumulator is initialized to undefined.
|
250 | (accumulator, page, index) => { /* ... */ },
|
251 | );
|
252 | });
|
253 | ```
|
254 |
|
255 | The returned value is a `Promise`, but what it resolves to depends on whether or not you include the fourth (optional)
|
256 | parameter. If you don't include it, the resolved value is always `undefined`. In this case, its used for control flow
|
257 | purposes (resuming the rest of your program), and the function in the third parameter is used to capture a result. If
|
258 | you do include the fourth parameter, then the resolved value is the value of the `accumulator`. This is a familiar
|
259 | pattern for people that use _functional programming_.
|
260 |
|
261 | </details>
|
262 |
|
263 | ---
|
264 |
|
265 | ### Logging
|
266 |
|
267 | The `WebClient` will log interesting information to the console by default. You can use the `logLevel` to decide how
|
268 | much information, or how interesting the information needs to be, in order for it to be output. There are a few possible
|
269 | log levels, which you can find in the `LogLevel` export. By default, the value is set to `LogLevel.INFO`. While you're
|
270 | in development, its sometimes helpful to set this to the most verbose: `LogLevel.DEBUG`.
|
271 |
|
272 | ```javascript
|
273 | // Import LogLevel from the package
|
274 | const { WebClient, LogLevel } = require('@slack/web-api');
|
275 |
|
276 | // Log level is one of the options you can set in the constructor
|
277 | const web = new WebClient(token, {
|
278 | logLevel: LogLevel.DEBUG,
|
279 | });
|
280 | ```
|
281 |
|
282 | All the log levels, in order of most to least information, are: `DEBUG`, `INFO`, `WARN`, and `ERROR`.
|
283 |
|
284 | <details>
|
285 | <summary markdown="span">
|
286 | <strong><i>Sending log output somewhere besides the console</i></strong>
|
287 | </summary>
|
288 |
|
289 | You can also choose to have logs sent to a custom logger using the `logger` option. A custom logger needs to implement
|
290 | specific methods (known as the `Logger` interface):
|
291 |
|
292 | | Method | Parameters | Return type |
|
293 | |--------------|-------------------|-------------|
|
294 | | `setLevel()` | `level: LogLevel` | `void` |
|
295 | | `setName()` | `name: string` | `void` |
|
296 | | `debug()` | `...msgs: any[]` | `void` |
|
297 | | `info()` | `...msgs: any[]` | `void` |
|
298 | | `warn()` | `...msgs: any[]` | `void` |
|
299 | | `error()` | `...msgs: any[]` | `void` |
|
300 |
|
301 | A very simple custom logger might ignore the name and level, and write all messages to a file.
|
302 |
|
303 | ```javascript
|
304 | const { createWriteStream } = require('fs');
|
305 | const logWritable = createWriteStream('/var/my_log_file'); // Not shown: close this stream
|
306 |
|
307 | const web = new WebClient(token, {
|
308 | // Creating a logger as a literal object. It's more likely that you'd create a class.
|
309 | logger: {
|
310 | debug(...msgs): { logWritable.write('debug: ' + JSON.stringify(msgs)); },
|
311 | info(...msgs): { logWritable.write('info: ' + JSON.stringify(msgs)); },
|
312 | warn(...msgs): { logWritable.write('warn: ' + JSON.stringify(msgs)); },
|
313 | error(...msgs): { logWritable.write('error: ' + JSON.stringify(msgs)); },
|
314 | setLevel(): { },
|
315 | setName(): { },
|
316 | },
|
317 | });
|
318 | ```
|
319 | </details>
|
320 |
|
321 | ---
|
322 |
|
323 | ### Automatic retries
|
324 |
|
325 | In production systems, you want your app to be resilient to short hiccups and temporary outages. Solving for this
|
326 | problem usually involves building a queuing system that handles retrying failed tasks. The `WebClient` comes with this
|
327 | queuing system out of the box, and it's on by default! The client will retry a failed API method call up to 10 times,
|
328 | spaced out over about 30 minutes. If the request doesn't succeed within that time, then the returned `Promise` will reject.
|
329 | You can observe each of the retries in your logs by [setting the log level to DEBUG](#logging). Try running the
|
330 | following code with your network disconnected, and then re-connect after you see a couple of log messages:
|
331 |
|
332 | ```javascript
|
333 | const { WebClient, LogLevel } = require('@slack/web-api');
|
334 |
|
335 | const web = new WebClient('bogus token');
|
336 |
|
337 | (async () => {
|
338 | await web.auth.test();
|
339 |
|
340 | console.log('Done!');
|
341 | })();
|
342 | ```
|
343 |
|
344 | Shortly after re-connecting your network, you should see the `Done!` message. Did you notice the program doesn't use a
|
345 | valid token? The client is doing something clever and helpful here. It knows the difference between an error such as not
|
346 | being able to reach `api.slack.com` and an error in the response from Slack about an invalid token. The former is
|
347 | something that can be resolved with a retry, so it was retried. The invalid token error means that the call isn't going
|
348 | to succeed until your app does something differently, so it stops attempting retries.
|
349 |
|
350 | You might not think 10 reties in 30 minutes is a good policy for your app. No problem, you can set the `retryConfig` to
|
351 | one that works better for you. The `retryPolicies` export contains a few well known options, and you can always write
|
352 | your own.
|
353 |
|
354 | ```javascript
|
355 | const { WebClient, retryPolicies } = require('@slack/web-api');
|
356 |
|
357 | const web = new WebClient(token, {
|
358 | retryConfig: retryPolicies.fiveRetriesInFiveMinutes,
|
359 | });
|
360 | ```
|
361 |
|
362 | Here are some other values that you might want to use for `retryConfig`:
|
363 |
|
364 | | `retryConfig` | Description |
|
365 | |------------------------------------------------|---------------------------------|
|
366 | | `retryPolicies.tenRetriesInAboutThirtyMinutes` | (default) |
|
367 | | `retryPolicies.fiveRetriesInFiveMinutes` | Five attempts in five minutes |
|
368 | | `retryPolicies.rapidRetryPolicy` | Used to keep tests running fast |
|
369 | | `{ retries: 0 }` | No retries ([other options](https://github.com/tim-kos/node-retry#retryoperationoptions)) |
|
370 |
|
371 | **Note**: If an API call results in a rate limit being exceeded, you might still notice the client automatically
|
372 | retrying the API call. If you'd like to opt out of that behavior, set the `rejectRateLimitedCalls` option to `true`.
|
373 |
|
374 | ---
|
375 |
|
376 | ### More
|
377 |
|
378 | The [documentation website](https://slack.dev/node-slack-sdk/web-api) has information about these additional features of
|
379 | the `WebClient`:
|
380 |
|
381 | * Upload a file with a `Buffer` or a `ReadableStream`.
|
382 | * Using a custom agent for proxying
|
383 | * Rate limit handling
|
384 | * Request concurrency
|
385 | * Custom TLS configuration
|
386 | * Custom API URL
|
387 | * Exchange an OAuth grant for a token
|
388 |
|
389 | ---
|
390 |
|
391 | ## Getting Help
|
392 |
|
393 | If you get stuck, we're here to help. The following are the best ways to get assistance working through your issue:
|
394 |
|
395 | * [Issue Tracker](http://github.com/slackapi/node-slack-sdk/issues) for questions, feature requests, bug reports and
|
396 | general discussion related to these packages. Try searching before you create a new issue.
|
397 | * [Email us](mailto:developers@slack.com) in Slack developer support: `developers@slack.com`
|
398 | * [Bot Developers Hangout](https://community.botkit.ai/): a Slack community for developers
|
399 | building all types of bots. You can find the maintainers and users of these packages in **#sdk-node-slack-sdk**.
|