@wazapi/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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022-2026 Oryn Labs LLC
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,144 @@
1
+ # @wazapi/sdk
2
+
3
+ Official Node.js SDK for the [Wazapi](https://wazapi.io) Public API v1 — a
4
+ server-to-server client for sending WhatsApp messages, triggering flows, and
5
+ managing contacts/templates.
6
+
7
+ - Zero runtime dependencies (uses the native `fetch` on Node 18+).
8
+ - Fully typed against the OpenAPI contract (`GET /api/openapi/v1.json`).
9
+ - Automatic `Idempotency-Key` generation for write operations.
10
+ - Built-in polling helper for asynchronous operations.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ pnpm add @wazapi/sdk
16
+ ```
17
+
18
+ ## Quick start
19
+
20
+ ```ts
21
+ import { WazapiClient } from '@wazapi/sdk'
22
+
23
+ const wazapi = new WazapiClient({ token: process.env.WAZAPI_API_TOKEN! })
24
+
25
+ // Discover a WhatsApp channel
26
+ const channels = await wazapi.listChannels()
27
+ const channel = channels.find((c) => c.capabilities.send_template)
28
+ if (!channel) throw new Error('No channel able to send templates')
29
+
30
+ // Fire a template and wait for the result
31
+ const { operation } = await wazapi.sendTemplate(channel.uuid, '+5511999998888', {
32
+ name: 'order_confirmed',
33
+ parameters: ['Maria', '1847'],
34
+ })
35
+ const final = await wazapi.waitForOperation(operation.uuid)
36
+ console.log(final.status) // 'succeeded' | 'failed'
37
+ ```
38
+
39
+ ## Sending a template (recommended flow)
40
+
41
+ Templates must be **APPROVED** and you must send exactly the number of body
42
+ parameters the template declares. Discover that shape first instead of guessing:
43
+
44
+ ```ts
45
+ const template = await wazapi.getTemplate('order_confirmed')
46
+
47
+ // How many positional {{1}}..{{n}} the body expects:
48
+ const expected = template.variables.body_parameter_count
49
+
50
+ // Templates needing a media/variable header, named params, or dynamic buttons
51
+ // are not sendable through the API yet — check before building the payload:
52
+ const sendable =
53
+ !template.variables.header?.has_variable &&
54
+ template.variables.header?.format !== 'IMAGE' &&
55
+ !template.variables.has_dynamic_buttons
56
+
57
+ const { operation, replayed } = await wazapi.sendTemplate(
58
+ channel.uuid,
59
+ '+5511999998888',
60
+ { name: template.name, language: template.language, parameters: ['Maria', '1847'] },
61
+ // Optional: pass your own idempotency key (e.g. the order id) so retries
62
+ // never send twice. Omit it and the SDK generates a UUID per call.
63
+ 'order-1847-confirmation'
64
+ )
65
+ ```
66
+
67
+ The send is asynchronous: the API returns `202 Accepted` with an operation you
68
+ poll (or receive via webhook). `waitForOperation` handles the polling:
69
+
70
+ ```ts
71
+ const result = await wazapi.waitForOperation(operation.uuid, {
72
+ intervalMs: 1000,
73
+ timeoutMs: 60_000,
74
+ })
75
+ if (result.status === 'failed') {
76
+ console.error(result.error?.code, result.error?.message)
77
+ }
78
+ ```
79
+
80
+ ## Error handling
81
+
82
+ Every non-2xx response throws a `WazapiError` carrying the stable error envelope:
83
+
84
+ ```ts
85
+ import { WazapiError } from '@wazapi/sdk'
86
+
87
+ try {
88
+ await wazapi.sendTemplate(channel.uuid, '+5511999998888', {
89
+ name: 'order_confirmed',
90
+ parameters: ['Maria'], // wrong count
91
+ })
92
+ } catch (err) {
93
+ if (err instanceof WazapiError) {
94
+ console.error(err.code) // 'template_parameter_count_mismatch'
95
+ console.error(err.requestId) // quote this to support
96
+ if (err.isRateLimited) console.error('retry after', err.retryAfter, 's')
97
+ }
98
+ }
99
+ ```
100
+
101
+ Common template send error codes (all rejected synchronously, before an
102
+ operation is created):
103
+
104
+ | Code | Meaning |
105
+ | ----------------------------------- | ---------------------------------------------------- |
106
+ | `template_not_found` | No APPROVED template with that name. |
107
+ | `template_language_unavailable` | No approved version in the requested `language`. |
108
+ | `template_parameter_count_mismatch` | `parameters.length` ≠ `body_parameter_count`. |
109
+ | `template_format_unsupported` | Needs media/variable header, named params, buttons. |
110
+
111
+ ## Contacts
112
+
113
+ ```ts
114
+ // Upsert by your own system's id — idempotent, safe to call repeatedly
115
+ await wazapi.upsertContactByExternalId('customer-1847', {
116
+ phone: '+5511999998888',
117
+ name: 'Maria Silva',
118
+ custom_fields: { tier: 'gold' },
119
+ })
120
+ ```
121
+
122
+ ## Configuration
123
+
124
+ ```ts
125
+ new WazapiClient({
126
+ token: 'waz_api_...',
127
+ baseUrl: 'https://wazapi.io/api/v1', // default
128
+ timeoutMs: 30_000, // per-request timeout
129
+ idempotencyKeyFactory: () => myKey(), // default: crypto.randomUUID()
130
+ })
131
+ ```
132
+
133
+ ## API surface
134
+
135
+ - `listChannels()`
136
+ - `listContacts(params)`, `getContact(uuid)`, `updateContact(uuid, input)`, `upsertContactByExternalId(externalId, input)`
137
+ - `listTemplates(params)`, `getTemplate(name)`
138
+ - `listFlows(params)`, `getFlow(uuid)`, `executeFlow(flowUuid, input, idempotencyKey?)`
139
+ - `listConversations(params)`, `getConversation(uuid)`, `listMessages(conversationUuid, params)`
140
+ - `sendMessage(input, idempotencyKey?)`, `sendText(...)`, `sendTemplate(...)`
141
+ - `getOperation(uuid)`, `waitForOperation(uuid, options?)`
142
+
143
+ See the OpenAPI contract at `https://wazapi.io/api/openapi/v1.json` for the full
144
+ schema.
@@ -0,0 +1,69 @@
1
+ import type { AcceptedResult, Channel, Contact, ContactWrite, Conversation, ExecuteFlowInput, Flow, ListParams, Message, Operation, Paginated, SendMessageInput, Template } from './types.js';
2
+ export interface WazapiClientOptions {
3
+ /** Bearer token issued in Settings → Wazapi API (starts with `waz_api_`). */
4
+ token: string;
5
+ /** API base URL. Defaults to the production endpoint. */
6
+ baseUrl?: string;
7
+ /** Custom fetch implementation (defaults to global fetch). */
8
+ fetch?: typeof fetch;
9
+ /** Per-request timeout in milliseconds. Defaults to 30000. */
10
+ timeoutMs?: number;
11
+ /**
12
+ * Generates the Idempotency-Key for write operations when the caller does not
13
+ * pass one. Defaults to `crypto.randomUUID()`.
14
+ */
15
+ idempotencyKeyFactory?: () => string;
16
+ }
17
+ export interface WaitForOperationOptions {
18
+ /** Poll interval in milliseconds. Defaults to 1000. */
19
+ intervalMs?: number;
20
+ /** Max time to wait before throwing a timeout error. Defaults to 60000. */
21
+ timeoutMs?: number;
22
+ /**
23
+ * Treat `waiting` as terminal too (a flow that parked on user input). Defaults
24
+ * to false, so `waitForOperation` only resolves on `succeeded`/`failed`.
25
+ */
26
+ resolveOnWaiting?: boolean;
27
+ }
28
+ export declare class WazapiClient {
29
+ private readonly token;
30
+ private readonly baseUrl;
31
+ private readonly fetchImpl;
32
+ private readonly timeoutMs;
33
+ private readonly newIdempotencyKey;
34
+ constructor(options: WazapiClientOptions);
35
+ listChannels(): Promise<Channel[]>;
36
+ listContacts(params?: ListParams): Promise<Paginated<Contact>>;
37
+ getContact(uuid: string): Promise<Contact>;
38
+ updateContact(uuid: string, input: ContactWrite): Promise<Contact>;
39
+ upsertContactByExternalId(externalId: string, input: ContactWrite & {
40
+ phone: string;
41
+ }): Promise<Contact>;
42
+ listTemplates(params?: ListParams): Promise<Paginated<Template>>;
43
+ getTemplate(name: string): Promise<Template>;
44
+ listFlows(params?: ListParams): Promise<Paginated<Flow>>;
45
+ getFlow(uuid: string): Promise<Flow>;
46
+ executeFlow(flowUuid: string, input: ExecuteFlowInput, idempotencyKey?: string): Promise<AcceptedResult>;
47
+ listConversations(params?: ListParams): Promise<Paginated<Conversation>>;
48
+ getConversation(uuid: string): Promise<Conversation>;
49
+ listMessages(conversationUuid: string, params?: ListParams): Promise<Paginated<Message>>;
50
+ sendMessage(input: SendMessageInput, idempotencyKey?: string): Promise<AcceptedResult>;
51
+ /** Convenience wrapper for a plain text send. */
52
+ sendText(channelUuid: string, phone: string, text: string, idempotencyKey?: string): Promise<AcceptedResult>;
53
+ /** Convenience wrapper for a template send with positional body parameters. */
54
+ sendTemplate(channelUuid: string, phone: string, template: {
55
+ name: string;
56
+ language?: string;
57
+ parameters?: string[];
58
+ }, idempotencyKey?: string): Promise<AcceptedResult>;
59
+ getOperation(uuid: string): Promise<Operation>;
60
+ /**
61
+ * Polls an operation until it reaches a terminal status. Resolves with the final
62
+ * operation (which may be `failed` — inspect `operation.error`) or throws on timeout.
63
+ */
64
+ waitForOperation(uuid: string, options?: WaitForOperationOptions): Promise<Operation>;
65
+ private accept;
66
+ private request;
67
+ private rawRequest;
68
+ private withQuery;
69
+ }
package/dist/client.js ADDED
@@ -0,0 +1,196 @@
1
+ import { WazapiError } from './error.js';
2
+ const TERMINAL_STATUSES = ['succeeded', 'failed'];
3
+ export class WazapiClient {
4
+ token;
5
+ baseUrl;
6
+ fetchImpl;
7
+ timeoutMs;
8
+ newIdempotencyKey;
9
+ constructor(options) {
10
+ if (!options.token)
11
+ throw new Error('WazapiClient requires a token.');
12
+ this.token = options.token;
13
+ this.baseUrl = (options.baseUrl ?? 'https://wazapi.io/api/v1').replace(/\/$/, '');
14
+ this.fetchImpl = options.fetch ?? globalThis.fetch;
15
+ if (!this.fetchImpl)
16
+ throw new Error('No fetch implementation available; pass options.fetch on Node < 18.');
17
+ this.timeoutMs = options.timeoutMs ?? 30_000;
18
+ this.newIdempotencyKey =
19
+ options.idempotencyKeyFactory ?? (() => globalThis.crypto.randomUUID());
20
+ }
21
+ // ---- Channels ---------------------------------------------------------
22
+ async listChannels() {
23
+ const body = await this.request('GET', '/channels');
24
+ return body.data;
25
+ }
26
+ // ---- Contacts ---------------------------------------------------------
27
+ listContacts(params = {}) {
28
+ return this.request('GET', this.withQuery('/contacts', params));
29
+ }
30
+ async getContact(uuid) {
31
+ const body = await this.request('GET', `/contacts/${encode(uuid)}`);
32
+ return body.data;
33
+ }
34
+ async updateContact(uuid, input) {
35
+ const body = await this.request('PATCH', `/contacts/${encode(uuid)}`, { body: input });
36
+ return body.data;
37
+ }
38
+ async upsertContactByExternalId(externalId, input) {
39
+ const body = await this.request('PUT', `/contacts/external/${encode(externalId)}`, { body: input });
40
+ return body.data;
41
+ }
42
+ // ---- Templates --------------------------------------------------------
43
+ listTemplates(params = {}) {
44
+ return this.request('GET', this.withQuery('/templates', params));
45
+ }
46
+ async getTemplate(name) {
47
+ const body = await this.request('GET', `/templates/${encode(name)}`);
48
+ return body.data;
49
+ }
50
+ // ---- Flows ------------------------------------------------------------
51
+ listFlows(params = {}) {
52
+ return this.request('GET', this.withQuery('/flows', params));
53
+ }
54
+ async getFlow(uuid) {
55
+ const body = await this.request('GET', `/flows/${encode(uuid)}`);
56
+ return body.data;
57
+ }
58
+ executeFlow(flowUuid, input, idempotencyKey) {
59
+ return this.accept('POST', `/flows/${encode(flowUuid)}/executions`, input, idempotencyKey);
60
+ }
61
+ // ---- Conversations ----------------------------------------------------
62
+ listConversations(params = {}) {
63
+ return this.request('GET', this.withQuery('/conversations', params));
64
+ }
65
+ async getConversation(uuid) {
66
+ const body = await this.request('GET', `/conversations/${encode(uuid)}`);
67
+ return body.data;
68
+ }
69
+ listMessages(conversationUuid, params = {}) {
70
+ return this.request('GET', this.withQuery(`/conversations/${encode(conversationUuid)}/messages`, params));
71
+ }
72
+ // ---- Messages ---------------------------------------------------------
73
+ sendMessage(input, idempotencyKey) {
74
+ return this.accept('POST', '/messages', input, idempotencyKey);
75
+ }
76
+ /** Convenience wrapper for a plain text send. */
77
+ sendText(channelUuid, phone, text, idempotencyKey) {
78
+ return this.sendMessage({ channel_uuid: channelUuid, recipient: { phone }, type: 'text', content: { text } }, idempotencyKey);
79
+ }
80
+ /** Convenience wrapper for a template send with positional body parameters. */
81
+ sendTemplate(channelUuid, phone, template, idempotencyKey) {
82
+ return this.sendMessage({ channel_uuid: channelUuid, recipient: { phone }, type: 'template', content: template }, idempotencyKey);
83
+ }
84
+ // ---- Operations -------------------------------------------------------
85
+ async getOperation(uuid) {
86
+ const body = await this.request('GET', `/operations/${encode(uuid)}`);
87
+ return body.data;
88
+ }
89
+ /**
90
+ * Polls an operation until it reaches a terminal status. Resolves with the final
91
+ * operation (which may be `failed` — inspect `operation.error`) or throws on timeout.
92
+ */
93
+ async waitForOperation(uuid, options = {}) {
94
+ const interval = options.intervalMs ?? 1000;
95
+ const deadline = Date.now() + (options.timeoutMs ?? 60_000);
96
+ const terminal = options.resolveOnWaiting
97
+ ? [...TERMINAL_STATUSES, 'waiting']
98
+ : TERMINAL_STATUSES;
99
+ for (;;) {
100
+ const operation = await this.getOperation(uuid);
101
+ if (terminal.includes(operation.status))
102
+ return operation;
103
+ if (Date.now() >= deadline)
104
+ throw new WazapiError({
105
+ status: 0,
106
+ code: 'operation_wait_timeout',
107
+ message: `Operation ${uuid} did not reach a terminal status in time.`,
108
+ });
109
+ await delay(Math.min(interval, Math.max(0, deadline - Date.now())));
110
+ }
111
+ }
112
+ // ---- Internals --------------------------------------------------------
113
+ async accept(method, path, body, idempotencyKey) {
114
+ const key = idempotencyKey ?? this.newIdempotencyKey();
115
+ const { data, headers } = await this.rawRequest(method, path, {
116
+ body,
117
+ headers: { 'Idempotency-Key': key },
118
+ });
119
+ return { operation: data.data, replayed: headers.get('Idempotent-Replayed') === 'true' };
120
+ }
121
+ async request(method, path, init = {}) {
122
+ const { data } = await this.rawRequest(method, path, init);
123
+ return data;
124
+ }
125
+ async rawRequest(method, path, init = {}) {
126
+ const controller = new AbortController();
127
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
128
+ let response;
129
+ try {
130
+ response = await this.fetchImpl(`${this.baseUrl}${path}`, {
131
+ method,
132
+ signal: controller.signal,
133
+ headers: {
134
+ Authorization: `Bearer ${this.token}`,
135
+ Accept: 'application/json',
136
+ ...(init.body !== undefined ? { 'Content-Type': 'application/json' } : {}),
137
+ ...init.headers,
138
+ },
139
+ body: init.body !== undefined ? JSON.stringify(init.body) : undefined,
140
+ });
141
+ }
142
+ catch (cause) {
143
+ clearTimeout(timer);
144
+ if (cause instanceof Error && cause.name === 'AbortError')
145
+ throw new WazapiError({
146
+ status: 0,
147
+ code: 'request_timeout',
148
+ message: `Request to ${path} timed out after ${this.timeoutMs}ms.`,
149
+ });
150
+ throw new WazapiError({
151
+ status: 0,
152
+ code: 'network_error',
153
+ message: cause instanceof Error ? cause.message : 'Network request failed.',
154
+ });
155
+ }
156
+ clearTimeout(timer);
157
+ const text = await response.text();
158
+ const parsed = text ? safeJson(text) : null;
159
+ if (!response.ok) {
160
+ const envelope = parsed?.error;
161
+ const retryAfterHeader = response.headers.get('Retry-After');
162
+ throw new WazapiError({
163
+ status: response.status,
164
+ code: envelope?.code ?? 'http_error',
165
+ message: envelope?.message ?? `Request failed with status ${response.status}.`,
166
+ requestId: envelope?.request_id ?? null,
167
+ details: envelope?.details ?? null,
168
+ retryAfter: retryAfterHeader ? Number(retryAfterHeader) : null,
169
+ });
170
+ }
171
+ return { data: parsed, headers: response.headers };
172
+ }
173
+ withQuery(path, params) {
174
+ const search = new URLSearchParams();
175
+ for (const [key, value] of Object.entries(params)) {
176
+ if (value !== undefined && value !== null)
177
+ search.set(key, String(value));
178
+ }
179
+ const qs = search.toString();
180
+ return qs ? `${path}?${qs}` : path;
181
+ }
182
+ }
183
+ function encode(segment) {
184
+ return encodeURIComponent(segment);
185
+ }
186
+ function safeJson(text) {
187
+ try {
188
+ return JSON.parse(text);
189
+ }
190
+ catch {
191
+ return null;
192
+ }
193
+ }
194
+ function delay(ms) {
195
+ return new Promise((resolve) => setTimeout(resolve, ms));
196
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Error thrown for any non-2xx response from the Wazapi API. Carries the stable
3
+ * error envelope (`code`, `message`, `request_id`) so callers can branch on
4
+ * `error.code` — e.g. `template_parameter_count_mismatch` — and quote `request_id`
5
+ * to support.
6
+ */
7
+ export declare class WazapiError extends Error {
8
+ readonly status: number;
9
+ readonly code: string;
10
+ readonly requestId: string | null;
11
+ readonly details: unknown;
12
+ /** Value of the `Retry-After` header on a 429, in seconds, when present. */
13
+ readonly retryAfter: number | null;
14
+ constructor(input: {
15
+ status: number;
16
+ code: string;
17
+ message: string;
18
+ requestId?: string | null;
19
+ details?: unknown;
20
+ retryAfter?: number | null;
21
+ });
22
+ /** True for 429 rate-limit responses. */
23
+ get isRateLimited(): boolean;
24
+ }
package/dist/error.js ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Error thrown for any non-2xx response from the Wazapi API. Carries the stable
3
+ * error envelope (`code`, `message`, `request_id`) so callers can branch on
4
+ * `error.code` — e.g. `template_parameter_count_mismatch` — and quote `request_id`
5
+ * to support.
6
+ */
7
+ export class WazapiError extends Error {
8
+ status;
9
+ code;
10
+ requestId;
11
+ details;
12
+ /** Value of the `Retry-After` header on a 429, in seconds, when present. */
13
+ retryAfter;
14
+ constructor(input) {
15
+ super(input.message);
16
+ this.name = 'WazapiError';
17
+ this.status = input.status;
18
+ this.code = input.code;
19
+ this.requestId = input.requestId ?? null;
20
+ this.details = input.details ?? null;
21
+ this.retryAfter = input.retryAfter ?? null;
22
+ }
23
+ /** True for 429 rate-limit responses. */
24
+ get isRateLimited() {
25
+ return this.status === 429;
26
+ }
27
+ }
@@ -0,0 +1,4 @@
1
+ export { WazapiClient } from './client.js';
2
+ export type { WazapiClientOptions, WaitForOperationOptions } from './client.js';
3
+ export { WazapiError } from './error.js';
4
+ export type * from './types.js';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { WazapiClient } from './client.js';
2
+ export { WazapiError } from './error.js';
@@ -0,0 +1,145 @@
1
+ export type PublicApiScope = 'channels:read' | 'contacts:read' | 'contacts:write' | 'conversations:read' | 'messages:read' | 'messages:write' | 'templates:read' | 'flows:read' | 'flows:execute' | 'operations:read';
2
+ export interface PaginationMeta {
3
+ next_cursor: string | null;
4
+ }
5
+ export interface Paginated<T> {
6
+ data: T[];
7
+ meta: PaginationMeta;
8
+ }
9
+ export interface Envelope<T> {
10
+ data: T;
11
+ }
12
+ export interface ChannelCapabilities {
13
+ send_text: boolean;
14
+ send_template: boolean;
15
+ start_flow: boolean;
16
+ }
17
+ export interface Channel {
18
+ uuid: string;
19
+ provider: 'whatsapp';
20
+ display_name: string | null;
21
+ phone_number: string | null;
22
+ status: string;
23
+ capabilities: ChannelCapabilities;
24
+ }
25
+ export interface Contact {
26
+ uuid: string;
27
+ external_id: string | null;
28
+ phone: string | null;
29
+ name: string | null;
30
+ email: string | null;
31
+ tags: string[];
32
+ custom_fields: Record<string, unknown>;
33
+ last_interaction_at: string | null;
34
+ created_at: string | null;
35
+ updated_at: string | null;
36
+ }
37
+ export interface ContactWrite {
38
+ name?: string | null;
39
+ email?: string | null;
40
+ tags?: string[];
41
+ custom_fields?: Record<string, unknown>;
42
+ }
43
+ export interface Flow {
44
+ uuid: string;
45
+ name: string;
46
+ status: 'draft' | 'active';
47
+ supported_providers: string[];
48
+ created_at: string | null;
49
+ updated_at: string | null;
50
+ }
51
+ export type TemplateStatus = 'APPROVED' | 'PENDING' | 'REJECTED' | 'PAUSED' | 'DISABLED' | 'IN_APPEAL' | 'DRAFT';
52
+ export type TemplateCategory = 'AUTHENTICATION' | 'MARKETING' | 'UTILITY';
53
+ export interface TemplateVariables {
54
+ body_parameter_count: number;
55
+ body_named_parameters: string[];
56
+ header: {
57
+ format: string;
58
+ has_variable: boolean;
59
+ } | null;
60
+ has_dynamic_buttons: boolean;
61
+ }
62
+ export interface Template {
63
+ uuid: string;
64
+ name: string;
65
+ language: string;
66
+ status: TemplateStatus;
67
+ category: TemplateCategory;
68
+ channel_uuid: string | null;
69
+ variables: TemplateVariables;
70
+ created_at: string | null;
71
+ updated_at: string | null;
72
+ }
73
+ export interface Conversation {
74
+ uuid: string;
75
+ status: string;
76
+ channel_uuid: string | null;
77
+ contact: Contact | null;
78
+ unread_count: number;
79
+ last_message_at: string | null;
80
+ created_at: string | null;
81
+ updated_at: string | null;
82
+ }
83
+ export interface Message {
84
+ uuid: string;
85
+ direction: 'inbound' | 'outbound';
86
+ type: string;
87
+ status: string;
88
+ content: Record<string, unknown>;
89
+ provider_message_id: string | null;
90
+ created_at: string | null;
91
+ updated_at: string | null;
92
+ }
93
+ export type OperationStatus = 'queued' | 'processing' | 'waiting' | 'succeeded' | 'failed';
94
+ export interface Operation {
95
+ uuid: string;
96
+ type: 'message.send' | 'flow.execute';
97
+ status: OperationStatus;
98
+ result: Record<string, unknown> | null;
99
+ error: {
100
+ code: string;
101
+ message: string | null;
102
+ } | null;
103
+ created_at: string | null;
104
+ started_at: string | null;
105
+ completed_at: string | null;
106
+ }
107
+ export interface Recipient {
108
+ phone: string;
109
+ external_id?: string;
110
+ }
111
+ export type SendMessageInput = {
112
+ channel_uuid: string;
113
+ recipient: Recipient;
114
+ type: 'text';
115
+ content: {
116
+ text: string;
117
+ };
118
+ } | {
119
+ channel_uuid: string;
120
+ recipient: Recipient;
121
+ type: 'template';
122
+ content: {
123
+ name: string;
124
+ language?: string;
125
+ parameters?: string[];
126
+ };
127
+ };
128
+ export interface ExecuteFlowInput {
129
+ channel_uuid: string;
130
+ recipient: Recipient;
131
+ variables?: Record<string, unknown>;
132
+ }
133
+ export interface ListParams {
134
+ after?: string;
135
+ limit?: number;
136
+ query?: string;
137
+ status?: string;
138
+ channel_uuid?: string;
139
+ updated_after?: string;
140
+ }
141
+ export interface AcceptedResult {
142
+ operation: Operation;
143
+ /** True when the request replayed a previously accepted idempotent operation. */
144
+ replayed: boolean;
145
+ }
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ // Types mirror the Wazapi Public API v1 OpenAPI contract
2
+ // (openapi/public-api.v1.json). Keep in sync when the contract changes.
3
+ export {};
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@wazapi/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Official Node.js SDK for the Wazapi Public API v1",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "homepage": "https://wazapi.io",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/wazap-ai/wazap-v2.git",
14
+ "directory": "apps/app/sdk"
15
+ },
16
+ "main": "./dist/index.js",
17
+ "module": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js"
23
+ }
24
+ },
25
+ "files": ["dist", "README.md"],
26
+ "engines": {
27
+ "node": ">=18"
28
+ },
29
+ "scripts": {
30
+ "build": "tsc -p tsconfig.json",
31
+ "test": "npm run build && node --test --experimental-strip-types test/*.test.ts",
32
+ "prepublishOnly": "npm run build"
33
+ },
34
+ "keywords": ["wazapi", "whatsapp", "api", "sdk", "messaging"],
35
+ "devDependencies": {
36
+ "typescript": "^5.6.0"
37
+ }
38
+ }