@adens/openwa 0.1.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 (51) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +319 -0
  4. package/bin/openwa.js +11 -0
  5. package/favicon.ico +0 -0
  6. package/logo-long.png +0 -0
  7. package/logo-square.png +0 -0
  8. package/package.json +69 -0
  9. package/prisma/schema.prisma +182 -0
  10. package/server/config.js +29 -0
  11. package/server/database/client.js +11 -0
  12. package/server/database/init.js +28 -0
  13. package/server/express/create-app.js +349 -0
  14. package/server/express/openapi.js +853 -0
  15. package/server/index.js +163 -0
  16. package/server/services/api-key-service.js +131 -0
  17. package/server/services/auth-service.js +162 -0
  18. package/server/services/chat-service.js +1014 -0
  19. package/server/services/session-service.js +81 -0
  20. package/server/socket/register.js +127 -0
  21. package/server/utils/avatar.js +34 -0
  22. package/server/utils/paths.js +29 -0
  23. package/server/whatsapp/adapters/mock-adapter.js +47 -0
  24. package/server/whatsapp/adapters/wwebjs-adapter.js +263 -0
  25. package/server/whatsapp/session-manager.js +356 -0
  26. package/web/components/AppHead.js +14 -0
  27. package/web/components/AuthCard.js +170 -0
  28. package/web/components/BrandLogo.js +11 -0
  29. package/web/components/ChatWindow.js +875 -0
  30. package/web/components/ChatWindow.js.tmp +0 -0
  31. package/web/components/ContactList.js +97 -0
  32. package/web/components/ContactsPanel.js +90 -0
  33. package/web/components/EmojiPicker.js +108 -0
  34. package/web/components/MediaPreviewModal.js +146 -0
  35. package/web/components/MessageActionMenu.js +155 -0
  36. package/web/components/SessionSidebar.js +167 -0
  37. package/web/components/SettingsModal.js +266 -0
  38. package/web/components/Skeletons.js +73 -0
  39. package/web/jsconfig.json +10 -0
  40. package/web/lib/api.js +33 -0
  41. package/web/lib/socket.js +9 -0
  42. package/web/pages/_app.js +5 -0
  43. package/web/pages/dashboard.js +541 -0
  44. package/web/pages/index.js +62 -0
  45. package/web/postcss.config.js +10 -0
  46. package/web/public/favicon.ico +0 -0
  47. package/web/public/logo-long.png +0 -0
  48. package/web/public/logo-square.png +0 -0
  49. package/web/store/useAppStore.js +209 -0
  50. package/web/styles/globals.css +52 -0
  51. package/web/tailwind.config.js +36 -0
