chub-dev 0.1.0 → 0.1.2-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/README.md +55 -0
  2. package/bin/chub-mcp +2 -0
  3. package/dist/airtable/docs/database/javascript/DOC.md +1437 -0
  4. package/dist/airtable/docs/database/python/DOC.md +1735 -0
  5. package/dist/amplitude/docs/analytics/javascript/DOC.md +1282 -0
  6. package/dist/amplitude/docs/analytics/python/DOC.md +1199 -0
  7. package/dist/anthropic/docs/claude-api/javascript/DOC.md +503 -0
  8. package/dist/anthropic/docs/claude-api/python/DOC.md +389 -0
  9. package/dist/asana/docs/tasks/DOC.md +1396 -0
  10. package/dist/assemblyai/docs/transcription/DOC.md +1043 -0
  11. package/dist/atlassian/docs/confluence/javascript/DOC.md +1347 -0
  12. package/dist/atlassian/docs/confluence/python/DOC.md +1604 -0
  13. package/dist/auth0/docs/identity/javascript/DOC.md +968 -0
  14. package/dist/auth0/docs/identity/python/DOC.md +1199 -0
  15. package/dist/aws/docs/s3/javascript/DOC.md +1773 -0
  16. package/dist/aws/docs/s3/python/DOC.md +1807 -0
  17. package/dist/binance/docs/trading/javascript/DOC.md +1315 -0
  18. package/dist/binance/docs/trading/python/DOC.md +1454 -0
  19. package/dist/braintree/docs/gateway/javascript/DOC.md +1278 -0
  20. package/dist/braintree/docs/gateway/python/DOC.md +1179 -0
  21. package/dist/chromadb/docs/embeddings-db/javascript/DOC.md +1263 -0
  22. package/dist/chromadb/docs/embeddings-db/python/DOC.md +1707 -0
  23. package/dist/clerk/docs/auth/javascript/DOC.md +1220 -0
  24. package/dist/clerk/docs/auth/python/DOC.md +274 -0
  25. package/dist/cloudflare/docs/workers/javascript/DOC.md +918 -0
  26. package/dist/cloudflare/docs/workers/python/DOC.md +994 -0
  27. package/dist/cockroachdb/docs/distributed-db/DOC.md +1500 -0
  28. package/dist/cohere/docs/llm/DOC.md +1335 -0
  29. package/dist/datadog/docs/monitoring/javascript/DOC.md +1740 -0
  30. package/dist/datadog/docs/monitoring/python/DOC.md +1815 -0
  31. package/dist/deepgram/docs/speech/javascript/DOC.md +885 -0
  32. package/dist/deepgram/docs/speech/python/DOC.md +685 -0
  33. package/dist/deepl/docs/translation/javascript/DOC.md +887 -0
  34. package/dist/deepl/docs/translation/python/DOC.md +944 -0
  35. package/dist/deepseek/docs/llm/DOC.md +1220 -0
  36. package/dist/directus/docs/headless-cms/javascript/DOC.md +1128 -0
  37. package/dist/directus/docs/headless-cms/python/DOC.md +1276 -0
  38. package/dist/discord/docs/bot/javascript/DOC.md +1090 -0
  39. package/dist/discord/docs/bot/python/DOC.md +1130 -0
  40. package/dist/elasticsearch/docs/search/DOC.md +1634 -0
  41. package/dist/elevenlabs/docs/text-to-speech/javascript/DOC.md +336 -0
  42. package/dist/elevenlabs/docs/text-to-speech/python/DOC.md +552 -0
  43. package/dist/firebase/docs/auth/DOC.md +1015 -0
  44. package/dist/gemini/docs/genai/javascript/DOC.md +691 -0
  45. package/dist/gemini/docs/genai/python/DOC.md +555 -0
  46. package/dist/github/docs/octokit/DOC.md +1560 -0
  47. package/dist/google/docs/bigquery/javascript/DOC.md +1688 -0
  48. package/dist/google/docs/bigquery/python/DOC.md +1503 -0
  49. package/dist/hubspot/docs/crm/javascript/DOC.md +1805 -0
  50. package/dist/hubspot/docs/crm/python/DOC.md +2033 -0
  51. package/dist/huggingface/docs/transformers/DOC.md +948 -0
  52. package/dist/intercom/docs/messaging/javascript/DOC.md +1844 -0
  53. package/dist/intercom/docs/messaging/python/DOC.md +1797 -0
  54. package/dist/jira/docs/issues/javascript/DOC.md +1420 -0
  55. package/dist/jira/docs/issues/python/DOC.md +1492 -0
  56. package/dist/kafka/docs/streaming/javascript/DOC.md +1671 -0
  57. package/dist/kafka/docs/streaming/python/DOC.md +1464 -0
  58. package/dist/landingai-ade/docs/api/DOC.md +620 -0
  59. package/dist/landingai-ade/docs/sdk/python/DOC.md +489 -0
  60. package/dist/landingai-ade/docs/sdk/typescript/DOC.md +542 -0
  61. package/dist/landingai-ade/skills/SKILL.md +489 -0
  62. package/dist/launchdarkly/docs/feature-flags/javascript/DOC.md +1191 -0
  63. package/dist/launchdarkly/docs/feature-flags/python/DOC.md +1671 -0
  64. package/dist/linear/docs/tracker/DOC.md +1554 -0
  65. package/dist/livekit/docs/realtime/javascript/DOC.md +303 -0
  66. package/dist/livekit/docs/realtime/python/DOC.md +163 -0
  67. package/dist/mailchimp/docs/marketing/DOC.md +1420 -0
  68. package/dist/meilisearch/docs/search/DOC.md +1241 -0
  69. package/dist/microsoft/docs/onedrive/javascript/DOC.md +1421 -0
  70. package/dist/microsoft/docs/onedrive/python/DOC.md +1549 -0
  71. package/dist/mongodb/docs/atlas/DOC.md +2041 -0
  72. package/dist/notion/docs/workspace-api/javascript/DOC.md +1435 -0
  73. package/dist/notion/docs/workspace-api/python/DOC.md +1400 -0
  74. package/dist/okta/docs/identity/javascript/DOC.md +1171 -0
  75. package/dist/okta/docs/identity/python/DOC.md +1401 -0
  76. package/dist/openai/docs/chat/javascript/DOC.md +407 -0
  77. package/dist/openai/docs/chat/python/DOC.md +568 -0
  78. package/dist/paypal/docs/checkout/DOC.md +278 -0
  79. package/dist/pinecone/docs/sdk/javascript/DOC.md +984 -0
  80. package/dist/pinecone/docs/sdk/python/DOC.md +1395 -0
  81. package/dist/plaid/docs/banking/javascript/DOC.md +1163 -0
  82. package/dist/plaid/docs/banking/python/DOC.md +1203 -0
  83. package/dist/playwright-community/skills/login-flows/SKILL.md +108 -0
  84. package/dist/postmark/docs/transactional-email/DOC.md +1168 -0
  85. package/dist/prisma/docs/orm/javascript/DOC.md +1419 -0
  86. package/dist/prisma/docs/orm/python/DOC.md +1317 -0
  87. package/dist/qdrant/docs/vector-search/javascript/DOC.md +1221 -0
  88. package/dist/qdrant/docs/vector-search/python/DOC.md +1653 -0
  89. package/dist/rabbitmq/docs/message-queue/javascript/DOC.md +1193 -0
  90. package/dist/rabbitmq/docs/message-queue/python/DOC.md +1243 -0
  91. package/dist/razorpay/docs/payments/javascript/DOC.md +1219 -0
  92. package/dist/razorpay/docs/payments/python/DOC.md +1330 -0
  93. package/dist/redis/docs/key-value/javascript/DOC.md +1851 -0
  94. package/dist/redis/docs/key-value/python/DOC.md +2054 -0
  95. package/dist/registry.json +2817 -0
  96. package/dist/replicate/docs/model-hosting/DOC.md +1318 -0
  97. package/dist/resend/docs/email/DOC.md +1271 -0
  98. package/dist/salesforce/docs/crm/javascript/DOC.md +1241 -0
  99. package/dist/salesforce/docs/crm/python/DOC.md +1183 -0
  100. package/dist/search-index.json +1 -0
  101. package/dist/sendgrid/docs/email-api/javascript/DOC.md +371 -0
  102. package/dist/sendgrid/docs/email-api/python/DOC.md +656 -0
  103. package/dist/sentry/docs/error-tracking/javascript/DOC.md +1073 -0
  104. package/dist/sentry/docs/error-tracking/python/DOC.md +1309 -0
  105. package/dist/shopify/docs/storefront/DOC.md +457 -0
  106. package/dist/slack/docs/workspace/javascript/DOC.md +933 -0
  107. package/dist/slack/docs/workspace/python/DOC.md +271 -0
  108. package/dist/square/docs/payments/javascript/DOC.md +1855 -0
  109. package/dist/square/docs/payments/python/DOC.md +1728 -0
  110. package/dist/stripe/docs/api/DOC.md +1727 -0
  111. package/dist/stripe/docs/payments/DOC.md +1726 -0
  112. package/dist/stytch/docs/auth/javascript/DOC.md +1813 -0
  113. package/dist/stytch/docs/auth/python/DOC.md +1962 -0
  114. package/dist/supabase/docs/client/DOC.md +1606 -0
  115. package/dist/twilio/docs/messaging/python/DOC.md +469 -0
  116. package/dist/twilio/docs/messaging/typescript/DOC.md +946 -0
  117. package/dist/vercel/docs/platform/DOC.md +1940 -0
  118. package/dist/weaviate/docs/vector-db/javascript/DOC.md +1268 -0
  119. package/dist/weaviate/docs/vector-db/python/DOC.md +1388 -0
  120. package/dist/zendesk/docs/support/javascript/DOC.md +2150 -0
  121. package/dist/zendesk/docs/support/python/DOC.md +2297 -0
  122. package/package.json +22 -6
  123. package/skills/get-api-docs/SKILL.md +84 -0
  124. package/src/commands/annotate.js +83 -0
  125. package/src/commands/build.js +12 -1
  126. package/src/commands/feedback.js +150 -0
  127. package/src/commands/get.js +83 -42
  128. package/src/commands/search.js +7 -0
  129. package/src/index.js +43 -17
  130. package/src/lib/analytics.js +90 -0
  131. package/src/lib/annotations.js +57 -0
  132. package/src/lib/bm25.js +170 -0
  133. package/src/lib/cache.js +69 -6
  134. package/src/lib/config.js +8 -3
  135. package/src/lib/identity.js +99 -0
  136. package/src/lib/registry.js +103 -20
  137. package/src/lib/telemetry.js +86 -0
  138. package/src/mcp/server.js +177 -0
  139. package/src/mcp/tools.js +251 -0
