@wokuapp/sdk 0.2.1 → 0.3.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/README.md CHANGED
@@ -12,15 +12,15 @@ Official **server-side** SDK for the [Woku](https://woku.app) management API.
12
12
 
13
13
  ## Why
14
14
 
15
- Manage your entire Woku account from your backend with one typed client:
15
+ Manage supported woku resources from your backend with one typed client:
16
16
  trackers, VoC tools (NPS/CSAT/CES), wokus, forms, flows, action plans,
17
17
  support tickets, delivery tracking and survey sends over the public `/v1` API.
18
18
 
19
19
  - **Typed** request bodies (generated from the OpenAPI spec) and response models.
20
20
  - **Automatic retries** with full-jitter backoff and `Retry-After` support.
21
- - **Idempotent creates**: creates carry an auto-generated `Idempotency-Key`, so
22
- a retry after a blip never creates twice. Action calls (`send`, `test`,
23
- `reply`) are never silently replayed.
21
+ - **Protected writes**: tracker/VoC definitions, invitations and five journey operations use
22
+ a stable idempotency key for retries. Other writes and uploads are
23
+ attempted once, even when a caller provides a key. See the retry policy below.
24
24
  - **Auto-paginated** lists: `for await (const item of await woku.tickets.list())`.
25
25
  - **Typed errors** with the server `request_id` for support.
26
26
  - **Zero runtime dependencies.**
@@ -50,18 +50,25 @@ const tracker = await woku.trackers.create({
50
50
  system: 'retail',
51
51
  });
52
52
 
53
- // Create an NPS tool and send it.
53
+ // Create an NPS tool.
54
54
  const tool = await woku.npsTools.create({
55
55
  name: 'Post-purchase',
56
56
  npsMessage: 'How likely are you to recommend us?',
57
57
  });
58
+
59
+ // Tag the NPS tool with the tracker, so every response is grouped by store.
60
+ await woku.trackers.assignToEntity('nps', tool._id, {
61
+ name: tracker.name,
62
+ value: 'TX-42',
63
+ });
64
+
65
+ // Send it, then read delivery + response rate.
58
66
  await woku.nps.sendInvitations({
59
67
  channel: 'email',
60
68
  npsToolId: tool._id,
61
69
  recipients: ['ana@example.com'],
62
70
  });
63
71
 
64
- // Read delivery + response rate.
65
72
  const stats = await woku.dispatches.stats({ channel: 'email' });
66
73
  console.log(stats.responseRate);
67
74
  ```
@@ -69,9 +76,155 @@ console.log(stats.responseRate);
69
76
  The key is read from `WOKU_API_KEY` when you omit `apiKey`. You can also pass
70
77
  it directly: `new Woku('sk_live_...')`.
71
78
 
79
+ ### Customer journeys
80
+
81
+ Use `authoringVersion: 2` for the business-form execution contract. Choose
82
+ `startMode: 'operator'`, `'response'`, or `'webhook'` for the first moment.
83
+ Only operator mode uses `enroll`. Response mode starts when the customer answers
84
+ the first tool through a shared link or QR, not when the link is opened.
85
+ Later moments use a delay or their own webhook. A webhook advances a timed moment
86
+ and cancels its wait. A webhook-primary moment can have a secondary fallback that
87
+ evaluates that same moment once.
88
+
89
+ Each moment creates its own CSAT, CES, NPS, or woku tool. `toolScope` defaults
90
+ to `'shared'` for new v2 moments, reusing the tool within that moment and
91
+ configuration. Use `'per_enrollment'` for one tool per participation. Later
92
+ moments receive a suggested 10-day wait in the authoring form; API callers must
93
+ specify the delay. In v2, `delayMs: 0` means one hour. Existing
94
+ tools cannot be assigned. Use `toolSpec` for question variables, or an uploaded
95
+ `fileId` for Woku. The example uses one initial send and no reminders.
96
+ For a bilingual Woku, set `toolSpec.descriptionEn` to its English title.
97
+
98
+ Agents with a local terminal can upload an image or MP4 to
99
+ `POST /v1/woku-media` using multipart and the company key. The response's
100
+ `fileId` can be used as `toolSpec.fileId` in a journey Woku moment or with the
101
+ MCP `create_woku` tool. The generated `Schemas` map includes the media upload
102
+ response type; media.upload wraps the binary upload endpoint.
103
+ The endpoint returns `400` for invalid media and `413` for multipart requests
104
+ over 25 MB.
105
+
106
+ ```ts
107
+ const day = 86_400_000;
108
+ const sequence = {
109
+ attemptOffsetsMs: [0],
110
+ deadlineMs: 3 * day,
111
+ cooldownAfterResponseMs: 0,
112
+ };
113
+ const journey = await woku.journeys.create({
114
+ name: 'Purchase and delivery',
115
+ authoringVersion: 2,
116
+ startMode: 'webhook',
117
+ recipients: {
118
+ ticketsEnabled: true,
119
+ plansEnabled: true,
120
+ ticketEmails: ['support@example.com'],
121
+ planMembers: [
122
+ { userId: '507f1f77bcf86cd799439011', role: 'admin' },
123
+ { userId: '507f1f77bcf86cd799439012', role: 'assignee' },
124
+ ],
125
+ },
126
+ moments: [
127
+ {
128
+ key: 'sale',
129
+ name: 'Purchase',
130
+ tool: 'csat',
131
+ enabled: true,
132
+ channel: 'email',
133
+ trigger: { type: 'webhook' },
134
+ webhook: { verification: { mode: 'url_token' } },
135
+ toolSpec: { subject: { es: 'tu compra', en: 'your purchase' } },
136
+ sequence,
137
+ },
138
+ {
139
+ key: 'delivery',
140
+ name: 'Delivery',
141
+ tool: 'ces',
142
+ enabled: true,
143
+ channel: 'email',
144
+ trigger: { type: 'webhook' },
145
+ webhook: { verification: { mode: 'url_token' } },
146
+ fallbackFromStage: 'sale',
147
+ fallbackAfterMs: 5 * day,
148
+ toolSpec: {
149
+ subject: { es: 'recibir tu pedido', en: 'receiving your order' },
150
+ },
151
+ sequence,
152
+ },
153
+ ],
154
+ });
155
+
156
+ // Generate once and securely store each URL in its sending system.
157
+ // Calling mintMomentUrl again replaces the previous credential.
158
+ const sale = await woku.journeys.mintMomentUrl(journey.id, 'sale');
159
+ const delivery = await woku.journeys.mintMomentUrl(journey.id, 'delivery');
160
+ await woku.journeys.update(journey.id, { enabled: true });
161
+
162
+ // CRM and logistics use different URLs but the same purchase reference.
163
+ await fetch(sale.url, {
164
+ method: 'POST',
165
+ headers: {
166
+ 'Content-Type': 'application/json',
167
+ 'X-Woku-Event-Id': 'crm-order-123',
168
+ },
169
+ body: JSON.stringify({
170
+ subjectKey: 'order-123',
171
+ contact: { email: 'customer@example.com' },
172
+ }),
173
+ });
174
+ await fetch(delivery.url, {
175
+ method: 'POST',
176
+ headers: {
177
+ 'Content-Type': 'application/json',
178
+ 'X-Woku-Event-Id': 'delivery-order-123',
179
+ },
180
+ body: JSON.stringify({ subjectKey: 'order-123' }),
181
+ });
182
+
183
+ const page = await woku.journeys.listEnrollments(journey.id, { limit: 20 });
184
+ const evaluation = page.items.find(
185
+ (item) =>
186
+ item.subjectKey === 'order-123' &&
187
+ (item.lifecycle === 'pending' || item.lifecycle === 'running'),
188
+ );
189
+ if (evaluation) {
190
+ await woku.journeys.stopEnrollment(
191
+ journey.id,
192
+ evaluation.id,
193
+ {
194
+ reason: 'Customer requested no further evaluations',
195
+ },
196
+ { idempotencyKey: `stop-${evaluation.id}` },
197
+ );
198
+ }
199
+ ```
200
+
201
+ `getEnrollment` reads one case. `connections` reports credential readiness;
202
+ `previewMoment` checks a sample against the saved payload mapping without sending.
203
+ `setSenderSecret` configures senders that sign with their own secret.
204
+ Enrollment lists use `{ items, nextCursor }`, not `Page`: pass `nextCursor` as the
205
+ next call's `cursor`. Stop is durable and specific to the selected participation;
206
+ answers, tickets, plans, shared tools and other cases remain. A send already
207
+ accepted by its provider may still arrive. `stopping` means cleanup is in progress;
208
+ `dispatchOutcomeUncertain` identifies an interrupted in-flight send.
209
+
210
+ `pendingMoments` names the moments that have not yet sent an invitation. A v2
211
+ participation completes when the customer answers its final tool or 30 days
212
+ after that tool's first send. The same `subjectKey` can be enrolled again once
213
+ the previous participation is completed or stopped; each cycle has its own `id`.
214
+
215
+ Tickets go to email destinations. Plans go to an action-plan group made of existing
216
+ company users; `planMembers` controls its admins and assignees. Definitions remain
217
+ off until activated and required resources are ready.
218
+ Set `recipients.ticketsEnabled` or `recipients.plansEnabled` to `false` to stop that
219
+ journey action independently. Both default to enabled when omitted. Disabled
220
+ actions do not require completed recipients; their current settings remain
221
+ available if re-enabled later.
222
+ Existing definitions retain their contract; create a new v2 journey to adopt these
223
+ rules. Current participations retain their original definition version.
224
+
72
225
  ## Pagination
73
226
 
74
- List methods return a `Page`. Iterate every item across pages, or walk pages:
227
+ Paginated resource methods return a `Page` (journey enrollments use the cursor envelope described above). Iterate every item across pages, or walk pages:
75
228
 
76
229
  ```ts
77
230
  for await (const ticket of await woku.tickets.list({ severity: 'high' })) {
@@ -131,8 +284,53 @@ await woku.npsTools.create(body, { idempotencyKey: 'my-key' });
131
284
 
132
285
  `trackers`, `npsTools` / `csatTools` / `cesTools`, `nps` / `csat` / `ces`,
133
286
  `wokus`, `forms`, `flows`, `actionPlans`, `actionPlanGroups`, `tickets`,
134
- `ticketDestinations`, `dispatches`, `reports`, `company`, `quarantines`.
287
+ `ticketDestinations`, `dispatches`, `reports`, `company`, `quarantines`,
288
+ `journeys`.
135
289
 
136
290
  ## License
137
291
 
138
292
  MIT
293
+
294
+ Advanced journey moments support `webhook.contentMode`, a bounded JSON `schema`,
295
+ `payload.clientFields`, conditional JavaScript text, localized variables, public
296
+ image URL paths, folders and tracker mappings. HTTP uses `sequence`; MCP uses
297
+ `cadence`. `Schemas` includes complete journey request and response shapes;
298
+ `JourneyMomentPreview` includes resolved content. A saved preview returns `200`
299
+ without verifying signatures or sending. Unknown HTTP and MCP moment fields now
300
+ return a validation error. `webhookSecret` is the legacy journey-wide signature,
301
+ separate from per-moment URL tokens and sender HMAC secrets.
302
+
303
+ `journeys.entryInfo` reads customer entry metadata; `journeys.prepareEntry`
304
+ prepares its first tool with a UUID request ID and identified contact. Neither
305
+ starts the journey. Pass the returned token as `dispatchToken` when capturing
306
+ its first valid answer. Journey reads, progress, previews, enrollment and event
307
+ results now use the generated public shapes directly. Server-side SDK keys
308
+ remain on the server; browser entry does not need a company secret key.
309
+
310
+ ## Journey SDK v4
311
+
312
+ Upload local image/MP4 bytes with `woku.media.upload({ file, filename, contentType })`.
313
+ The result provides fileId for a Woku moment. Fetch supplies the multipart boundary;
314
+ uploads are not retried because this endpoint has no idempotency ledger. 413 is a
315
+ PayloadTooLargeError with the server requestId.
316
+
317
+ Iterate customer cases with `for await (const item of woku.journeys.iterEnrollments(id))`.
318
+ listEnrollments keeps its cursor envelope. Repeated cursors/pages raise code
319
+ pagination_error instead of looping.
320
+
321
+ Automatic write retries apply only to declared idempotent operations: tracker
322
+ creation, VoC-tool creation, invitations, journey creation/enrollment/stopping,
323
+ URL minting and journey events. Other writes are sent once. A key alone cannot
324
+ make an unsupported endpoint safe to retry. Errors expose idempotencyKey for
325
+ reconciling the original operation. Keep keys opaque and credential URLs out of logs.
326
+ Retry-After is honored; abort signals interrupt backoff.
327
+
328
+ Configure baseURL for staging. Absolute API paths are rejected and ids are encoded
329
+ as single segments. Company secret keys remain server-side. Tickets and Data Studio
330
+ are Corporate capabilities; API access remains available on every plan.
331
+
332
+ See [the example](../../examples/journey-hybrid.ts) for a typed four-moment journey with local media, conditional
333
+ webhook content and waits. It remains disabled and sends no evaluations.
334
+
335
+ Generate API types with `pnpm generate`; `pnpm check:generated` checks drift
336
+ against the vendored OpenAPI without requiring a sibling server checkout.