@@ -0,0 +1,853 @@
1
+ const path = require("path");
2
+ const { rootDir } = require("../utils/paths");
3
+
4
+ const packageJson = require(path.join(rootDir, "package.json"));
5
+
6
+ function createOpenApiDocument(config) {
7
+ return {
8
+ openapi: "3.1.0",
9
+ info: {
10
+ title: "OpenWA API",
11
+ version: packageJson.version,
12
+ description: "HTTP API for the local OpenWA runtime, including auth, sessions, chats, contacts, messaging, and runtime metadata. For AI agents, fetch `/docs/readme` first, then authenticate with `X-API-Key` and use the HTTP endpoints directly."
13
+ },
14
+ servers: [
15
+ { url: config.frontendUrl, description: "Frontend-facing URL with proxied docs/health/version endpoints" },
16
+ { url: config.backendUrl, description: "Direct backend API URL" }
17
+ ],
18
+ tags: [
19
+ { name: "Runtime" },
20
+ { name: "Auth" },
21
+ { name: "Workspace" },
22
+ { name: "Sessions" },
23
+ { name: "Chats" },
24
+ { name: "Contacts" },
25
+ { name: "Messages" },
26
+ { name: "Media" }
27
+ ],
28
+ components: {
29
+ securitySchemes: {
30
+ bearerAuth: {
31
+ type: "http",
32
+ scheme: "bearer",
33
+ bearerFormat: "JWT"
34
+ },
35
+ apiKeyAuth: {
36
+ type: "apiKey",
37
+ in: "header",
38
+ name: "X-API-Key"
39
+ }
40
+ },
41
+ schemas: {
42
+ ErrorResponse: {
43
+ type: "object",
44
+ properties: {
45
+ error: { type: "string" }
46
+ },
47
+ required: ["error"]
48
+ },
49
+ User: {
50
+ type: "object",
51
+ properties: {
52
+ id: { type: "string" },
53
+ name: { type: "string" },
54
+ email: { type: "string", format: "email" },
55
+ createdAt: { type: "string", format: "date-time" }
56
+ },
57
+ required: ["id", "name", "email", "createdAt"]
58
+ },
59
+ AuthResponse: {
60
+ type: "object",
61
+ properties: {
62
+ token: { type: "string" },
63
+ user: { $ref: "#/components/schemas/User" }
64
+ },
65
+ required: ["token", "user"]
66
+ },
67
+ Session: {
68
+ type: "object",
69
+ properties: {
70
+ id: { type: "string" },
71
+ userId: { type: "string" },
72
+ name: { type: "string" },
73
+ phoneNumber: { type: ["string", "null"] },
74
+ status: { type: "string" },
75
+ transportType: { type: ["string", "null"] },
76
+ qrCode: { type: ["string", "null"] },
77
+ errorMessage: { type: ["string", "null"] },
78
+ createdAt: { type: "string", format: "date-time" },
79
+ updatedAt: { type: "string", format: "date-time" }
80
+ }
81
+ },
82
+ Contact: {
83
+ type: "object",
84
+ properties: {
85
+ id: { type: "string" },
86
+ externalId: { type: "string" },
87
+ displayName: { type: "string" },
88
+ avatarUrl: { type: ["string", "null"] },
89
+ lastMessagePreview: { type: ["string", "null"] },
90
+ lastMessageAt: { type: ["string", "null"], format: "date-time" },
91
+ unreadCount: { type: "integer" },
92
+ sessionId: { type: ["string", "null"] },
93
+ hasChat: { type: "boolean" }
94
+ }
95
+ },
96
+ MediaFile: {
97
+ type: "object",
98
+ properties: {
99
+ id: { type: "string" },
100
+ originalName: { type: "string" },
101
+ mimeType: { type: "string" },
102
+ relativePath: { type: "string" }
103
+ }
104
+ },
105
+ MessageStatus: {
106
+ type: "object",
107
+ properties: {
108
+ status: { type: "string" }
109
+ }
110
+ },
111
+ Message: {
112
+ type: "object",
113
+ properties: {
114
+ id: { type: "string" },
115
+ chatId: { type: "string" },
116
+ sessionId: { type: ["string", "null"] },
117
+ sender: { type: "string" },
118
+ receiver: { type: "string" },
119
+ body: { type: ["string", "null"] },
120
+ type: { type: "string" },
121
+ direction: { type: "string" },
122
+ createdAt: { type: "string", format: "date-time" },
123
+ updatedAt: { type: "string", format: "date-time" },
124
+ mediaFile: {
125
+ anyOf: [{ $ref: "#/components/schemas/MediaFile" }, { type: "null" }]
126
+ },
127
+ statuses: {
128
+ type: "array",
129
+ items: { $ref: "#/components/schemas/MessageStatus" }
130
+ }
131
+ }
132
+ },
133
+ Chat: {
134
+ type: "object",
135
+ properties: {
136
+ id: { type: "string" },
137
+ title: { type: ["string", "null"] },
138
+ sessionId: { type: ["string", "null"] },
139
+ contact: { $ref: "#/components/schemas/Contact" },
140
+ lastMessage: {
141
+ anyOf: [{ $ref: "#/components/schemas/Message" }, { type: "null" }]
142
+ },
143
+ updatedAt: { type: "string", format: "date-time" }
144
+ }
145
+ }
146
+ }
147
+ },
148
+ paths: {
149
+ "/health": {
150
+ get: {
151
+ tags: ["Runtime"],
152
+ summary: "Health check",
153
+ responses: {
154
+ 200: {
155
+ description: "Runtime health status",
156
+ content: {
157
+ "application/json": {
158
+ schema: {
159
+ type: "object",
160
+ properties: {
161
+ ok: { type: "boolean" },
162
+ service: { type: "string" },
163
+ version: { type: "string" }
164
+ },
165
+ required: ["ok", "service", "version"]
166
+ }
167
+ }
168
+ }
169
+ }
170
+ }
171
+ }
172
+ },
173
+ "/version": {
174
+ get: {
175
+ tags: ["Runtime"],
176
+ summary: "Get API version",
177
+ responses: {
178
+ 200: {
179
+ description: "Package version",
180
+ content: {
181
+ "application/json": {
182
+ schema: {
183
+ type: "object",
184
+ properties: {
185
+ name: { type: "string" },
186
+ version: { type: "string" }
187
+ },
188
+ required: ["name", "version"]
189
+ }
190
+ }
191
+ }
192
+ }
193
+ }
194
+ }
195
+ },
196
+ "/docs/json": {
197
+ get: {
198
+ tags: ["Runtime"],
199
+ summary: "Get OpenAPI document",
200
+ responses: {
201
+ 200: {
202
+ description: "OpenAPI JSON document"
203
+ }
204
+ }
205
+ }
206
+ },
207
+ "/docs/readme": {
208
+ get: {
209
+ tags: ["Runtime"],
210
+ summary: "Get agent-friendly API usage guide",
211
+ responses: {
212
+ 200: {
213
+ description: "Markdown guide for AI agents and external clients"
214
+ }
215
+ }
216
+ }
217
+ },
218
+ "/api/health": {
219
+ get: {
220
+ tags: ["Runtime"],
221
+ summary: "Backend health alias",
222
+ responses: {
223
+ 200: {
224
+ description: "Backend health status"
225
+ }
226
+ }
227
+ }
228
+ },
229
+ "/api/auth/register": {
230
+ post: {
231
+ tags: ["Auth"],
232
+ summary: "Register a user",
233
+ description: "Dashboard-oriented auth. External agents should normally use an API key instead of calling register.",
234
+ requestBody: {
235
+ required: true,
236
+ content: {
237
+ "application/json": {
238
+ schema: {
239
+ type: "object",
240
+ properties: {
241
+ name: { type: "string" },
242
+ email: { type: "string", format: "email" },
243
+ password: { type: "string" }
244
+ },
245
+ required: ["name", "email", "password"]
246
+ }
247
+ }
248
+ }
249
+ },
250
+ responses: {
251
+ 201: {
252
+ description: "User registered",
253
+ content: {
254
+ "application/json": {
255
+ schema: { $ref: "#/components/schemas/AuthResponse" }
256
+ }
257
+ }
258
+ },
259
+ 400: {
260
+ description: "Validation error",
261
+ content: {
262
+ "application/json": {
263
+ schema: { $ref: "#/components/schemas/ErrorResponse" }
264
+ }
265
+ }
266
+ }
267
+ }
268
+ }
269
+ },
270
+ "/api/auth/login": {
271
+ post: {
272
+ tags: ["Auth"],
273
+ summary: "Login a user",
274
+ description: "Dashboard-oriented auth. External agents normally do not need this when an API key is already provided.",
275
+ requestBody: {
276
+ required: true,
277
+ content: {
278
+ "application/json": {
279
+ schema: {
280
+ type: "object",
281
+ properties: {
282
+ email: { type: "string", format: "email" },
283
+ password: { type: "string" }
284
+ },
285
+ required: ["email", "password"]
286
+ }
287
+ }
288
+ }
289
+ },
290
+ responses: {
291
+ 200: {
292
+ description: "User logged in",
293
+ content: {
294
+ "application/json": {
295
+ schema: { $ref: "#/components/schemas/AuthResponse" }
296
+ }
297
+ }
298
+ },
299
+ 400: {
300
+ description: "Invalid credentials",
301
+ content: {
302
+ "application/json": {
303
+ schema: { $ref: "#/components/schemas/ErrorResponse" }
304
+ }
305
+ }
306
+ }
307
+ }
308
+ }
309
+ },
310
+ "/api/auth/me": {
311
+ get: {
312
+ tags: ["Auth"],
313
+ summary: "Get authenticated user",
314
+ security: [{ bearerAuth: [] }, { apiKeyAuth: [] }],
315
+ responses: {
316
+ 200: {
317
+ description: "Current user",
318
+ content: {
319
+ "application/json": {
320
+ schema: {
321
+ type: "object",
322
+ properties: {
323
+ user: { $ref: "#/components/schemas/User" }
324
+ },
325
+ required: ["user"]
326
+ }
327
+ }
328
+ }
329
+ },
330
+ 401: {
331
+ description: "Unauthorized",
332
+ content: {
333
+ "application/json": {
334
+ schema: { $ref: "#/components/schemas/ErrorResponse" }
335
+ }
336
+ }
337
+ }
338
+ }
339
+ }
340
+ },
341
+ "/api/api-keys": {
342
+ get: {
343
+ tags: ["Auth"],
344
+ summary: "List API keys for the current user",
345
+ security: [{ bearerAuth: [] }],
346
+ responses: {
347
+ 200: {
348
+ description: "API key list"
349
+ }
350
+ }
351
+ },
352
+ post: {
353
+ tags: ["Auth"],
354
+ summary: "Create an API key",
355
+ security: [{ bearerAuth: [] }],
356
+ requestBody: {
357
+ required: true,
358
+ content: {
359
+ "application/json": {
360
+ schema: {
361
+ type: "object",
362
+ properties: {
363
+ name: { type: "string" }
364
+ },
365
+ required: ["name"]
366
+ }
367
+ }
368
+ }
369
+ },
370
+ responses: {
371
+ 201: {
372
+ description: "Created API key"
373
+ }
374
+ }
375
+ }
376
+ },
377
+ "/api/api-keys/{apiKeyId}": {
378
+ delete: {
379
+ tags: ["Auth"],
380
+ summary: "Revoke an API key",
381
+ security: [{ bearerAuth: [] }],
382
+ parameters: [
383
+ {
384
+ name: "apiKeyId",
385
+ in: "path",
386
+ required: true,
387
+ schema: { type: "string" }
388
+ }
389
+ ],
390
+ responses: {
391
+ 200: {
392
+ description: "Revoked API key"
393
+ }
394
+ }
395
+ }
396
+ },
397
+ "/api/bootstrap": {
398
+ get: {
399
+ tags: ["Workspace"],
400
+ summary: "Load initial workspace payload",
401
+ security: [{ bearerAuth: [] }, { apiKeyAuth: [] }],
402
+ responses: {
403
+ 200: {
404
+ description: "Workspace bootstrap",
405
+ content: {
406
+ "application/json": {
407
+ schema: {
408
+ type: "object",
409
+ properties: {
410
+ user: { $ref: "#/components/schemas/User" },
411
+ sessions: {
412
+ type: "array",
413
+ items: { $ref: "#/components/schemas/Session" }
414
+ },
415
+ chats: {
416
+ type: "array",
417
+ items: { $ref: "#/components/schemas/Chat" }
418
+ },
419
+ activeChatId: { type: ["string", "null"] },
420
+ messages: {
421
+ type: "array",
422
+ items: { $ref: "#/components/schemas/Message" }
423
+ },
424
+ hasMoreMessages: { type: "boolean" },
425
+ nextBefore: { type: ["string", "null"] }
426
+ }
427
+ }
428
+ }
429
+ }
430
+ }
431
+ }
432
+ }
433
+ },
434
+ "/api/sessions": {
435
+ get: {
436
+ tags: ["Sessions"],
437
+ summary: "List WhatsApp sessions",
438
+ security: [{ bearerAuth: [] }, { apiKeyAuth: [] }],
439
+ responses: {
440
+ 200: {
441
+ description: "Session list"
442
+ }
443
+ }
444
+ },
445
+ post: {
446
+ tags: ["Sessions"],
447
+ summary: "Create a WhatsApp session",
448
+ security: [{ bearerAuth: [] }, { apiKeyAuth: [] }],
449
+ requestBody: {
450
+ required: true,
451
+ content: {
452
+ "application/json": {
453
+ schema: {
454
+ type: "object",
455
+ properties: {
456
+ name: { type: "string" },
457
+ phoneNumber: { type: "string" }
458
+ },
459
+ required: ["name"]
460
+ }
461
+ }
462
+ }
463
+ },
464
+ responses: {
465
+ 201: {
466
+ description: "Created session"
467
+ }
468
+ }
469
+ }
470
+ },
471
+ "/api/sessions/{sessionId}/connect": {
472
+ post: {
473
+ tags: ["Sessions"],
474
+ summary: "Connect a WhatsApp session",
475
+ security: [{ bearerAuth: [] }, { apiKeyAuth: [] }],
476
+ parameters: [
477
+ {
478
+ name: "sessionId",
479
+ in: "path",
480
+ required: true,
481
+ schema: { type: "string" }
482
+ }
483
+ ],
484
+ responses: {
485
+ 200: {
486
+ description: "Updated session"
487
+ }
488
+ }
489
+ }
490
+ },
491
+ "/api/sessions/{sessionId}/disconnect": {
492
+ post: {
493
+ tags: ["Sessions"],
494
+ summary: "Disconnect a WhatsApp session",
495
+ security: [{ bearerAuth: [] }, { apiKeyAuth: [] }],
496
+ parameters: [
497
+ {
498
+ name: "sessionId",
499
+ in: "path",
500
+ required: true,
501
+ schema: { type: "string" }
502
+ }
503
+ ],
504
+ responses: {
505
+ 200: {
506
+ description: "Updated session"
507
+ }
508
+ }
509
+ }
510
+ },
511
+ "/api/chats": {
512
+ get: {
513
+ tags: ["Chats"],
514
+ summary: "List chats",
515
+ security: [{ bearerAuth: [] }, { apiKeyAuth: [] }],
516
+ parameters: [
517
+ {
518
+ name: "sessionId",
519
+ in: "query",
520
+ required: false,
521
+ schema: { type: "string" }
522
+ },
523
+ {
524
+ name: "q",
525
+ in: "query",
526
+ required: false,
527
+ schema: { type: "string" }
528
+ }
529
+ ],
530
+ responses: {
531
+ 200: {
532
+ description: "Chat list"
533
+ }
534
+ }
535
+ }
536
+ },
537
+ "/api/contacts": {
538
+ get: {
539
+ tags: ["Contacts"],
540
+ summary: "List contacts",
541
+ security: [{ bearerAuth: [] }, { apiKeyAuth: [] }],
542
+ parameters: [
543
+ {
544
+ name: "sessionId",
545
+ in: "query",
546
+ required: false,
547
+ schema: { type: "string" }
548
+ },
549
+ {
550
+ name: "q",
551
+ in: "query",
552
+ required: false,
553
+ schema: { type: "string" }
554
+ }
555
+ ],
556
+ responses: {
557
+ 200: {
558
+ description: "Contact list"
559
+ }
560
+ }
561
+ }
562
+ },
563
+ "/api/contacts/{contactId}/open": {
564
+ post: {
565
+ tags: ["Contacts"],
566
+ summary: "Open or create a chat for a contact",
567
+ security: [{ bearerAuth: [] }, { apiKeyAuth: [] }],
568
+ parameters: [
569
+ {
570
+ name: "contactId",
571
+ in: "path",
572
+ required: true,
573
+ schema: { type: "string" }
574
+ }
575
+ ],
576
+ responses: {
577
+ 200: {
578
+ description: "Opened chat"
579
+ }
580
+ }
581
+ }
582
+ },
583
+ "/api/chats/{chatId}/messages": {
584
+ get: {
585
+ tags: ["Messages"],
586
+ summary: "List messages for a chat",
587
+ security: [{ bearerAuth: [] }, { apiKeyAuth: [] }],
588
+ parameters: [
589
+ {
590
+ name: "chatId",
591
+ in: "path",
592
+ required: true,
593
+ schema: { type: "string" }
594
+ },
595
+ {
596
+ name: "take",
597
+ in: "query",
598
+ required: false,
599
+ schema: { type: "integer" }
600
+ },
601
+ {
602
+ name: "before",
603
+ in: "query",
604
+ required: false,
605
+ schema: { type: "string", format: "date-time" }
606
+ },
607
+ {
608
+ name: "search",
609
+ in: "query",
610
+ required: false,
611
+ schema: { type: "string" }
612
+ }
613
+ ],
614
+ responses: {
615
+ 200: {
616
+ description: "Messages payload"
617
+ }
618
+ }
619
+ }
620
+ },
621
+ "/api/chats/{chatId}/messages/send": {
622
+ post: {
623
+ tags: ["Messages"],
624
+ summary: "Send a message over HTTP",
625
+ description: "Preferred for AI agents and external clients that do not use Socket.IO. Supports text messages directly and media messages via an uploaded `mediaFileId`.",
626
+ security: [{ bearerAuth: [] }, { apiKeyAuth: [] }],
627
+ parameters: [
628
+ {
629
+ name: "chatId",
630
+ in: "path",
631
+ required: true,
632
+ schema: { type: "string" }
633
+ }
634
+ ],
635
+ requestBody: {
636
+ required: true,
637
+ content: {
638
+ "application/json": {
639
+ schema: {
640
+ type: "object",
641
+ properties: {
642
+ body: { type: "string" },
643
+ type: { type: "string", enum: ["text", "image", "video", "audio", "document", "sticker"] },
644
+ mediaFileId: { type: "string" },
645
+ replyToId: { type: "string" }
646
+ }
647
+ },
648
+ examples: {
649
+ textMessage: {
650
+ summary: "Send a text message",
651
+ value: {
652
+ body: "Halo dari agent",
653
+ type: "text"
654
+ }
655
+ }
656
+ }
657
+ }
658
+ }
659
+ },
660
+ responses: {
661
+ 200: {
662
+ description: "Sent message payload"
663
+ }
664
+ }
665
+ }
666
+ },
667
+ "/api/messages/{messageId}": {
668
+ delete: {
669
+ tags: ["Messages"],
670
+ summary: "Delete a message",
671
+ security: [{ bearerAuth: [] }, { apiKeyAuth: [] }],
672
+ parameters: [
673
+ {
674
+ name: "messageId",
675
+ in: "path",
676
+ required: true,
677
+ schema: { type: "string" }
678
+ }
679
+ ],
680
+ responses: {
681
+ 200: {
682
+ description: "Delete result"
683
+ }
684
+ }
685
+ }
686
+ },
687
+ "/api/messages/{messageId}/forward": {
688
+ post: {
689
+ tags: ["Messages"],
690
+ summary: "Forward a message",
691
+ security: [{ bearerAuth: [] }, { apiKeyAuth: [] }],
692
+ parameters: [
693
+ {
694
+ name: "messageId",
695
+ in: "path",
696
+ required: true,
697
+ schema: { type: "string" }
698
+ }
699
+ ],
700
+ requestBody: {
701
+ required: true,
702
+ content: {
703
+ "application/json": {
704
+ schema: {
705
+ type: "object",
706
+ properties: {
707
+ targetChatId: { type: "string" }
708
+ },
709
+ required: ["targetChatId"]
710
+ }
711
+ }
712
+ }
713
+ },
714
+ responses: {
715
+ 200: {
716
+ description: "Forward result"
717
+ }
718
+ }
719
+ }
720
+ },
721
+ "/api/media": {
722
+ post: {
723
+ tags: ["Media"],
724
+ summary: "Upload media",
725
+ security: [{ bearerAuth: [] }, { apiKeyAuth: [] }],
726
+ requestBody: {
727
+ required: true,
728
+ content: {
729
+ "multipart/form-data": {
730
+ schema: {
731
+ type: "object",
732
+ properties: {
733
+ file: {
734
+ type: "string",
735
+ format: "binary"
736
+ }
737
+ },
738
+ required: ["file"]
739
+ }
740
+ }
741
+ }
742
+ },
743
+ responses: {
744
+ 201: {
745
+ description: "Uploaded media metadata"
746
+ }
747
+ }
748
+ }
749
+ }
750
+ }
751
+ };
752
+ }
753
+
754
+ function createAgentReadme(config) {
755
+ return `# OpenWA Agent Guide
756
+
757
+ Recommended base URL for agents:
758
+
759
+ - ${config.frontendUrl}
760
+
761
+ Use the frontend URL because docs and runtime metadata endpoints are already proxied to the backend.
762
+
763
+ ## Authentication
764
+
765
+ Use either of these headers:
766
+
767
+ \`\`\`
768
+ X-API-Key: <api-key>
769
+ \`\`\`
770
+
771
+ or
772
+
773
+ \`\`\`
774
+ Authorization: Bearer <api-key>
775
+ \`\`\`
776
+
777
+ Agents do **not** need to log in through dashboard auth endpoints as long as they already have an API key.
778
+
779
+ ## Quick start
780
+
781
+ 1. Check runtime availability:
782
+ - \`GET /health\`
783
+ - \`GET /version\`
784
+ 2. Fetch the machine-readable specification:
785
+ - \`GET /docs/json\`
786
+ 3. Read chats and contacts:
787
+ - \`GET /api/chats\`
788
+ - \`GET /api/contacts\`
789
+ 4. Open or create a chat from a contact:
790
+ - \`POST /api/contacts/:contactId/open\`
791
+ 5. Read or search messages:
792
+ - \`GET /api/chats/:chatId/messages\`
793
+ - \`GET /api/chats/:chatId/messages?search=keyword\`
794
+ 6. Send a message:
795
+ - \`POST /api/chats/:chatId/messages/send\`
796
+
797
+ ## Important notes
798
+
799
+ - \`/api/auth/register\` and \`/api/auth/login\` are meant for dashboard or human login flows, not the normal agent flow.
800
+ - API keys are created from the OpenWA dashboard under **Settings → API Access**.
801
+ - For media messages, upload the file to \`POST /api/media\` first, then send the returned \`mediaFileId\` through the HTTP send message endpoint.
802
+ - Main business endpoints accept JWT **or** API key authentication, but API key management endpoints only accept dashboard JWT authentication.
803
+ `;
804
+ }
805
+
806
+ function createSwaggerHtml() {
807
+ return `<!DOCTYPE html>
808
+ <html lang="en">
809
+ <head>
810
+ <meta charset="utf-8" />
811
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
812
+ <title>OpenWA API Docs</title>
813
+ <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css" />
814
+ <style>
815
+ html, body {
816
+ margin: 0;
817
+ padding: 0;
818
+ background: #f4f5f7;
819
+ color: #1f2937;
820
+ }
821
+
822
+ #swagger-ui {
823
+ min-height: 100vh;
824
+ }
825
+
826
+ .swagger-ui .topbar {
827
+ display: none;
828
+ }
829
+ </style>
830
+ </head>
831
+ <body>
832
+ <div id="swagger-ui"></div>
833
+ <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
834
+ <script>
835
+ window.ui = SwaggerUIBundle({
836
+ url: "/docs/json",
837
+ dom_id: "#swagger-ui",
838
+ deepLinking: true,
839
+ displayRequestDuration: true,
840
+ presets: [SwaggerUIBundle.presets.apis]
841
+ });
842
+ </script>
843
+ </body>
844
+ </html>`;
845
+ }
846
+
847
+ module.exports = {
848
+ createOpenApiDocument,
849
+ createAgentReadme,
850
+ createSwaggerHtml,
851
+ packageVersion: packageJson.version,
852
+ packageName: packageJson.name
853
+ };