@wuapidev/sdk 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 (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +355 -0
  3. package/dist/client.d.ts +50 -0
  4. package/dist/client.d.ts.map +1 -0
  5. package/dist/client.js +80 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/core.d.ts +58 -0
  8. package/dist/core.d.ts.map +1 -0
  9. package/dist/core.js +191 -0
  10. package/dist/core.js.map +1 -0
  11. package/dist/errors.d.ts +26 -0
  12. package/dist/errors.d.ts.map +1 -0
  13. package/dist/errors.js +29 -0
  14. package/dist/errors.js.map +1 -0
  15. package/dist/index.d.ts +17 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +8 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/pagination.d.ts +23 -0
  20. package/dist/pagination.d.ts.map +1 -0
  21. package/dist/pagination.js +53 -0
  22. package/dist/pagination.js.map +1 -0
  23. package/dist/resources/accounts.d.ts +76 -0
  24. package/dist/resources/accounts.d.ts.map +1 -0
  25. package/dist/resources/accounts.js +139 -0
  26. package/dist/resources/accounts.js.map +1 -0
  27. package/dist/resources/base.d.ts +18 -0
  28. package/dist/resources/base.d.ts.map +1 -0
  29. package/dist/resources/base.js +34 -0
  30. package/dist/resources/base.js.map +1 -0
  31. package/dist/resources/channels.d.ts +22 -0
  32. package/dist/resources/channels.d.ts.map +1 -0
  33. package/dist/resources/channels.js +45 -0
  34. package/dist/resources/channels.js.map +1 -0
  35. package/dist/resources/chats.d.ts +35 -0
  36. package/dist/resources/chats.d.ts.map +1 -0
  37. package/dist/resources/chats.js +64 -0
  38. package/dist/resources/chats.js.map +1 -0
  39. package/dist/resources/contacts.d.ts +45 -0
  40. package/dist/resources/contacts.d.ts.map +1 -0
  41. package/dist/resources/contacts.js +79 -0
  42. package/dist/resources/contacts.js.map +1 -0
  43. package/dist/resources/groups.d.ts +36 -0
  44. package/dist/resources/groups.d.ts.map +1 -0
  45. package/dist/resources/groups.js +81 -0
  46. package/dist/resources/groups.js.map +1 -0
  47. package/dist/resources/messages.d.ts +36 -0
  48. package/dist/resources/messages.d.ts.map +1 -0
  49. package/dist/resources/messages.js +60 -0
  50. package/dist/resources/messages.js.map +1 -0
  51. package/dist/resources/misc.d.ts +21 -0
  52. package/dist/resources/misc.d.ts.map +1 -0
  53. package/dist/resources/misc.js +23 -0
  54. package/dist/resources/misc.js.map +1 -0
  55. package/dist/resources/projects.d.ts +65 -0
  56. package/dist/resources/projects.d.ts.map +1 -0
  57. package/dist/resources/projects.js +115 -0
  58. package/dist/resources/projects.js.map +1 -0
  59. package/dist/types.d.ts +1073 -0
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +46 -0
  62. package/dist/types.js.map +1 -0
  63. package/dist/webhook.d.ts +15 -0
  64. package/dist/webhook.d.ts.map +1 -0
  65. package/dist/webhook.js +77 -0
  66. package/dist/webhook.js.map +1 -0
  67. package/package.json +56 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 wuapi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,355 @@
1
+ # wuapi
2
+
3
+ TypeScript SDK for [wuapi](https://wuapi.dev), an unofficial WhatsApp API. Link your own WhatsApp numbers by QR code or pairing code, send and receive messages, manage chats, contacts, groups, communities and channels, split them into projects, and verify webhooks.
4
+
5
+ - Zero runtime dependencies. Uses the global `fetch` and WebCrypto.
6
+ - Runs on Node 18+, Bun, Deno and edge runtimes.
7
+ - ESM with TypeScript types that match the [OpenAPI spec](https://wuapi.dev/openapi.json).
8
+
9
+ > **Unofficial.** wuapi is not affiliated with, endorsed by or sponsored by WhatsApp or Meta, and it does not use the WhatsApp Business Platform (Cloud API). Numbers are linked as devices, the same way WhatsApp Web works. WhatsApp can restrict or ban numbers that behave like spam. You are responsible for your recipients' consent and for following WhatsApp's terms.
10
+
11
+ ## Install
12
+
13
+ ```sh
14
+ npm install @wuapidev/sdk
15
+ # or: pnpm add @wuapidev/sdk / yarn add @wuapidev/sdk / bun add @wuapidev/sdk
16
+ ```
17
+
18
+ Using a coding agent? Paste [wuapi.dev/llms-full.txt](https://wuapi.dev/llms-full.txt), the whole documentation as one Markdown file, or install the skills once with `npx skills add wuapi/skills`.
19
+
20
+ ## Quickstart: link a number and send a message
21
+
22
+ ```ts
23
+ import { Wuapi } from "@wuapidev/sdk";
24
+
25
+ const wuapi = new Wuapi({ apiKey: process.env.WUAPI_API_KEY });
26
+
27
+ // 1. Pick where the number's traffic exits: use the phone number's country.
28
+ const [location] = await wuapi.proxyLocations.list({ country: "VE" }).toArray(1);
29
+
30
+ // 2. Create an account. Your first one links with no card; sending starts with the free trial (402 before it).
31
+ const account = await wuapi.accounts.create({
32
+ name: "Support line",
33
+ proxyLocation: { country: location!.country, city: location!.city },
34
+ });
35
+
36
+ // 3. Wait for the QR code and show it to the phone owner.
37
+ // On the phone: WhatsApp > Linked devices > Link a device.
38
+ const withQr = await wuapi.accounts.waitForQrCode(account.id);
39
+ console.log("Scan this QR code:", withQr.qrCodeUrl); // PNG data URL, e.g. <img src={qrCodeUrl} />
40
+
41
+ // 4. Wait until the phone finishes linking. The QR code rotates while it
42
+ // waits; onQrCode is called with each new one.
43
+ const ready = await wuapi.accounts.waitUntilReady(account.id, {
44
+ onQrCode: (qrCodeUrl) => console.log("New QR code:", qrCodeUrl),
45
+ });
46
+ console.log("Linked", ready.phone);
47
+
48
+ // 5. Send a message.
49
+ const message = await wuapi.messages.send({
50
+ accountId: ready.id,
51
+ to: "+584241112233",
52
+ text: "Your order has shipped.",
53
+ });
54
+ console.log(message.id, message.status); // "queued"
55
+ ```
56
+
57
+ `proxyLocation` is required: every account connects through its own residential proxy, and `{ country, city }` says where it exits. `proxyLocations.list()` returns every supported pair (`country` is ISO 3166-1 alpha-2, `city` a lowercase slug); anything else answers `400 unsupported_proxy_location`. Search it with `q`, which ignores case and accents and returns the best match first: `proxyLocations.list({ q: "sao" })` starts with São Paulo.
58
+
59
+ `apiKey` falls back to the `WUAPI_API_KEY` environment variable, so `new Wuapi()` works when it is set.
60
+
61
+ A send returns the message with `status: "queued"`. The outcome arrives as the `message.sent` or `message.failed` webhook, or by calling `wuapi.messages.get(id)`. A recipient without WhatsApp fails with `error.code: "not_on_whatsapp"`.
62
+
63
+ Every resource carries `object` (`"account"`, `"message"`, ...). Contacts are E.164 (`+584241112233`), or `lid:<digits>` when WhatsApp hides the number; groups are `…@g.us`, channels `…@newsletter`.
64
+
65
+ ## Link by pairing code instead of QR
66
+
67
+ When the phone owner cannot scan a screen, link by phone number. They type an 8-character code in WhatsApp > Linked devices > Link a device > Link with phone number instead.
68
+
69
+ ```ts
70
+ // Either create the account with pairingPhone...
71
+ const account = await wuapi.accounts.create({
72
+ name: "Field phone",
73
+ proxyLocation: { country: "VE", city: "caracas" },
74
+ pairingPhone: "+584121234567",
75
+ });
76
+ const { pairingCode } = await wuapi.accounts.waitForPairingCode(account.id);
77
+ console.log("Type this on the phone:", pairingCode); // "ABCD-1234"
78
+
79
+ // ...or ask for a code for an account that is not linked yet.
80
+ const { code, expiresAt } = await wuapi.accounts.createPairingCode(account.id, { phone: "+584121234567" });
81
+
82
+ // Codes expire after about 160 seconds; waitUntilReady reports each new one.
83
+ await wuapi.accounts.waitUntilReady(account.id, { onPairingCode: (c) => console.log("New code:", c) });
84
+ ```
85
+
86
+ `createPairingCode` answers `409 already_linked` for an account that is already `ready`.
87
+
88
+ ## Sending other types
89
+
90
+ ```ts
91
+ await wuapi.messages.send({
92
+ accountId,
93
+ to: "+584241112233",
94
+ type: "document",
95
+ media: { url: "https://example.com/invoice.pdf", filename: "invoice.pdf" },
96
+ text: "Your invoice",
97
+ });
98
+
99
+ await wuapi.messages.send({
100
+ accountId,
101
+ to: "+584241112233",
102
+ type: "location",
103
+ location: { latitude: 10.4806, longitude: -66.9036, name: "Store" },
104
+ });
105
+
106
+ // Reply to (quote) a message in the same chat.
107
+ await wuapi.messages.send({ accountId, to: "+584241112233", text: "Tomorrow.", replyToMessageId: "msg_..." });
108
+
109
+ // Voice note (send ogg/opus; nothing is transcoded).
110
+ await wuapi.messages.send({
111
+ accountId,
112
+ to: "+584241112233",
113
+ type: "voice",
114
+ media: { url: "https://example.com/note.ogg", mimeType: "audio/ogg; codecs=opus" },
115
+ });
116
+
117
+ // Poll, then vote in it and read the tally from the returned message.
118
+ const poll = await wuapi.messages.send({
119
+ accountId,
120
+ to: "120363041234567890@g.us",
121
+ type: "poll",
122
+ poll: { name: "Lunch?", options: ["Pizza", "Sushi"], selectableCount: 1 },
123
+ });
124
+ await wuapi.messages.vote(poll.id, ["Sushi"]);
125
+
126
+ // Edit, star, react to and delete what you sent.
127
+ await wuapi.messages.edit(message.id, "Your order has shipped. Tracking: 1Z999.");
128
+ await wuapi.messages.star(message.id);
129
+ await wuapi.messages.react(message.id, "\u{1F44D}");
130
+ await wuapi.messages.delete(message.id);
131
+
132
+ // Send to a group, or post to a channel you administer, by its id.
133
+ await wuapi.messages.send({ accountId, to: "120363041234567890@g.us", text: "Hello group" });
134
+ await wuapi.messages.send({ accountId, to: "120363198765432101@newsletter", text: "Version 2.4 is out." });
135
+ ```
136
+
137
+ Send types: `text`, `image`, `video`, `audio`, `voice` (an audio sent as a voice note), `document`, `sticker`, `location`, `contact`, `contacts`, `poll` and `calendar_event`. Any send also takes `mentions`, `mentionAll` (groups), `forwarded`, `viewOnce`, `disappearingSeconds`, `linkPreview` (text) and `media.gifPlayback`. A channel takes `text`, `image`, `video` and `document`.
138
+
139
+ Everything else works on the account: chats, contacts, the profile, privacy, stories, groups and communities, channels, labels and calls. Each method takes the `accountId` first, and the account must be `ready`. Opposites are two methods: `archive` / `unarchive`, `pin` / `unpin`, `mute` / `unmute`, `block` / `unblock`, `follow` / `unfollow`, `star` / `unstar`.
140
+
141
+ ```ts
142
+ await wuapi.chats.sendReadReceipts(accountId, "+584241112233"); // blue ticks
143
+ await wuapi.chats.archive(accountId, "+584241112233");
144
+ const [check] = await wuapi.contacts.check(accountId, ["+584241112233"]);
145
+ await wuapi.stories.create(accountId, { text: "Open until 18:00", backgroundColor: "#0F766E" });
146
+ await wuapi.groups.create(accountId, { name: "Customers", community: true });
147
+ await wuapi.accounts.update(accountId, { rejectCalls: true, rejectCallsMessage: "Please write to us." });
148
+ ```
149
+
150
+ ## Pacing
151
+
152
+ Each number sends at most 12 messages a minute, 5 a minute to people who never wrote to it, and shows "typing..." for 800 to 6000 ms first. A paced message waits in the queue up to 60 minutes. Those are defaults, tunable per account:
153
+
154
+ ```ts
155
+ const acc = await wuapi.accounts.update(accountId, {
156
+ pacing: { messagesPerMinute: 20, typing: { maxMs: 4000 }, queueTimeoutMinutes: 120 },
157
+ });
158
+ acc.pacing; // effective values, with custom: true
159
+ await wuapi.accounts.update(accountId, { pacing: null }); // back to the defaults
160
+ ```
161
+
162
+ Ranges: `messagesPerMinute` 1 to 30, `firstContactPerMinute` 1 to `messagesPerMinute`, `typing.minMs` 0 to 10000, `typing.maxMs` `minMs` to 20000, `typing.charsPerSecond` 5 to 100, `queueTimeoutMinutes` 1 to 1440. Faster pacing raises the chance WhatsApp restricts the number.
163
+
164
+ ## Lists and pagination
165
+
166
+ List methods return a `Paginator`. Iterate it to walk every item across pages, or ask for one page:
167
+
168
+ ```ts
169
+ for await (const message of wuapi.messages.list({ accountId, direction: "inbound" })) {
170
+ console.log(message.from, message.text);
171
+ }
172
+
173
+ const { items, nextCursor } = await wuapi.messages.list({ limit: 20 }).page();
174
+ const next = await wuapi.messages.list({ limit: 20 }).page(nextCursor ?? undefined);
175
+ ```
176
+
177
+ Every list has the same shape, `{ object: "list", items, nextCursor }`, including lists read live from WhatsApp (`groups.list`, `channels.list`, `contacts.listBlocked`, `channels.listMessages`, ...). Batch actions (`contacts.check`, `groups.addParticipants`, ...) return arrays.
178
+
179
+ ## Webhooks
180
+
181
+ Create an endpoint once and store the secret; it is returned only on creation.
182
+
183
+ ```ts
184
+ const endpoint = await wuapi.webhookEndpoints.create({
185
+ url: "https://example.com/webhooks/wuapi",
186
+ events: ["message.received", "message.failed", "account.disconnected"],
187
+ });
188
+ console.log(endpoint.secret); // whsec_...
189
+ ```
190
+
191
+ Verify each request with the raw body, before parsing it as JSON:
192
+
193
+ ```ts
194
+ import { verifyWebhook, WebhookVerificationError } from "@wuapidev/sdk";
195
+
196
+ export async function POST(request: Request) {
197
+ const rawBody = await request.text();
198
+ try {
199
+ const event = await verifyWebhook(
200
+ rawBody,
201
+ request.headers.get("wuapi-signature"),
202
+ process.env.WUAPI_WEBHOOK_SECRET!,
203
+ );
204
+ switch (event.type) {
205
+ case "message.received":
206
+ console.log(event.data.object.from, event.data.object.text);
207
+ break;
208
+ case "account.disconnected":
209
+ console.log(event.data.object.id, event.data.object.disconnectReason);
210
+ break;
211
+ }
212
+ return new Response(null, { status: 204 });
213
+ } catch (err) {
214
+ if (err instanceof WebhookVerificationError) return new Response("invalid signature", { status: 400 });
215
+ throw err;
216
+ }
217
+ }
218
+ ```
219
+
220
+ `verifyWebhook` checks the `Wuapi-Signature` header (`t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<rawBody>">`) in constant time and rejects timestamps more than 300 seconds away (pass a fourth argument to change it). Deliveries can repeat: deduplicate on `event.id`.
221
+
222
+ Every event has the same envelope: `{ id, object: "event", type, createdAt, organizationId, projectId, data: { object } }`. `data.object` is the resource in its REST shape (an `account`, a `message`, a `group`, ...) or the event's own object (`poll_vote`, `call`, `chat_change`, ...). `message.edited` and `invitation.status_changed` add `data.previousAttributes`. `WEBHOOK_EVENT_TYPES` lists every type.
223
+
224
+ ## Projects
225
+
226
+ Three levels: your **organization** pays; a **project** is one of your customers, or an environment, with its own accounts, API keys, webhooks, limits and usage, isolated from every other project; an **account** is a linked WhatsApp number with a name, like "Sales" or "Support".
227
+
228
+ ```ts
229
+ // Organization key: create the project with your own id for the customer.
230
+ const project = await wuapi.projects.create({ name: "Northwind Dental", externalId: "customer_8812", maxAccounts: 3 });
231
+
232
+ // Act inside it: every request carries the Wuapi-Project header.
233
+ const northwind = wuapi.withProject("ext:customer_8812"); // or project.id
234
+ await northwind.accounts.list().page(); // only Northwind's accounts
235
+ await northwind.webhookEndpoints.create({ url: "https://example.com/hooks/northwind", events: ["message.received"] });
236
+
237
+ // Or hand the customer a key that can only reach their project.
238
+ const { key } = await wuapi.projects.apiKeys.create(project.id, { name: "Northwind production" });
239
+ const theirs = new Wuapi({ apiKey: key! });
240
+ ```
241
+
242
+ `new Wuapi({ project })` does the same as `withProject` from the start. A resource outside the scope answers `404 not_found`; a project key naming another project answers `403 forbidden`. Every webhook payload carries `organizationId` and `projectId`. An organization endpoint receives every project's events; a project endpoint receives only its own.
243
+
244
+ ### Invitations
245
+
246
+ Instead of building a QR screen, send someone else, your customer, a store manager or a sales rep, an invitation: a page on wuapi with your name, logo and color where they link their own number. They need no account or key.
247
+
248
+ ```ts
249
+ await wuapi.branding.update({ displayName: "Northwind Cloud", accentColor: "#0F766E" });
250
+ // hideWuapiBranding: true also needs the White label add-on; without it: 402 addon_required.
251
+
252
+ const invitation = await wuapi.invitations.create({
253
+ projectId: "ext:customer_8812",
254
+ inviteeName: "Maria Perez",
255
+ inviteeEmail: "maria@example.com",
256
+ accountName: "Front desk",
257
+ suggestedCountry: "MX",
258
+ returnUrl: "https://app.example.com/settings/whatsapp",
259
+ metadata: { store: "cdmx-2" },
260
+ });
261
+ if (!invitation.emailSentAt) console.log(invitation.url); // send it yourself; it is returned only by create and resend
262
+ ```
263
+
264
+ Pass `proxyLocation` to preset where the account's proxy exits; without it the invitee picks the country and city on the page (`invitation.proxyLocation` is `null` until then). They then scans a live QR or types a pairing code. The account is created in the project when they start and named `accountName`; `metadata` is copied onto it when the number is linked. Each step fires `invitation.status_changed`, with the invitation as `data.object` and `data.previousAttributes.status`: `pending` → `in_progress` → `completed`, or `failed` (the link keeps working and a retry reuses the account). `expired` is computed on read, from `expiresInDays` (1 to 30, default 7).
265
+
266
+ ```ts
267
+ for await (const inv of wuapi.invitations.list({ status: "failed" })) console.log(inv.id, inv.failureReason);
268
+ await wuapi.invitations.resend(invitation.id); // new url, the old one stops working, emailed again
269
+ await wuapi.invitations.cancel(invitation.id); // 409 already_completed once completed
270
+ ```
271
+
272
+ ### Suspension, limits and usage
273
+
274
+ ```ts
275
+ await wuapi.projects.update("ext:customer_8812", { status: "suspended" }); // sends and writes answer 403 project_suspended; inbound keeps arriving
276
+ await wuapi.projects.update("ext:customer_8812", { status: "active" });
277
+
278
+ const report = await wuapi.usage.byProject({ month: "2026-09" }); // one line per project, for rebilling
279
+ for (const line of report.projects) console.log(line.externalId, line.billableAccountCount, line.sentMessageCount);
280
+ ```
281
+
282
+ wuapi bills the organization across all its projects; `usage.byProject` is what you rebill from. `usage.get()` is the organization's own bill this month: every billable account includes 0.5 GB of proxy, pooled, so `proxyBytes` is everything used, `includedProxyBytes` the pool, and `proxyFeeCents` bills only `billableProxyBytes`, the traffic past it, at $3 per GB.
283
+
284
+ ## Errors and retries
285
+
286
+ Every non-2xx response throws a `WuapiError` with `status`, `code`, `message`, `details` and `requestId` (when the server sends `x-request-id`).
287
+
288
+ ```ts
289
+ import { WuapiError } from "@wuapidev/sdk";
290
+
291
+ try {
292
+ await wuapi.messages.send({ accountId, to: "+584241112233", text: "hi" });
293
+ } catch (err) {
294
+ if (err instanceof WuapiError && err.code === "account_not_ready") {
295
+ await wuapi.accounts.reconnect(accountId);
296
+ }
297
+ }
298
+ ```
299
+
300
+ The client retries up to `maxRetries` times (default 2):
301
+
302
+ - `429 rate_limited`, waiting for `Retry-After`.
303
+ - 5xx responses, network errors and timeouts, with exponential backoff.
304
+
305
+ Every `POST` carries an `Idempotency-Key` header, generated per call when you do not pass one, so a retried send or create is answered with the first response (`Idempotent-Replayed: true`) instead of running twice. Pass your own key to make retries across processes safe too:
306
+
307
+ ```ts
308
+ await wuapi.messages.send({ accountId, to: "+584241112233", text: "Shipped." }, { idempotencyKey: "order-A-1042-shipped" });
309
+ ```
310
+
311
+ Every method takes this options object last: `{ idempotencyKey?, signal? }`.
312
+
313
+ ## Options
314
+
315
+ ```ts
316
+ new Wuapi({
317
+ apiKey: "wu_live_...", // or WUAPI_API_KEY
318
+ baseUrl: "https://api.wuapi.dev", // default
319
+ timeoutMs: 30_000, // per attempt
320
+ maxRetries: 2,
321
+ fetch: customFetch, // optional
322
+ project: "ext:customer_8812", // optional: sends Wuapi-Project on every request
323
+ });
324
+ ```
325
+
326
+ ## Reference
327
+
328
+ | Resource | Methods |
329
+ |---|---|
330
+ | `proxyLocations` | `list` |
331
+ | `accounts` | `list`, `create`, `get`, `update`, `delete`, `reconnect`, `logout`, `createPairingCode`, `setPresence`, `setDefaultDisappearingTimer`, `waitForQrCode`, `waitForPairingCode`, `waitUntilReady` |
332
+ | `messages` | `send`, `list`, `get`, `edit`, `delete`, `react`, `vote`, `star`, `unstar`, `addLabel`, `removeLabel` |
333
+ | `chats` | `sendPresence`, `sendReadReceipts`, `markRead`, `markUnread`, `archive`, `unarchive`, `pin`, `unpin`, `mute`, `unmute`, `delete`, `setDisappearingTimer`, `addLabel`, `removeLabel` |
334
+ | `stories` | `create` |
335
+ | `contacts` | `check`, `lookup`, `getPicture`, `getBusinessProfile`, `subscribePresence`, `block`, `unblock`, `listBlocked`, `getLink`, `resetLink`, `resolveLink` |
336
+ | `bots` | `list` |
337
+ | `profile` | `update`, `setPicture`, `deletePicture` |
338
+ | `privacy` | `get`, `update`, `getStoryPrivacy` |
339
+ | `labels` | `upsert`, `delete` |
340
+ | `calls` | `reject` |
341
+ | `stickerPacks`, `orders` | `get` |
342
+ | `groups` | `list`, `create`, `get`, `update`, `leave`, `addParticipants`, `removeParticipants`, `promoteParticipants`, `demoteParticipants`, `getInviteLink`, `resetInviteLink`, `join`, `getInvite`, `setPicture`, `deletePicture`, `listJoinRequests`, `approveJoinRequests`, `rejectJoinRequests`, `listSubgroups`, `linkSubgroup`, `unlinkSubgroup`, `listCommunityParticipants` |
343
+ | `channels` | `list`, `create`, `get`, `getInvite`, `follow`, `unfollow`, `mute`, `unmute`, `listMessages`, `react`, `markViewed` |
344
+ | `webhookEndpoints` | `list`, `create`, `get`, `update`, `delete`, `rotateSecret` |
345
+ | `projects` | `list`, `create`, `get`, `update`, `delete`, `getUsage`, `apiKeys.list`, `apiKeys.create`, `apiKeys.revoke` |
346
+ | `invitations` | `create`, `list`, `get`, `cancel`, `resend` |
347
+ | `branding` | `get`, `update` |
348
+ | `usage` | `get`, `byProject` |
349
+ | client | `me()`, `withProject(project)`, `project` |
350
+
351
+ Account-level resources take the `accountId` first. Full API reference: [wuapi.dev/docs](https://wuapi.dev/docs) and [openapi.json](https://wuapi.dev/openapi.json).
352
+
353
+ ## License
354
+
355
+ MIT
@@ -0,0 +1,50 @@
1
+ import { type ClientOptions } from "./core.js";
2
+ import { Accounts, ProxyLocations } from "./resources/accounts.js";
3
+ import { Channels } from "./resources/channels.js";
4
+ import { Chats, Labels } from "./resources/chats.js";
5
+ import { Bots, Contacts, Privacy, Profile } from "./resources/contacts.js";
6
+ import { Groups } from "./resources/groups.js";
7
+ import { Messages, Stories } from "./resources/messages.js";
8
+ import { Calls, Orders, StickerPacks } from "./resources/misc.js";
9
+ import { BrandingResource, Invitations, Projects, UsageResource, WebhookEndpoints } from "./resources/projects.js";
10
+ import type { AuthContext, CallOptions } from "./types.js";
11
+ export declare class Wuapi {
12
+ #private;
13
+ readonly accounts: Accounts;
14
+ readonly proxyLocations: ProxyLocations;
15
+ readonly messages: Messages;
16
+ readonly chats: Chats;
17
+ readonly stories: Stories;
18
+ readonly contacts: Contacts;
19
+ readonly bots: Bots;
20
+ readonly profile: Profile;
21
+ readonly privacy: Privacy;
22
+ readonly labels: Labels;
23
+ readonly calls: Calls;
24
+ readonly stickerPacks: StickerPacks;
25
+ readonly orders: Orders;
26
+ readonly groups: Groups;
27
+ readonly channels: Channels;
28
+ readonly webhookEndpoints: WebhookEndpoints;
29
+ readonly projects: Projects;
30
+ readonly invitations: Invitations;
31
+ readonly branding: BrandingResource;
32
+ readonly usage: UsageResource;
33
+ constructor(options?: ClientOptions);
34
+ get baseUrl(): string;
35
+ /** The project this client is scoped to (`Wuapi-Project`), if any. */
36
+ get project(): string | undefined;
37
+ /**
38
+ * A client scoped to one project (its id or `ext:<externalId>`), sharing this
39
+ * client's key and configuration. Every request carries `Wuapi-Project`.
40
+ *
41
+ * ```ts
42
+ * const northwind = wuapi.withProject("ext:customer_8812");
43
+ * await northwind.accounts.create({ proxyLocation: { country: "US", city: "newyorkcity" }, name: "Front desk" }); // lands in that project
44
+ * ```
45
+ */
46
+ withProject(project: string): Wuapi;
47
+ /** The organization, API key and project scope behind the current token. */
48
+ me(options?: CallOptions): Promise<AuthContext>;
49
+ }
50
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAC3E,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AACnH,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE3D,qBAAa,KAAK;;IAChB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;gBAGlB,OAAO,GAAE,aAAkB;IAyBvC,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,sEAAsE;IACtE,IAAI,OAAO,IAAI,MAAM,GAAG,SAAS,CAEhC;IAED;;;;;;;;OAQG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK;IAInC,4EAA4E;IAC5E,EAAE,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;CAGhD"}
package/dist/client.js ADDED
@@ -0,0 +1,80 @@
1
+ import { callOpts, HttpClient } from "./core.js";
2
+ import { Accounts, ProxyLocations } from "./resources/accounts.js";
3
+ import { Channels } from "./resources/channels.js";
4
+ import { Chats, Labels } from "./resources/chats.js";
5
+ import { Bots, Contacts, Privacy, Profile } from "./resources/contacts.js";
6
+ import { Groups } from "./resources/groups.js";
7
+ import { Messages, Stories } from "./resources/messages.js";
8
+ import { Calls, Orders, StickerPacks } from "./resources/misc.js";
9
+ import { BrandingResource, Invitations, Projects, UsageResource, WebhookEndpoints } from "./resources/projects.js";
10
+ export class Wuapi {
11
+ accounts;
12
+ proxyLocations;
13
+ messages;
14
+ chats;
15
+ stories;
16
+ contacts;
17
+ bots;
18
+ profile;
19
+ privacy;
20
+ labels;
21
+ calls;
22
+ stickerPacks;
23
+ orders;
24
+ groups;
25
+ channels;
26
+ webhookEndpoints;
27
+ projects;
28
+ invitations;
29
+ branding;
30
+ usage;
31
+ #http;
32
+ constructor(options = {}) {
33
+ const http = options instanceof HttpClient ? options : new HttpClient(options);
34
+ this.#http = http;
35
+ this.accounts = new Accounts(http);
36
+ this.proxyLocations = new ProxyLocations(http);
37
+ this.messages = new Messages(http);
38
+ this.chats = new Chats(http);
39
+ this.stories = new Stories(http);
40
+ this.contacts = new Contacts(http);
41
+ this.bots = new Bots(http);
42
+ this.profile = new Profile(http);
43
+ this.privacy = new Privacy(http);
44
+ this.labels = new Labels(http);
45
+ this.calls = new Calls(http);
46
+ this.stickerPacks = new StickerPacks(http);
47
+ this.orders = new Orders(http);
48
+ this.groups = new Groups(http);
49
+ this.channels = new Channels(http);
50
+ this.webhookEndpoints = new WebhookEndpoints(http);
51
+ this.projects = new Projects(http);
52
+ this.invitations = new Invitations(http);
53
+ this.branding = new BrandingResource(http);
54
+ this.usage = new UsageResource(http);
55
+ }
56
+ get baseUrl() {
57
+ return this.#http.baseUrl;
58
+ }
59
+ /** The project this client is scoped to (`Wuapi-Project`), if any. */
60
+ get project() {
61
+ return this.#http.project;
62
+ }
63
+ /**
64
+ * A client scoped to one project (its id or `ext:<externalId>`), sharing this
65
+ * client's key and configuration. Every request carries `Wuapi-Project`.
66
+ *
67
+ * ```ts
68
+ * const northwind = wuapi.withProject("ext:customer_8812");
69
+ * await northwind.accounts.create({ proxyLocation: { country: "US", city: "newyorkcity" }, name: "Front desk" }); // lands in that project
70
+ * ```
71
+ */
72
+ withProject(project) {
73
+ return new Wuapi(this.#http.withProject(project));
74
+ }
75
+ /** The organization, API key and project scope behind the current token. */
76
+ me(options) {
77
+ return this.#http.request({ method: "GET", path: "/v1/me", ...callOpts(options) });
78
+ }
79
+ }
80
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/core.d.ts ADDED
@@ -0,0 +1,58 @@
1
+ import type { CallOptions } from "./types.js";
2
+ export declare const VERSION = "0.1.0";
3
+ export declare const DEFAULT_BASE_URL = "https://api.wuapi.dev";
4
+ export type FetchLike = (input: string, init: RequestInit) => Promise<Response>;
5
+ export interface ClientOptions {
6
+ /** API key (`wu_live_...`). Falls back to the `WUAPI_API_KEY` environment variable. */
7
+ apiKey?: string;
8
+ /** Defaults to https://api.wuapi.dev. */
9
+ baseUrl?: string;
10
+ /** Per-attempt timeout in milliseconds. Defaults to 30000. */
11
+ timeoutMs?: number;
12
+ /** Retries after the first attempt. Defaults to 2. */
13
+ maxRetries?: number;
14
+ /** Custom fetch implementation. Defaults to the global `fetch`. */
15
+ fetch?: FetchLike;
16
+ /**
17
+ * Act inside one project: its id, or `ext:<externalId>`. Sent as the
18
+ * `Wuapi-Project` header on every request. With a project API key it may be
19
+ * omitted (the key already names its project).
20
+ */
21
+ project?: string;
22
+ }
23
+ export declare const PROJECT_HEADER = "Wuapi-Project";
24
+ export type Query = Record<string, string | number | boolean | undefined | null>;
25
+ export interface RequestOptions {
26
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
27
+ path: string;
28
+ query?: Query | undefined;
29
+ body?: unknown;
30
+ /**
31
+ * Sent as `Idempotency-Key`. Every POST gets a random one when this is
32
+ * omitted, so a retried POST is replayed by the server instead of repeated.
33
+ */
34
+ idempotencyKey?: string | undefined;
35
+ signal?: AbortSignal | undefined;
36
+ }
37
+ export declare const IDEMPOTENCY_HEADER = "Idempotency-Key";
38
+ export declare class HttpClient {
39
+ #private;
40
+ readonly baseUrl: string;
41
+ readonly timeoutMs: number;
42
+ readonly maxRetries: number;
43
+ /** The `Wuapi-Project` header value, when this client is scoped to a project. */
44
+ readonly project: string | undefined;
45
+ constructor(options?: ClientOptions);
46
+ /** A client with the same configuration, scoped to another project. */
47
+ withProject(project: string): HttpClient;
48
+ buildUrl(path: string, query?: Query): string;
49
+ request<T>(opts: RequestOptions): Promise<T>;
50
+ }
51
+ export declare const enc: typeof encodeURIComponent;
52
+ /** Spread into `RequestOptions`. */
53
+ export declare function callOpts(options: CallOptions | undefined): Pick<RequestOptions, "idempotencyKey" | "signal">;
54
+ /** A random idempotency key, so automatic retries never repeat a POST. */
55
+ export declare function randomKey(): string;
56
+ /** `/v1/accounts/{accountId}` plus a suffix, with every segment encoded. */
57
+ export declare function accountPath(accountId: string, suffix?: string): string;
58
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,eAAO,MAAM,OAAO,UAAU,CAAC;AAC/B,eAAO,MAAM,gBAAgB,0BAA0B,CAAC;AAExD,MAAM,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEhF,MAAM,WAAW,aAAa;IAC5B,uFAAuF;IACvF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,cAAc,kBAAkB,CAAC;AAE9C,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,IAAI,CAAC,CAAC;AAEjF,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;IAC1B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAClC;AAED,eAAO,MAAM,kBAAkB,oBAAoB,CAAC;AAqBpD,qBAAa,UAAU;;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,iFAAiF;IACjF,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;gBAKzB,OAAO,GAAE,aAAkB;IAmBvC,uEAAuE;IACvE,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU;IAKxC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM;IAavC,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;CAsGnD;AAED,eAAO,MAAM,GAAG,2BAAqB,CAAC;AAEtC,oCAAoC;AACpC,wBAAgB,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,GAAG,IAAI,CAAC,cAAc,EAAE,gBAAgB,GAAG,QAAQ,CAAC,CAE5G;AAED,0EAA0E;AAC1E,wBAAgB,SAAS,IAAI,MAAM,CAIlC;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,SAAK,GAAG,MAAM,CAElE"}