UNPKG

16.9 kBMarkdownView Raw
1# Slack Web API
2
3The `@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
9This package supports Node v12.13 and higher. It's highly recommended to use [the latest LTS version of
10node](https://github.com/nodejs/Release#release-schedule), and the documentation is written using syntax and features
11from that version.
12
13This package also has experimental support for Deno v1.15.2 and higher, though not all features are supported at this
14time.
15
16## Installation
17
18### Node.js
19
20```shell
21$ npm install @slack/web-api
22```
23
24### Deno
25
26```typescript
27import { WebClient } from 'https://deno.land/x/slack_web_api/mod.js';
28```
29
30<!-- START: Remove before copying into the docs directory -->
31
32## Usage
33
34These examples show the most common features of the `WebClient`. You'll find even more extensive [documentation on the
35package's website](https://slack.dev/node-slack-sdk/web-api).
36
37<!-- END: Remove before copying into the docs directory -->
38
39---
40
41### Initialize the client
42
43The package exports a `WebClient` class. All you need to do is instantiate it, and you're ready to go. You'll typically
44initialize it with a `token`, so that you don't have to provide the token each time you call a method. A token usually
45begins with `xoxb` or `xoxp`. You get them from each workspace an app is installed onto. The app configuration pages
46help you get your first token for your development workspace.
47
48```javascript
49const { WebClient } = require('@slack/web-api');
50
51// Read a token from the environment variables
52const token = process.env.SLACK_TOKEN;
53
54// Initialize
55const web = new WebClient(token);
56```
57
58<details>
59<summary markdown="span">
60<strong><i>Initializing without a token</i></strong>
61</summary>
62
63Alternatively, 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
67const { WebClient } = require('@slack/web-api');
68
69// Initialize a single instance for the whole app
70const 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
87The client instance has a named method for each of the public methods in the Web API. The most popular one is
88called `chat.postMessage`, and it's used to send a message to a conversation. For every method, you pass arguments as
89properties of an options object. This helps with the readability of your code since every argument has a name. All
90named 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)
94const 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,
111you'll get hints for all the arguments each method supports. This helps you save time by reducing the number of
112times you need to pop out to a webpage to check the reference. There's more information about [using
113TypeScript](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
116where 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
123If you want to provide the method name as a string so that you can decide which method to call dynamically or to call
124a method that might not be available in your version of the client, use the `WebClient.apiCall(methodName, [options])`
125method. The API method call above can also be written as follows:
126
127```javascript
128const 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
144Errors can happen for many reasons: maybe the token doesn't have the proper [scopes](https://api.slack.com/scopes) to
145call 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
147app can proceed.
148
149Each error contains a `code` property, which you can check against the `ErrorCode` export to understand the kind of
150error 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
155const { 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
179There 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
200lists of objects, and are known to be **cursor-paginated**. The result of calling these methods will contain a part of
201the list, or a page, and also provide you with information on how to continue to the next page on a subsequent API call.
202Instead of calling many times manually, the `WebClient` can manage to get each page, allowing you to determine when to
203stop, and help you process the results.
204
205The process of retrieving multiple pages from Slack's API can be described as **asynchronous iteration**, which means
206you're processing items in a collection, but getting each item is an asynchronous operation. Fortunately, JavaScript
207has 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
226The `for await...of` syntax is available in Node v10.0.0 and above. If you're using an older version of Node, see
227functional iteration below.
228
229<details>
230<summary markdown="span">
231<strong><i>Using functional iteration</i></strong>
232</summary>
233
234The `.paginate()` method can accept up to two additional parameters. The third parameter, `stopFn`, is a function that
235is called once for each page of the result, and should return `true` when the app no longer needs to get another page.
236The fourth parameter is `reducerFn`, which is a function that gets called once for each page of the result, but can
237be used to aggregate a result. The value it returns is used to call it the next time as the `accumulator`. The first
238time 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
255The returned value is a `Promise`, but what it resolves to depends on whether or not you include the fourth (optional)
256parameter. If you don't include it, the resolved value is always `undefined`. In this case, its used for control flow
257purposes (resuming the rest of your program), and the function in the third parameter is used to capture a result. If
258you do include the fourth parameter, then the resolved value is the value of the `accumulator`. This is a familiar
259pattern for people that use _functional programming_.
260
261</details>
262
263---
264
265### Logging
266
267The `WebClient` will log interesting information to the console by default. You can use the `logLevel` to decide how
268much information, or how interesting the information needs to be, in order for it to be output. There are a few possible
269log levels, which you can find in the `LogLevel` export. By default, the value is set to `LogLevel.INFO`. While you're
270in development, its sometimes helpful to set this to the most verbose: `LogLevel.DEBUG`.
271
272```javascript
273// Import LogLevel from the package
274const { WebClient, LogLevel } = require('@slack/web-api');
275
276// Log level is one of the options you can set in the constructor
277const web = new WebClient(token, {
278 logLevel: LogLevel.DEBUG,
279});
280```
281
282All 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
289You can also choose to have logs sent to a custom logger using the `logger` option. A custom logger needs to implement
290specific 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
301A very simple custom logger might ignore the name and level, and write all messages to a file.
302
303```javascript
304const { createWriteStream } = require('fs');
305const logWritable = createWriteStream('/var/my_log_file'); // Not shown: close this stream
306
307const 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
325In production systems, you want your app to be resilient to short hiccups and temporary outages. Solving for this
326problem usually involves building a queuing system that handles retrying failed tasks. The `WebClient` comes with this
327queuing system out of the box, and it's on by default! The client will retry a failed API method call up to 10 times,
328spaced out over about 30 minutes. If the request doesn't succeed within that time, then the returned `Promise` will reject.
329You can observe each of the retries in your logs by [setting the log level to DEBUG](#logging). Try running the
330following code with your network disconnected, and then re-connect after you see a couple of log messages:
331
332```javascript
333const { WebClient, LogLevel } = require('@slack/web-api');
334
335const web = new WebClient('bogus token');
336
337(async () => {
338 await web.auth.test();
339
340 console.log('Done!');
341})();
342```
343
344Shortly after re-connecting your network, you should see the `Done!` message. Did you notice the program doesn't use a
345valid token? The client is doing something clever and helpful here. It knows the difference between an error such as not
346being able to reach `api.slack.com` and an error in the response from Slack about an invalid token. The former is
347something that can be resolved with a retry, so it was retried. The invalid token error means that the call isn't going
348to succeed until your app does something differently, so it stops attempting retries.
349
350You might not think 10 reties in 30 minutes is a good policy for your app. No problem, you can set the `retryConfig` to
351one that works better for you. The `retryPolicies` export contains a few well known options, and you can always write
352your own.
353
354```javascript
355const { WebClient, retryPolicies } = require('@slack/web-api');
356
357const web = new WebClient(token, {
358 retryConfig: retryPolicies.fiveRetriesInFiveMinutes,
359});
360```
361
362Here 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
372retrying 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
378The [documentation website](https://slack.dev/node-slack-sdk/web-api) has information about these additional features of
379the `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
393If 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**.