@@ -0,0 +1,1396 @@
1
+ ---
2
+ name: tasks
3
+ description: "Asana API coding guide for tasks, project management, and workflow"
4
+ metadata:
5
+ languages: "javascript"
6
+ versions: "3.1.2"
7
+ updated-on: "2026-03-02"
8
+ source: maintainer
9
+ tags: "asana,tasks,project-management,workflow,api"
10
+ ---
11
+
12
+ # Asana API Coding Guide
13
+
14
+ ## 1. Golden Rule
15
+
16
+ **Always use the official Asana Node.js SDK package:**
17
+ - Package name: `asana`
18
+ - Official library maintained by Asana for Node.js and browser JavaScript
19
+
20
+ **Never use deprecated or unofficial libraries.** The `asana` package is the only supported library maintained by Asana, Inc.
21
+
22
+ **Current SDK Version:** v3.1.2 (Node.js library)
23
+
24
+ **API Version:** Asana API 1.0
25
+
26
+ ## 2. Installation
27
+
28
+ ### Node.js Installation
29
+
30
+ ```bash
31
+ npm install asana
32
+ ```
33
+
34
+ ```bash
35
+ yarn add asana
36
+ ```
37
+
38
+ ```bash
39
+ pnpm add asana
40
+ ```
41
+
42
+ **Requirements:** Node.js 12+ (recommended Node.js 18+ for production)
43
+
44
+ ### Environment Variables
45
+
46
+ ```bash
47
+ # Required - Personal Access Token
48
+ ASANA_ACCESS_TOKEN=your_personal_access_token_here
49
+
50
+ # Optional - OAuth credentials
51
+ ASANA_CLIENT_ID=your_client_id
52
+ ASANA_CLIENT_SECRET=your_client_secret
53
+ ASANA_REDIRECT_URI=http://localhost:3000/auth/callback
54
+
55
+ # Optional - Workspace/Organization IDs
56
+ ASANA_WORKSPACE_ID=your_workspace_gid
57
+ ASANA_PROJECT_ID=your_project_gid
58
+ ```
59
+
60
+ **CRITICAL:** Never commit access tokens to version control. Use environment variables or secure secret management systems.
61
+
62
+ ## 3. Initialization
63
+
64
+ ### Basic Initialization with Personal Access Token
65
+
66
+ ```javascript
67
+ const Asana = require('asana');
68
+
69
+ const client = Asana.ApiClient.instance;
70
+ const token = client.authentications['token'];
71
+ token.accessToken = process.env.ASANA_ACCESS_TOKEN;
72
+ ```
73
+
74
+ **With ES6 Modules:**
75
+
76
+ ```javascript
77
+ import Asana from 'asana';
78
+
79
+ const client = Asana.ApiClient.instance;
80
+ const token = client.authentications['token'];
81
+ token.accessToken = process.env.ASANA_ACCESS_TOKEN;
82
+ ```
83
+
84
+ ### Advanced Initialization with OAuth
85
+
86
+ ```javascript
87
+ const Asana = require('asana');
88
+
89
+ const client = Asana.ApiClient.instance;
90
+ const oauth = client.authentications['oauth2'];
91
+ oauth.accessToken = 'YOUR_OAUTH_ACCESS_TOKEN';
92
+ ```
93
+
94
+ ### Client Configuration Options
95
+
96
+ ```javascript
97
+ const Asana = require('asana');
98
+
99
+ const client = Asana.ApiClient.instance;
100
+ client.defaultHeaders = {
101
+ 'asana-enable': 'new_user_task_lists,new_project_templates'
102
+ };
103
+ client.timeout = 60000; // 60 seconds
104
+
105
+ const token = client.authentications['token'];
106
+ token.accessToken = process.env.ASANA_ACCESS_TOKEN;
107
+ ```
108
+
109
+ ## 4. Core API Surfaces
110
+
111
+ ### Tasks API
112
+
113
+ Tasks are the basic unit of action in Asana. They can be assigned, have due dates, contain notes, and be organized into projects.
114
+
115
+ #### Creating Tasks
116
+
117
+ **Minimal Example:**
118
+
119
+ ```javascript
120
+ const Asana = require('asana');
121
+
122
+ const client = Asana.ApiClient.instance;
123
+ const token = client.authentications['token'];
124
+ token.accessToken = process.env.ASANA_ACCESS_TOKEN;
125
+
126
+ const tasksApiInstance = new Asana.TasksApi();
127
+
128
+ const body = {
129
+ data: {
130
+ name: 'Buy milk',
131
+ workspace: '1234567890123456'
132
+ }
133
+ };
134
+
135
+ const opts = {
136
+ opt_fields: 'name,completed,assignee,due_on'
137
+ };
138
+
139
+ tasksApiInstance.createTask(body, opts).then((result) => {
140
+ console.log('Task created:', result.data);
141
+ }).catch((error) => {
142
+ console.error('Error creating task:', error.response.body);
143
+ });
144
+ ```
145
+
146
+ **Advanced Example with All Options:**
147
+
148
+ ```javascript
149
+ const body = {
150
+ data: {
151
+ name: 'Design new feature mockups',
152
+ notes: 'Create high-fidelity mockups for the new dashboard feature',
153
+ assignee: '9876543210987654',
154
+ workspace: '1234567890123456',
155
+ projects: ['1111111111111111'],
156
+ due_on: '2025-12-31',
157
+ due_at: '2025-12-31T17:00:00.000Z',
158
+ start_on: '2025-01-15',
159
+ followers: ['user_gid_1', 'user_gid_2'],
160
+ tags: ['tag_gid_1'],
161
+ custom_fields: {
162
+ '5678901234567890': 'High',
163
+ '9012345678901234': '42'
164
+ },
165
+ resource_subtype: 'default_task',
166
+ completed: false,
167
+ liked: false,
168
+ html_notes: '<body>Create <strong>high-fidelity</strong> mockups</body>',
169
+ external: {
170
+ gid: 'my_external_id_123',
171
+ data: 'Custom external data'
172
+ }
173
+ }
174
+ };
175
+
176
+ const opts = {
177
+ opt_fields: 'name,assignee,assignee.name,due_on,completed,projects,projects.name,tags,tags.name,custom_fields,custom_fields.name,followers,followers.name'
178
+ };
179
+
180
+ tasksApiInstance.createTask(body, opts).then((result) => {
181
+ console.log('Task created:', JSON.stringify(result.data, null, 2));
182
+ }).catch((error) => {
183
+ console.error('Error:', error.response.body);
184
+ });
185
+ ```
186
+
187
+ #### Getting a Task
188
+
189
+ ```javascript
190
+ const taskGid = '1234567890123456';
191
+
192
+ const opts = {
193
+ opt_fields: 'name,notes,assignee,assignee.name,assignee.email,completed,due_on,due_at,projects,projects.name,tags,tags.name,custom_fields,custom_fields.name,custom_fields.display_value,followers,followers.name,created_at,modified_at,completed_at,memberships,memberships.project.name,memberships.section.name'
194
+ };
195
+
196
+ tasksApiInstance.getTask(taskGid, opts).then((result) => {
197
+ console.log('Task details:', JSON.stringify(result.data, null, 2));
198
+ }).catch((error) => {
199
+ console.error('Error getting task:', error.response.body);
200
+ });
201
+ ```
202
+
203
+ #### Updating Tasks
204
+
205
+ ```javascript
206
+ const taskGid = '1234567890123456';
207
+
208
+ const body = {
209
+ data: {
210
+ name: 'Updated task name',
211
+ notes: 'Updated task description',
212
+ completed: true,
213
+ assignee: 'another_user_gid',
214
+ due_on: '2025-12-31'
215
+ }
216
+ };
217
+
218
+ const opts = {
219
+ opt_fields: 'name,completed,assignee.name,due_on'
220
+ };
221
+
222
+ tasksApiInstance.updateTask(body, taskGid, opts).then((result) => {
223
+ console.log('Task updated:', result.data);
224
+ }).catch((error) => {
225
+ console.error('Error updating task:', error.response.body);
226
+ });
227
+ ```
228
+
229
+ #### Deleting Tasks
230
+
231
+ ```javascript
232
+ const taskGid = '1234567890123456';
233
+
234
+ tasksApiInstance.deleteTask(taskGid).then((result) => {
235
+ console.log('Task deleted successfully');
236
+ }).catch((error) => {
237
+ console.error('Error deleting task:', error.response.body);
238
+ });
239
+ ```
240
+
241
+ #### Searching Tasks in a Workspace
242
+
243
+ ```javascript
244
+ const workspaceGid = '1234567890123456';
245
+
246
+ const opts = {
247
+ assignee: 'me',
248
+ completed: false,
249
+ opt_fields: 'name,assignee.name,due_on,projects.name,completed',
250
+ sort_by: 'due_date',
251
+ sort_ascending: true
252
+ };
253
+
254
+ tasksApiInstance.searchTasksForWorkspace(workspaceGid, opts).then((result) => {
255
+ console.log('Tasks found:', result.data);
256
+ }).catch((error) => {
257
+ console.error('Error searching tasks:', error.response.body);
258
+ });
259
+ ```
260
+
261
+ ### Projects API
262
+
263
+ Projects represent a prioritized list of tasks or a board with columns of tasks.
264
+
265
+ #### Creating Projects
266
+
267
+ **Minimal Example:**
268
+
269
+ ```javascript
270
+ const Asana = require('asana');
271
+
272
+ const client = Asana.ApiClient.instance;
273
+ const token = client.authentications['token'];
274
+ token.accessToken = process.env.ASANA_ACCESS_TOKEN;
275
+
276
+ const projectsApiInstance = new Asana.ProjectsApi();
277
+
278
+ const body = {
279
+ data: {
280
+ name: 'Marketing Campaign Q4',
281
+ workspace: '1234567890123456'
282
+ }
283
+ };
284
+
285
+ const opts = {
286
+ opt_fields: 'name,owner,due_date,created_at'
287
+ };
288
+
289
+ projectsApiInstance.createProject(body, opts).then((result) => {
290
+ console.log('Project created:', result.data);
291
+ }).catch((error) => {
292
+ console.error('Error creating project:', error.response.body);
293
+ });
294
+ ```
295
+
296
+ **Advanced Example:**
297
+
298
+ ```javascript
299
+ const body = {
300
+ data: {
301
+ name: 'Website Redesign 2025',
302
+ notes: 'Complete redesign of company website with new branding',
303
+ workspace: '1234567890123456',
304
+ team: '9876543210987654',
305
+ owner: 'user_gid',
306
+ due_date: '2025-12-31',
307
+ start_on: '2025-01-01',
308
+ color: 'light-green',
309
+ archived: false,
310
+ public: true,
311
+ default_view: 'board',
312
+ custom_fields: {
313
+ '5678901234567890': 'Active'
314
+ },
315
+ followers: ['user_gid_1', 'user_gid_2']
316
+ }
317
+ };
318
+
319
+ const opts = {
320
+ opt_fields: 'name,owner.name,due_date,team.name,custom_fields,members,members.name,archived,color,created_at,current_status,default_view'
321
+ };
322
+
323
+ projectsApiInstance.createProject(body, opts).then((result) => {
324
+ console.log('Project created:', JSON.stringify(result.data, null, 2));
325
+ }).catch((error) => {
326
+ console.error('Error:', error.response.body);
327
+ });
328
+ ```
329
+
330
+ #### Getting Projects
331
+
332
+ ```javascript
333
+ const projectGid = '1234567890123456';
334
+
335
+ const opts = {
336
+ opt_fields: 'name,owner.name,notes,due_date,start_on,archived,color,created_at,modified_at,team.name,workspace.name,members,members.name,followers,followers.name,custom_fields,custom_fields.name,custom_fields.display_value'
337
+ };
338
+
339
+ projectsApiInstance.getProject(projectGid, opts).then((result) => {
340
+ console.log('Project details:', JSON.stringify(result.data, null, 2));
341
+ }).catch((error) => {
342
+ console.error('Error getting project:', error.response.body);
343
+ });
344
+ ```
345
+
346
+ #### Updating Projects
347
+
348
+ ```javascript
349
+ const projectGid = '1234567890123456';
350
+
351
+ const body = {
352
+ data: {
353
+ name: 'Updated Project Name',
354
+ notes: 'Updated project description',
355
+ color: 'dark-blue',
356
+ archived: false,
357
+ public: false
358
+ }
359
+ };
360
+
361
+ projectsApiInstance.updateProject(body, projectGid).then((result) => {
362
+ console.log('Project updated:', result.data);
363
+ }).catch((error) => {
364
+ console.error('Error updating project:', error.response.body);
365
+ });
366
+ ```
367
+
368
+ #### Getting Tasks in a Project
369
+
370
+ ```javascript
371
+ const projectGid = '1234567890123456';
372
+
373
+ const opts = {
374
+ opt_fields: 'name,assignee.name,completed,due_on,tags.name'
375
+ };
376
+
377
+ tasksApiInstance.getTasksForProject(projectGid, opts).then((result) => {
378
+ console.log('Project tasks:', result.data);
379
+ }).catch((error) => {
380
+ console.error('Error getting project tasks:', error.response.body);
381
+ });
382
+ ```
383
+
384
+ #### Adding a Task to a Project
385
+
386
+ ```javascript
387
+ const taskGid = '1234567890123456';
388
+
389
+ const body = {
390
+ data: {
391
+ project: '9876543210987654'
392
+ }
393
+ };
394
+
395
+ tasksApiInstance.addProjectForTask(body, taskGid).then((result) => {
396
+ console.log('Task added to project');
397
+ }).catch((error) => {
398
+ console.error('Error adding task to project:', error.response.body);
399
+ });
400
+ ```
401
+
402
+ ### Sections API
403
+
404
+ Sections divide tasks within a project into categories, workflow stages, or priorities.
405
+
406
+ #### Creating Sections
407
+
408
+ ```javascript
409
+ const Asana = require('asana');
410
+
411
+ const client = Asana.ApiClient.instance;
412
+ const token = client.authentications['token'];
413
+ token.accessToken = process.env.ASANA_ACCESS_TOKEN;
414
+
415
+ const sectionsApiInstance = new Asana.SectionsApi();
416
+ const projectGid = '1234567890123456';
417
+
418
+ const body = {
419
+ data: {
420
+ name: 'In Progress'
421
+ }
422
+ };
423
+
424
+ const opts = {
425
+ opt_fields: 'name,created_at,project.name'
426
+ };
427
+
428
+ sectionsApiInstance.createSectionForProject(body, projectGid, opts).then((result) => {
429
+ console.log('Section created:', result.data);
430
+ }).catch((error) => {
431
+ console.error('Error creating section:', error.response.body);
432
+ });
433
+ ```
434
+
435
+ #### Getting Sections in a Project
436
+
437
+ ```javascript
438
+ const projectGid = '1234567890123456';
439
+
440
+ const opts = {
441
+ opt_fields: 'name,created_at,project.name'
442
+ };
443
+
444
+ sectionsApiInstance.getSectionsForProject(projectGid, opts).then((result) => {
445
+ console.log('Sections:', result.data);
446
+ }).catch((error) => {
447
+ console.error('Error getting sections:', error.response.body);
448
+ });
449
+ ```
450
+
451
+ #### Adding a Task to a Section
452
+
453
+ ```javascript
454
+ const sectionGid = '1234567890123456';
455
+
456
+ const body = {
457
+ data: {
458
+ task: '9876543210987654'
459
+ }
460
+ };
461
+
462
+ sectionsApiInstance.addTaskForSection(body, sectionGid).then((result) => {
463
+ console.log('Task added to section');
464
+ }).catch((error) => {
465
+ console.error('Error adding task to section:', error.response.body);
466
+ });
467
+ ```
468
+
469
+ ### Workspaces API
470
+
471
+ Workspaces are the highest-level organizational unit in Asana.
472
+
473
+ #### Getting Workspaces
474
+
475
+ ```javascript
476
+ const Asana = require('asana');
477
+
478
+ const client = Asana.ApiClient.instance;
479
+ const token = client.authentications['token'];
480
+ token.accessToken = process.env.ASANA_ACCESS_TOKEN;
481
+
482
+ const workspacesApiInstance = new Asana.WorkspacesApi();
483
+
484
+ const opts = {
485
+ opt_fields: 'name,is_organization,email_domains'
486
+ };
487
+
488
+ workspacesApiInstance.getWorkspaces(opts).then((result) => {
489
+ console.log('Workspaces:', result.data);
490
+ }).catch((error) => {
491
+ console.error('Error getting workspaces:', error.response.body);
492
+ });
493
+ ```
494
+
495
+ #### Getting a Workspace
496
+
497
+ ```javascript
498
+ const workspaceGid = '1234567890123456';
499
+
500
+ const opts = {
501
+ opt_fields: 'name,is_organization,email_domains'
502
+ };
503
+
504
+ workspacesApiInstance.getWorkspace(workspaceGid, opts).then((result) => {
505
+ console.log('Workspace:', result.data);
506
+ }).catch((error) => {
507
+ console.error('Error getting workspace:', error.response.body);
508
+ });
509
+ ```
510
+
511
+ #### Getting Projects in a Workspace
512
+
513
+ ```javascript
514
+ const workspaceGid = '1234567890123456';
515
+
516
+ const projectsApiInstance = new Asana.ProjectsApi();
517
+
518
+ const opts = {
519
+ archived: false,
520
+ opt_fields: 'name,owner.name,due_date,created_at'
521
+ };
522
+
523
+ projectsApiInstance.getProjectsForWorkspace(workspaceGid, opts).then((result) => {
524
+ console.log('Projects:', result.data);
525
+ }).catch((error) => {
526
+ console.error('Error getting projects:', error.response.body);
527
+ });
528
+ ```
529
+
530
+ ### Users API
531
+
532
+ Users represent individuals in Asana.
533
+
534
+ #### Getting the Current User
535
+
536
+ ```javascript
537
+ const Asana = require('asana');
538
+
539
+ const client = Asana.ApiClient.instance;
540
+ const token = client.authentications['token'];
541
+ token.accessToken = process.env.ASANA_ACCESS_TOKEN;
542
+
543
+ const usersApiInstance = new Asana.UsersApi();
544
+ const userGid = 'me';
545
+
546
+ const opts = {
547
+ opt_fields: 'name,email,photo,workspaces,workspaces.name'
548
+ };
549
+
550
+ usersApiInstance.getUser(userGid, opts).then((result) => {
551
+ console.log('Current user:', result.data);
552
+ }).catch((error) => {
553
+ console.error('Error getting user:', error.response.body);
554
+ });
555
+ ```
556
+
557
+ #### Getting Users in a Workspace
558
+
559
+ ```javascript
560
+ const workspaceGid = '1234567890123456';
561
+
562
+ const opts = {
563
+ opt_fields: 'name,email,photo'
564
+ };
565
+
566
+ usersApiInstance.getUsersForWorkspace(workspaceGid, opts).then((result) => {
567
+ console.log('Users:', result.data);
568
+ }).catch((error) => {
569
+ console.error('Error getting users:', error.response.body);
570
+ });
571
+ ```
572
+
573
+ ### Teams API
574
+
575
+ Teams organize people and projects within a workspace.
576
+
577
+ #### Getting Teams
578
+
579
+ ```javascript
580
+ const Asana = require('asana');
581
+
582
+ const client = Asana.ApiClient.instance;
583
+ const token = client.authentications['token'];
584
+ token.accessToken = process.env.ASANA_ACCESS_TOKEN;
585
+
586
+ const teamsApiInstance = new Asana.TeamsApi();
587
+ const workspaceGid = '1234567890123456';
588
+
589
+ const opts = {
590
+ opt_fields: 'name,description,organization.name'
591
+ };
592
+
593
+ teamsApiInstance.getTeamsForWorkspace(workspaceGid, opts).then((result) => {
594
+ console.log('Teams:', result.data);
595
+ }).catch((error) => {
596
+ console.error('Error getting teams:', error.response.body);
597
+ });
598
+ ```
599
+
600
+ #### Getting a Team
601
+
602
+ ```javascript
603
+ const teamGid = '1234567890123456';
604
+
605
+ const opts = {
606
+ opt_fields: 'name,description,organization.name,html_description'
607
+ };
608
+
609
+ teamsApiInstance.getTeam(teamGid, opts).then((result) => {
610
+ console.log('Team:', result.data);
611
+ }).catch((error) => {
612
+ console.error('Error getting team:', error.response.body);
613
+ });
614
+ ```
615
+
616
+ ### Custom Fields API
617
+
618
+ Custom fields allow you to add structured metadata to tasks and projects.
619
+
620
+ #### Getting Custom Fields in a Workspace
621
+
622
+ ```javascript
623
+ const Asana = require('asana');
624
+
625
+ const client = Asana.ApiClient.instance;
626
+ const token = client.authentications['token'];
627
+ token.accessToken = process.env.ASANA_ACCESS_TOKEN;
628
+
629
+ const customFieldsApiInstance = new Asana.CustomFieldsApi();
630
+ const workspaceGid = '1234567890123456';
631
+
632
+ const opts = {
633
+ opt_fields: 'name,resource_subtype,type,enum_options,enum_options.name,precision'
634
+ };
635
+
636
+ customFieldsApiInstance.getCustomFieldsForWorkspace(workspaceGid, opts).then((result) => {
637
+ console.log('Custom fields:', result.data);
638
+ }).catch((error) => {
639
+ console.error('Error getting custom fields:', error.response.body);
640
+ });
641
+ ```
642
+
643
+ #### Creating a Custom Field
644
+
645
+ ```javascript
646
+ const workspaceGid = '1234567890123456';
647
+
648
+ const body = {
649
+ data: {
650
+ name: 'Priority',
651
+ resource_subtype: 'enum',
652
+ type: 'enum',
653
+ workspace: workspaceGid,
654
+ enum_options: [
655
+ { name: 'Low', enabled: true, color: 'blue' },
656
+ { name: 'Medium', enabled: true, color: 'yellow' },
657
+ { name: 'High', enabled: true, color: 'red' }
658
+ ]
659
+ }
660
+ };
661
+
662
+ customFieldsApiInstance.createCustomField(body).then((result) => {
663
+ console.log('Custom field created:', result.data);
664
+ }).catch((error) => {
665
+ console.error('Error creating custom field:', error.response.body);
666
+ });
667
+ ```
668
+
669
+ #### Updating Custom Field Value on a Task
670
+
671
+ ```javascript
672
+ const taskGid = '1234567890123456';
673
+ const customFieldGid = '9876543210987654';
674
+
675
+ const body = {
676
+ data: {
677
+ custom_fields: {
678
+ [customFieldGid]: 'High'
679
+ }
680
+ }
681
+ };
682
+
683
+ tasksApiInstance.updateTask(body, taskGid).then((result) => {
684
+ console.log('Custom field updated');
685
+ }).catch((error) => {
686
+ console.error('Error updating custom field:', error.response.body);
687
+ });
688
+ ```
689
+
690
+ ### Tags API
691
+
692
+ Tags are labels that can be attached to tasks.
693
+
694
+ #### Creating Tags
695
+
696
+ ```javascript
697
+ const Asana = require('asana');
698
+
699
+ const client = Asana.ApiClient.instance;
700
+ const token = client.authentications['token'];
701
+ token.accessToken = process.env.ASANA_ACCESS_TOKEN;
702
+
703
+ const tagsApiInstance = new Asana.TagsApi();
704
+
705
+ const body = {
706
+ data: {
707
+ name: 'Bug',
708
+ workspace: '1234567890123456',
709
+ color: 'red'
710
+ }
711
+ };
712
+
713
+ const opts = {
714
+ opt_fields: 'name,color,created_at'
715
+ };
716
+
717
+ tagsApiInstance.createTag(body, opts).then((result) => {
718
+ console.log('Tag created:', result.data);
719
+ }).catch((error) => {
720
+ console.error('Error creating tag:', error.response.body);
721
+ });
722
+ ```
723
+
724
+ #### Getting Tags in a Workspace
725
+
726
+ ```javascript
727
+ const workspaceGid = '1234567890123456';
728
+
729
+ const opts = {
730
+ opt_fields: 'name,color,created_at'
731
+ };
732
+
733
+ tagsApiInstance.getTagsForWorkspace(workspaceGid, opts).then((result) => {
734
+ console.log('Tags:', result.data);
735
+ }).catch((error) => {
736
+ console.error('Error getting tags:', error.response.body);
737
+ });
738
+ ```
739
+
740
+ ### Attachments API
741
+
742
+ Attachments are files or URLs associated with tasks.
743
+
744
+ #### Uploading an Attachment to a Task
745
+
746
+ ```javascript
747
+ const Asana = require('asana');
748
+ const fs = require('fs');
749
+
750
+ const client = Asana.ApiClient.instance;
751
+ const token = client.authentications['token'];
752
+ token.accessToken = process.env.ASANA_ACCESS_TOKEN;
753
+
754
+ const attachmentsApiInstance = new Asana.AttachmentsApi();
755
+ const taskGid = '1234567890123456';
756
+
757
+ const body = {
758
+ file: fs.createReadStream('/path/to/file.pdf'),
759
+ parent: taskGid
760
+ };
761
+
762
+ const opts = {
763
+ opt_fields: 'name,download_url,size,host'
764
+ };
765
+
766
+ attachmentsApiInstance.createAttachmentForObject(body, opts).then((result) => {
767
+ console.log('Attachment uploaded:', result.data);
768
+ }).catch((error) => {
769
+ console.error('Error uploading attachment:', error.response.body);
770
+ });
771
+ ```
772
+
773
+ #### Getting Attachments for a Task
774
+
775
+ ```javascript
776
+ const taskGid = '1234567890123456';
777
+
778
+ const opts = {
779
+ opt_fields: 'name,download_url,size,host,created_at'
780
+ };
781
+
782
+ attachmentsApiInstance.getAttachmentsForObject(taskGid, opts).then((result) => {
783
+ console.log('Attachments:', result.data);
784
+ }).catch((error) => {
785
+ console.error('Error getting attachments:', error.response.body);
786
+ });
787
+ ```
788
+
789
+ ### Webhooks API
790
+
791
+ Webhooks allow applications to be notified of changes in Asana.
792
+
793
+ #### Creating a Webhook
794
+
795
+ ```javascript
796
+ const Asana = require('asana');
797
+
798
+ const client = Asana.ApiClient.instance;
799
+ const token = client.authentications['token'];
800
+ token.accessToken = process.env.ASANA_ACCESS_TOKEN;
801
+
802
+ const webhooksApiInstance = new Asana.WebhooksApi();
803
+
804
+ const body = {
805
+ data: {
806
+ resource: '1234567890123456', // project GID
807
+ target: 'https://example.com/webhooks/asana'
808
+ }
809
+ };
810
+
811
+ const opts = {
812
+ opt_fields: 'resource,target,active,last_success_at,last_failure_at'
813
+ };
814
+
815
+ webhooksApiInstance.createWebhook(body, opts).then((result) => {
816
+ console.log('Webhook created:', result.data);
817
+ }).catch((error) => {
818
+ console.error('Error creating webhook:', error.response.body);
819
+ });
820
+ ```
821
+
822
+ #### Getting Webhooks
823
+
824
+ ```javascript
825
+ const workspaceGid = '1234567890123456';
826
+
827
+ const opts = {
828
+ resource: workspaceGid,
829
+ opt_fields: 'resource,target,active,created_at,last_success_at,last_failure_at'
830
+ };
831
+
832
+ webhooksApiInstance.getWebhooks(opts).then((result) => {
833
+ console.log('Webhooks:', result.data);
834
+ }).catch((error) => {
835
+ console.error('Error getting webhooks:', error.response.body);
836
+ });
837
+ ```
838
+
839
+ #### Deleting a Webhook
840
+
841
+ ```javascript
842
+ const webhookGid = '1234567890123456';
843
+
844
+ webhooksApiInstance.deleteWebhook(webhookGid).then((result) => {
845
+ console.log('Webhook deleted');
846
+ }).catch((error) => {
847
+ console.error('Error deleting webhook:', error.response.body);
848
+ });
849
+ ```
850
+
851
+ #### Handling Webhook Events
852
+
853
+ ```javascript
854
+ const express = require('express');
855
+ const crypto = require('crypto');
856
+ const app = express();
857
+
858
+ app.use(express.json());
859
+
860
+ app.post('/webhooks/asana', (req, res) => {
861
+ // Verify webhook signature
862
+ const signature = req.headers['x-hook-signature'];
863
+ const secret = process.env.ASANA_WEBHOOK_SECRET;
864
+
865
+ const hash = crypto
866
+ .createHmac('sha256', secret)
867
+ .update(JSON.stringify(req.body))
868
+ .digest('hex');
869
+
870
+ if (signature !== hash) {
871
+ return res.status(401).send('Invalid signature');
872
+ }
873
+
874
+ // Handle handshake
875
+ if (req.headers['x-hook-secret']) {
876
+ res.setHeader('X-Hook-Secret', req.headers['x-hook-secret']);
877
+ return res.status(200).send();
878
+ }
879
+
880
+ // Process webhook events
881
+ const events = req.body.events || [];
882
+
883
+ events.forEach((event) => {
884
+ console.log('Event:', event.action, 'Resource:', event.resource);
885
+
886
+ if (event.action === 'added') {
887
+ console.log('Task added:', event.resource.gid);
888
+ } else if (event.action === 'changed') {
889
+ console.log('Task changed:', event.resource.gid);
890
+ } else if (event.action === 'removed') {
891
+ console.log('Task removed:', event.resource.gid);
892
+ }
893
+ });
894
+
895
+ res.status(200).send();
896
+ });
897
+
898
+ app.listen(3000, () => {
899
+ console.log('Webhook server listening on port 3000');
900
+ });
901
+ ```
902
+
903
+ ### Stories API (Comments and Activity)
904
+
905
+ Stories represent the activity feed on tasks and projects.
906
+
907
+ #### Creating a Comment on a Task
908
+
909
+ ```javascript
910
+ const Asana = require('asana');
911
+
912
+ const client = Asana.ApiClient.instance;
913
+ const token = client.authentications['token'];
914
+ token.accessToken = process.env.ASANA_ACCESS_TOKEN;
915
+
916
+ const storiesApiInstance = new Asana.StoriesApi();
917
+ const taskGid = '1234567890123456';
918
+
919
+ const body = {
920
+ data: {
921
+ text: 'This is a comment on the task',
922
+ is_pinned: false
923
+ }
924
+ };
925
+
926
+ const opts = {
927
+ opt_fields: 'text,created_at,created_by.name,is_pinned'
928
+ };
929
+
930
+ storiesApiInstance.createStoryForTask(body, taskGid, opts).then((result) => {
931
+ console.log('Comment created:', result.data);
932
+ }).catch((error) => {
933
+ console.error('Error creating comment:', error.response.body);
934
+ });
935
+ ```
936
+
937
+ #### Getting Comments for a Task
938
+
939
+ ```javascript
940
+ const taskGid = '1234567890123456';
941
+
942
+ const opts = {
943
+ opt_fields: 'text,created_at,created_by.name,resource_subtype,type'
944
+ };
945
+
946
+ storiesApiInstance.getStoriesForTask(taskGid, opts).then((result) => {
947
+ console.log('Stories:', result.data);
948
+ }).catch((error) => {
949
+ console.error('Error getting stories:', error.response.body);
950
+ });
951
+ ```
952
+
953
+ ### Portfolios API
954
+
955
+ Portfolios are collections of projects.
956
+
957
+ #### Creating a Portfolio
958
+
959
+ ```javascript
960
+ const Asana = require('asana');
961
+
962
+ const client = Asana.ApiClient.instance;
963
+ const token = client.authentications['token'];
964
+ token.accessToken = process.env.ASANA_ACCESS_TOKEN;
965
+
966
+ const portfoliosApiInstance = new Asana.PortfoliosApi();
967
+
968
+ const body = {
969
+ data: {
970
+ name: 'Product Initiatives',
971
+ workspace: '1234567890123456',
972
+ color: 'light-pink',
973
+ public: false
974
+ }
975
+ };
976
+
977
+ const opts = {
978
+ opt_fields: 'name,color,created_at,owner.name'
979
+ };
980
+
981
+ portfoliosApiInstance.createPortfolio(body, opts).then((result) => {
982
+ console.log('Portfolio created:', result.data);
983
+ }).catch((error) => {
984
+ console.error('Error creating portfolio:', error.response.body);
985
+ });
986
+ ```
987
+
988
+ #### Adding a Project to a Portfolio
989
+
990
+ ```javascript
991
+ const portfolioGid = '1234567890123456';
992
+
993
+ const body = {
994
+ data: {
995
+ project: '9876543210987654'
996
+ }
997
+ };
998
+
999
+ portfoliosApiInstance.addItemForPortfolio(body, portfolioGid).then((result) => {
1000
+ console.log('Project added to portfolio');
1001
+ }).catch((error) => {
1002
+ console.error('Error adding project to portfolio:', error.response.body);
1003
+ });
1004
+ ```
1005
+
1006
+ ## Error Handling
1007
+
1008
+ ### Basic Error Handling
1009
+
1010
+ ```javascript
1011
+ const Asana = require('asana');
1012
+
1013
+ const client = Asana.ApiClient.instance;
1014
+ const token = client.authentications['token'];
1015
+ token.accessToken = process.env.ASANA_ACCESS_TOKEN;
1016
+
1017
+ const tasksApiInstance = new Asana.TasksApi();
1018
+
1019
+ async function getTask(taskGid) {
1020
+ try {
1021
+ const result = await tasksApiInstance.getTask(taskGid);
1022
+ console.log('Task:', result.data);
1023
+ return result.data;
1024
+ } catch (error) {
1025
+ if (error.response) {
1026
+ console.error('API Error:', error.response.body);
1027
+ console.error('Status:', error.response.status);
1028
+
1029
+ if (error.response.status === 401) {
1030
+ console.error('Authentication failed. Check your access token.');
1031
+ } else if (error.response.status === 403) {
1032
+ console.error('Permission denied. You do not have access to this resource.');
1033
+ } else if (error.response.status === 404) {
1034
+ console.error('Resource not found.');
1035
+ } else if (error.response.status === 429) {
1036
+ console.error('Rate limit exceeded. Please retry after some time.');
1037
+ }
1038
+ } else {
1039
+ console.error('Network error:', error.message);
1040
+ }
1041
+ throw error;
1042
+ }
1043
+ }
1044
+ ```
1045
+
1046
+ ### Retry Pattern for Rate Limiting
1047
+
1048
+ ```javascript
1049
+ async function makeRequestWithRetry(apiCall, maxRetries = 3) {
1050
+ for (let i = 0; i < maxRetries; i++) {
1051
+ try {
1052
+ return await apiCall();
1053
+ } catch (error) {
1054
+ if (error.response?.status === 429) {
1055
+ const retryAfter = error.response.headers['retry-after'] || Math.pow(2, i);
1056
+ console.log(`Rate limited. Retrying after ${retryAfter} seconds...`);
1057
+ await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
1058
+ } else {
1059
+ throw error;
1060
+ }
1061
+ }
1062
+ }
1063
+ throw new Error('Max retries exceeded');
1064
+ }
1065
+
1066
+ // Usage
1067
+ makeRequestWithRetry(() => tasksApiInstance.getTask(taskGid))
1068
+ .then(result => console.log('Task:', result.data))
1069
+ .catch(error => console.error('Failed after retries:', error));
1070
+ ```
1071
+
1072
+ ## Pagination
1073
+
1074
+ ### Handling Paginated Results
1075
+
1076
+ ```javascript
1077
+ async function getAllTasks(projectGid) {
1078
+ const allTasks = [];
1079
+ let offset = undefined;
1080
+
1081
+ do {
1082
+ const opts = {
1083
+ limit: 100,
1084
+ offset: offset,
1085
+ opt_fields: 'name,completed,assignee.name'
1086
+ };
1087
+
1088
+ const result = await tasksApiInstance.getTasksForProject(projectGid, opts);
1089
+ allTasks.push(...result.data);
1090
+
1091
+ offset = result.next_page?.offset;
1092
+ } while (offset);
1093
+
1094
+ return allTasks;
1095
+ }
1096
+
1097
+ // Usage
1098
+ getAllTasks('1234567890123456').then(tasks => {
1099
+ console.log(`Total tasks: ${tasks.length}`);
1100
+ tasks.forEach(task => console.log(task.name));
1101
+ });
1102
+ ```
1103
+
1104
+ ## OAuth Authentication Flow
1105
+
1106
+ ### Setting Up OAuth
1107
+
1108
+ ```javascript
1109
+ const express = require('express');
1110
+ const Asana = require('asana');
1111
+
1112
+ const app = express();
1113
+
1114
+ const ASANA_CLIENT_ID = process.env.ASANA_CLIENT_ID;
1115
+ const ASANA_CLIENT_SECRET = process.env.ASANA_CLIENT_SECRET;
1116
+ const REDIRECT_URI = process.env.ASANA_REDIRECT_URI;
1117
+
1118
+ // Generate authorization URL
1119
+ app.get('/auth', (req, res) => {
1120
+ const authUrl = `https://app.asana.com/-/oauth_authorize?client_id=${ASANA_CLIENT_ID}&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&response_type=code&state=random_state_string`;
1121
+ res.redirect(authUrl);
1122
+ });
1123
+
1124
+ // Handle OAuth callback
1125
+ app.get('/auth/callback', async (req, res) => {
1126
+ const code = req.query.code;
1127
+
1128
+ try {
1129
+ const response = await fetch('https://app.asana.com/-/oauth_token', {
1130
+ method: 'POST',
1131
+ headers: {
1132
+ 'Content-Type': 'application/x-www-form-urlencoded',
1133
+ },
1134
+ body: new URLSearchParams({
1135
+ grant_type: 'authorization_code',
1136
+ client_id: ASANA_CLIENT_ID,
1137
+ client_secret: ASANA_CLIENT_SECRET,
1138
+ redirect_uri: REDIRECT_URI,
1139
+ code: code
1140
+ })
1141
+ });
1142
+
1143
+ const data = await response.json();
1144
+
1145
+ if (data.access_token) {
1146
+ // Store access_token and refresh_token securely
1147
+ console.log('Access token:', data.access_token);
1148
+ console.log('Refresh token:', data.refresh_token);
1149
+
1150
+ // Initialize Asana client with OAuth token
1151
+ const client = Asana.ApiClient.instance;
1152
+ const oauth = client.authentications['oauth2'];
1153
+ oauth.accessToken = data.access_token;
1154
+
1155
+ res.send('Authentication successful!');
1156
+ } else {
1157
+ res.status(400).send('Authentication failed');
1158
+ }
1159
+ } catch (error) {
1160
+ console.error('OAuth error:', error);
1161
+ res.status(500).send('Error during authentication');
1162
+ }
1163
+ });
1164
+
1165
+ app.listen(3000, () => {
1166
+ console.log('Server listening on http://localhost:3000');
1167
+ });
1168
+ ```
1169
+
1170
+ ### Refreshing OAuth Tokens
1171
+
1172
+ ```javascript
1173
+ async function refreshAccessToken(refreshToken) {
1174
+ try {
1175
+ const response = await fetch('https://app.asana.com/-/oauth_token', {
1176
+ method: 'POST',
1177
+ headers: {
1178
+ 'Content-Type': 'application/x-www-form-urlencoded',
1179
+ },
1180
+ body: new URLSearchParams({
1181
+ grant_type: 'refresh_token',
1182
+ client_id: process.env.ASANA_CLIENT_ID,
1183
+ client_secret: process.env.ASANA_CLIENT_SECRET,
1184
+ refresh_token: refreshToken
1185
+ })
1186
+ });
1187
+
1188
+ const data = await response.json();
1189
+
1190
+ if (data.access_token) {
1191
+ console.log('New access token:', data.access_token);
1192
+ return data.access_token;
1193
+ }
1194
+ } catch (error) {
1195
+ console.error('Token refresh error:', error);
1196
+ throw error;
1197
+ }
1198
+ }
1199
+ ```
1200
+
1201
+ ## TypeScript Support
1202
+
1203
+ ### Using Asana SDK with TypeScript
1204
+
1205
+ ```typescript
1206
+ import Asana from 'asana';
1207
+
1208
+ const client = Asana.ApiClient.instance;
1209
+ const token = client.authentications['token'];
1210
+ token.accessToken = process.env.ASANA_ACCESS_TOKEN!;
1211
+
1212
+ const tasksApiInstance = new Asana.TasksApi();
1213
+
1214
+ interface TaskData {
1215
+ name: string;
1216
+ workspace: string;
1217
+ assignee?: string;
1218
+ due_on?: string;
1219
+ }
1220
+
1221
+ async function createTask(taskData: TaskData): Promise<any> {
1222
+ const body = {
1223
+ data: taskData
1224
+ };
1225
+
1226
+ const opts = {
1227
+ opt_fields: 'name,assignee.name,due_on,completed'
1228
+ };
1229
+
1230
+ try {
1231
+ const result = await tasksApiInstance.createTask(body, opts);
1232
+ return result.data;
1233
+ } catch (error) {
1234
+ console.error('Error creating task:', error);
1235
+ throw error;
1236
+ }
1237
+ }
1238
+
1239
+ // Usage
1240
+ createTask({
1241
+ name: 'TypeScript task',
1242
+ workspace: '1234567890123456',
1243
+ due_on: '2025-12-31'
1244
+ }).then(task => {
1245
+ console.log('Task created:', task);
1246
+ });
1247
+ ```
1248
+
1249
+ ## Batch Operations
1250
+
1251
+ ### Creating Multiple Tasks
1252
+
1253
+ ```javascript
1254
+ async function createMultipleTasks(taskDataArray) {
1255
+ const promises = taskDataArray.map(taskData => {
1256
+ const body = { data: taskData };
1257
+ return tasksApiInstance.createTask(body);
1258
+ });
1259
+
1260
+ try {
1261
+ const results = await Promise.all(promises);
1262
+ console.log(`Created ${results.length} tasks`);
1263
+ return results.map(r => r.data);
1264
+ } catch (error) {
1265
+ console.error('Error creating tasks:', error);
1266
+ throw error;
1267
+ }
1268
+ }
1269
+
1270
+ // Usage
1271
+ const tasksToCreate = [
1272
+ { name: 'Task 1', workspace: '1234567890123456' },
1273
+ { name: 'Task 2', workspace: '1234567890123456' },
1274
+ { name: 'Task 3', workspace: '1234567890123456' }
1275
+ ];
1276
+
1277
+ createMultipleTasks(tasksToCreate).then(tasks => {
1278
+ console.log('Tasks created:', tasks);
1279
+ });
1280
+ ```
1281
+
1282
+ ## Environment Variable Validation
1283
+
1284
+ ```javascript
1285
+ const Asana = require('asana');
1286
+ require('dotenv').config();
1287
+
1288
+ function validateEnvironment() {
1289
+ if (!process.env.ASANA_ACCESS_TOKEN) {
1290
+ console.error('Error: ASANA_ACCESS_TOKEN is required in .env file');
1291
+ process.exit(1);
1292
+ }
1293
+
1294
+ console.log('Environment validated successfully');
1295
+ }
1296
+
1297
+ validateEnvironment();
1298
+
1299
+ const client = Asana.ApiClient.instance;
1300
+ const token = client.authentications['token'];
1301
+ token.accessToken = process.env.ASANA_ACCESS_TOKEN;
1302
+ ```
1303
+
1304
+ ## Complete Example Application
1305
+
1306
+ ```javascript
1307
+ const Asana = require('asana');
1308
+ require('dotenv').config();
1309
+
1310
+ // Validate environment
1311
+ if (!process.env.ASANA_ACCESS_TOKEN || !process.env.ASANA_WORKSPACE_ID) {
1312
+ console.error('Missing required environment variables');
1313
+ process.exit(1);
1314
+ }
1315
+
1316
+ // Initialize client
1317
+ const client = Asana.ApiClient.instance;
1318
+ const token = client.authentications['token'];
1319
+ token.accessToken = process.env.ASANA_ACCESS_TOKEN;
1320
+
1321
+ const tasksApiInstance = new Asana.TasksApi();
1322
+ const projectsApiInstance = new Asana.ProjectsApi();
1323
+ const usersApiInstance = new Asana.UsersApi();
1324
+
1325
+ async function main() {
1326
+ try {
1327
+ // Get current user
1328
+ console.log('Getting current user...');
1329
+ const userResult = await usersApiInstance.getUser('me', {
1330
+ opt_fields: 'name,email,workspaces.name'
1331
+ });
1332
+ console.log('Logged in as:', userResult.data.name);
1333
+
1334
+ // Create a project
1335
+ console.log('\nCreating project...');
1336
+ const projectBody = {
1337
+ data: {
1338
+ name: 'API Demo Project',
1339
+ workspace: process.env.ASANA_WORKSPACE_ID,
1340
+ notes: 'Project created via Asana API'
1341
+ }
1342
+ };
1343
+ const projectResult = await projectsApiInstance.createProject(projectBody, {
1344
+ opt_fields: 'name,gid'
1345
+ });
1346
+ console.log('Project created:', projectResult.data.name);
1347
+ const projectGid = projectResult.data.gid;
1348
+
1349
+ // Create tasks
1350
+ console.log('\nCreating tasks...');
1351
+ const taskNames = ['Design mockups', 'Implement feature', 'Write tests', 'Deploy'];
1352
+
1353
+ for (const taskName of taskNames) {
1354
+ const taskBody = {
1355
+ data: {
1356
+ name: taskName,
1357
+ workspace: process.env.ASANA_WORKSPACE_ID,
1358
+ projects: [projectGid]
1359
+ }
1360
+ };
1361
+ const taskResult = await tasksApiInstance.createTask(taskBody);
1362
+ console.log('Created task:', taskResult.data.name);
1363
+ }
1364
+
1365
+ // Get all tasks in project
1366
+ console.log('\nFetching project tasks...');
1367
+ const tasksResult = await tasksApiInstance.getTasksForProject(projectGid, {
1368
+ opt_fields: 'name,completed'
1369
+ });
1370
+ console.log(`Project has ${tasksResult.data.length} tasks`);
1371
+
1372
+ // Mark first task as complete
1373
+ if (tasksResult.data.length > 0) {
1374
+ const firstTaskGid = tasksResult.data[0].gid;
1375
+ console.log('\nMarking first task as complete...');
1376
+ await tasksApiInstance.updateTask(
1377
+ { data: { completed: true } },
1378
+ firstTaskGid
1379
+ );
1380
+ console.log('Task marked as complete');
1381
+ }
1382
+
1383
+ console.log('\nDemo completed successfully!');
1384
+
1385
+ } catch (error) {
1386
+ console.error('Error:', error.response?.body || error.message);
1387
+ process.exit(1);
1388
+ }
1389
+ }
1390
+
1391
+ main();
1392
+ ```
1393
+
1394
+ ## Notes
1395
+
1396
+ The Asana Node.js SDK is auto-generated from the OpenAPI specification, ensuring it stays current with the latest API features. The SDK supports both Personal Access Tokens for simple authentication and OAuth 2.0 for multi-user applications. All API methods return Promises and can be used with async/await syntax. The SDK automatically handles request formatting and response parsing. Rate limits apply: 1500 requests per minute for most operations, with some endpoints having lower limits. Use the `opt_fields` parameter to optimize API responses by requesting only the fields you need.