@medalsocial/sdk 1.5.0 → 1.7.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 +213 -1
- package/dist/openapi/medal-social.openapi.json +3739 -1411
- package/dist/pilot/index.d.mts +2 -2
- package/dist/pilot/index.d.ts +2 -2
- package/dist/src/index.d.mts +1376 -258
- package/dist/src/index.d.ts +1376 -258
- package/dist/src/index.js +667 -57
- package/dist/src/index.js.map +1 -1
- package/dist/src/index.mjs +661 -57
- package/dist/src/index.mjs.map +1 -1
- package/dist/src/openapi.generated.d.mts +1287 -113
- package/dist/src/openapi.generated.d.ts +1287 -113
- package/dist/src/openapi.generated.js.map +1 -1
- package/openapi/medal-social.openapi.yaml +1394 -27
- package/package.json +18 -17
- package/skills/client/SKILL.md +2 -2
- package/skills/resources/SKILL.md +105 -3
package/dist/src/index.d.mts
CHANGED
|
@@ -1,51 +1,28 @@
|
|
|
1
1
|
export { components as OpenApiComponents, operations as OpenApiOperations, paths as OpenApiPaths } from './openapi.generated.mjs';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
interface
|
|
5
|
-
|
|
6
|
-
token: string;
|
|
7
|
-
workspaceId?: string;
|
|
8
|
-
timeout: number;
|
|
9
|
-
userAgent: string;
|
|
3
|
+
/** Successful API response wrapper */
|
|
4
|
+
interface ApiResponse<T> {
|
|
5
|
+
data: T;
|
|
10
6
|
}
|
|
11
|
-
/**
|
|
12
|
-
interface
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
*/
|
|
19
|
-
idempotencyKey?: string;
|
|
20
|
-
/**
|
|
21
|
-
* Capability confirmation token sent as the `X-Capability-Confirmation`
|
|
22
|
-
* header. Required alongside `idempotencyKey` when a token granted a
|
|
23
|
-
* capability-style scope directly (e.g. `helpdesk.webhook.manage`) executes
|
|
24
|
-
* a confirmable write route. Obtain one from
|
|
25
|
-
* `POST /api/v1/capability-confirmations`. API keys with legacy scopes do
|
|
26
|
-
* not need it.
|
|
27
|
-
*/
|
|
28
|
-
capabilityConfirmation?: string;
|
|
7
|
+
/** Paginated API response */
|
|
8
|
+
interface PaginatedResponse<T> {
|
|
9
|
+
data: T[];
|
|
10
|
+
pagination: {
|
|
11
|
+
has_more: boolean;
|
|
12
|
+
next_cursor: string | null;
|
|
13
|
+
};
|
|
29
14
|
}
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
post<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
|
|
42
|
-
/** Execute an authenticated PATCH request with a JSON body. */
|
|
43
|
-
patch<T>(path: string, body: unknown, options?: RequestOptions): Promise<T>;
|
|
44
|
-
/** Execute an authenticated DELETE request. */
|
|
45
|
-
delete<T>(path: string, options?: RequestOptions): Promise<T>;
|
|
46
|
-
private writeHeaders;
|
|
47
|
-
private buildUrl;
|
|
48
|
-
private request;
|
|
15
|
+
/** API error thrown by the client */
|
|
16
|
+
declare class MedalApiError extends Error {
|
|
17
|
+
readonly status: number;
|
|
18
|
+
readonly code: string;
|
|
19
|
+
readonly details?: unknown;
|
|
20
|
+
constructor(status: number, code: string, message: string, details?: unknown);
|
|
21
|
+
}
|
|
22
|
+
/** Pagination options for list endpoints */
|
|
23
|
+
interface PaginationOptions {
|
|
24
|
+
limit?: number;
|
|
25
|
+
cursor?: string;
|
|
49
26
|
}
|
|
50
27
|
|
|
51
28
|
/** Lifecycle status of a hosted connect link. */
|
|
@@ -91,8 +68,8 @@ interface ConnectLink {
|
|
|
91
68
|
/** Unix timestamp in milliseconds. */
|
|
92
69
|
created_at: number;
|
|
93
70
|
}
|
|
94
|
-
/** Filters for listing connect links. */
|
|
95
|
-
interface ListConnectLinksOptions {
|
|
71
|
+
/** Filters and cursor pagination for listing connect links. */
|
|
72
|
+
interface ListConnectLinksOptions extends PaginationOptions {
|
|
96
73
|
channel_type?: string;
|
|
97
74
|
status?: ConnectLinkStatus;
|
|
98
75
|
}
|
|
@@ -120,35 +97,1008 @@ interface ChannelConnectionDisconnectResult {
|
|
|
120
97
|
state: "disconnected";
|
|
121
98
|
}
|
|
122
99
|
|
|
123
|
-
/**
|
|
124
|
-
|
|
125
|
-
|
|
100
|
+
/** Lifecycle status of a helpdesk conversation. */
|
|
101
|
+
type ConversationStatus = "open" | "snoozed" | "closed";
|
|
102
|
+
/** Who authored a helpdesk message. */
|
|
103
|
+
type MessageAuthorType = "visitor" | "operator" | "ai" | "system";
|
|
104
|
+
/** Kind of helpdesk message. `note` is operator-internal and never delivered to the customer. */
|
|
105
|
+
type HelpdeskMessageType = "chat" | "email" | "note";
|
|
106
|
+
/** A helpdesk conversation across any connected channel (widget, email, social DMs, …). */
|
|
107
|
+
interface Conversation {
|
|
108
|
+
id: string;
|
|
109
|
+
/** Channel type, e.g. 'widget', 'instagram', 'messenger', 'whatsapp', 'email'. */
|
|
110
|
+
channel: string;
|
|
111
|
+
channel_connection_id: string | null;
|
|
112
|
+
status: ConversationStatus;
|
|
113
|
+
subject: string | null;
|
|
114
|
+
assignee_user_id: string | null;
|
|
115
|
+
contact_id: string | null;
|
|
116
|
+
visitor_name: string | null;
|
|
117
|
+
visitor_email: string | null;
|
|
118
|
+
external_conversation_id: string | null;
|
|
119
|
+
channel_account_id: string | null;
|
|
120
|
+
message_count: number;
|
|
121
|
+
unread_for_operator: number;
|
|
122
|
+
/** Unix timestamp in milliseconds. */
|
|
123
|
+
last_message_at: number;
|
|
124
|
+
last_message_preview: string | null;
|
|
125
|
+
last_message_author_type: MessageAuthorType | null;
|
|
126
|
+
/** Unix timestamp in milliseconds. */
|
|
127
|
+
created_at: number;
|
|
128
|
+
/** Unix timestamp in milliseconds. */
|
|
129
|
+
updated_at: number;
|
|
126
130
|
}
|
|
127
|
-
/**
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Outbound delivery state of a helpdesk message.
|
|
133
|
+
*
|
|
134
|
+
* - `pending` — queued for the channel, not handed off yet.
|
|
135
|
+
* - `sent` — handed to the channel provider, no confirmation yet.
|
|
136
|
+
* - `delivered` — the channel confirmed delivery to the customer.
|
|
137
|
+
* - `failed` — delivery failed; see `delivery_error` on the message.
|
|
138
|
+
*/
|
|
139
|
+
type MessageDeliveryStatus = "pending" | "sent" | "delivered" | "failed";
|
|
140
|
+
/** A single message inside a helpdesk conversation. */
|
|
141
|
+
interface ConversationMessage {
|
|
142
|
+
id: string;
|
|
143
|
+
conversation_id: string;
|
|
144
|
+
author_type: MessageAuthorType;
|
|
145
|
+
message_type: HelpdeskMessageType;
|
|
146
|
+
author_user_id: string | null;
|
|
147
|
+
author_name: string | null;
|
|
148
|
+
body: string;
|
|
149
|
+
/**
|
|
150
|
+
* Outbound delivery state, or `null` for inbound messages and internal
|
|
151
|
+
* notes — neither is ever sent to a channel, so neither has delivery state.
|
|
152
|
+
*
|
|
153
|
+
* **A `201` from `helpdesk.replies.create` means the reply was ACCEPTED, not
|
|
154
|
+
* delivered.** The channel hand-off happens asynchronously afterwards: poll
|
|
155
|
+
* this field (or subscribe to the `helpdesk.message_delivery_updated`
|
|
156
|
+
* webhook event, which carries the same values) to learn whether the message
|
|
157
|
+
* actually reached the customer.
|
|
158
|
+
*/
|
|
159
|
+
delivery_status: MessageDeliveryStatus | null;
|
|
160
|
+
/**
|
|
161
|
+
* Last send error for a `failed` outbound message, or `null` when there is
|
|
162
|
+
* no error to report (including on inbound messages and internal notes).
|
|
163
|
+
*/
|
|
164
|
+
delivery_error: string | null;
|
|
165
|
+
/** Unix timestamp in milliseconds. */
|
|
166
|
+
created_at: number;
|
|
167
|
+
}
|
|
168
|
+
/** Filters for listing/searching helpdesk conversations. */
|
|
169
|
+
interface ListConversationsOptions extends PaginationOptions {
|
|
170
|
+
status?: ConversationStatus;
|
|
171
|
+
/** Only conversations assigned to this user. */
|
|
172
|
+
assignee_user_id?: string;
|
|
173
|
+
/** Match against visitor name/email. */
|
|
174
|
+
requester?: string;
|
|
175
|
+
/** Free-text search query. */
|
|
176
|
+
query?: string;
|
|
177
|
+
/** Only conversations on these channels (serialized as CSV). */
|
|
178
|
+
channels?: string[];
|
|
179
|
+
}
|
|
180
|
+
/** Input for updating a conversation's status and/or assignee. At least one field is required. */
|
|
181
|
+
interface UpdateConversationInput {
|
|
182
|
+
status?: ConversationStatus;
|
|
183
|
+
/** User ID to assign, or `null` to unassign. */
|
|
184
|
+
assignee_user_id?: string | null;
|
|
185
|
+
}
|
|
186
|
+
/** Result of a conversation update. */
|
|
187
|
+
interface ConversationUpdateResult {
|
|
188
|
+
id: string;
|
|
189
|
+
status: ConversationStatus;
|
|
190
|
+
assignee_user_id: string | null;
|
|
191
|
+
}
|
|
192
|
+
/** Input for sending an operator reply (or internal note) into a conversation. */
|
|
193
|
+
interface CreateReplyInput {
|
|
194
|
+
conversation_id: string;
|
|
195
|
+
/** Message body (max 20,000 characters). */
|
|
196
|
+
body: string;
|
|
197
|
+
/** `note` = operator-internal note (not delivered to the customer). Default `chat`. */
|
|
198
|
+
message_type?: "chat" | "note";
|
|
199
|
+
/** Agent display name for bridged replies (shown in widget + inbox). */
|
|
200
|
+
author_name?: string;
|
|
201
|
+
}
|
|
202
|
+
/** Result returned after creating a reply (HTTP 201). */
|
|
203
|
+
interface ReplyCreateResult {
|
|
204
|
+
id: string;
|
|
205
|
+
conversation_id: string;
|
|
206
|
+
status: string;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** A webhook endpoint registered in the workspace. */
|
|
210
|
+
interface WebhookEndpoint {
|
|
211
|
+
id: string;
|
|
212
|
+
name: string;
|
|
213
|
+
/** Destination URL (must be https). */
|
|
214
|
+
url: string;
|
|
215
|
+
enabled: boolean;
|
|
216
|
+
/** Subscribed event types. Empty array = all events. */
|
|
217
|
+
event_types: string[];
|
|
218
|
+
/** Channel-type filter (e.g. ['widget', 'whatsapp']), or `null` for all channels. */
|
|
219
|
+
channels: string[] | null;
|
|
220
|
+
/** Channel-connection filter, or `null` for all connections. */
|
|
221
|
+
channel_connection_ids: string[] | null;
|
|
222
|
+
/** Last 4 characters of the signing secret, for identification. */
|
|
223
|
+
secret_last4: string;
|
|
224
|
+
consecutive_failures: number;
|
|
225
|
+
/** Unix timestamps in milliseconds, or `null` if never. */
|
|
226
|
+
last_delivery_at: number | null;
|
|
227
|
+
last_success_at: number | null;
|
|
228
|
+
last_error_at: number | null;
|
|
229
|
+
last_error: string | null;
|
|
230
|
+
created_at: number;
|
|
231
|
+
updated_at: number;
|
|
232
|
+
/**
|
|
233
|
+
* Full signing secret (`whsec_…`) — present ONLY in the `create` response.
|
|
234
|
+
* It is returned exactly once and can never be retrieved again. Store it
|
|
235
|
+
* securely immediately; you need it to verify delivery signatures.
|
|
236
|
+
*/
|
|
237
|
+
secret?: string;
|
|
238
|
+
}
|
|
239
|
+
/** Input for creating a webhook endpoint. */
|
|
240
|
+
interface CreateWebhookInput {
|
|
241
|
+
/** Display name (max 100 characters). */
|
|
242
|
+
name: string;
|
|
243
|
+
/** Destination URL — must be https. */
|
|
244
|
+
url: string;
|
|
245
|
+
/** Event types to subscribe to (e.g. 'helpdesk.message_received'). Empty = all. */
|
|
246
|
+
event_types: string[];
|
|
247
|
+
/** Restrict to these channel types (e.g. ['widget', 'whatsapp']). */
|
|
248
|
+
channels?: string[];
|
|
249
|
+
/** Restrict to these channel connection IDs. */
|
|
250
|
+
channel_connection_ids?: string[];
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Input for updating a webhook endpoint. Only provided fields change.
|
|
254
|
+
* Pass `null` for `channels` or `channel_connection_ids` to CLEAR an existing
|
|
255
|
+
* filter (deliver for all channels / all accounts again); omitting the field
|
|
256
|
+
* leaves the current filter unchanged.
|
|
257
|
+
*/
|
|
258
|
+
interface UpdateWebhookInput {
|
|
259
|
+
name?: string;
|
|
260
|
+
url?: string;
|
|
261
|
+
event_types?: string[];
|
|
262
|
+
channels?: string[] | null;
|
|
263
|
+
channel_connection_ids?: string[] | null;
|
|
264
|
+
enabled?: boolean;
|
|
265
|
+
}
|
|
266
|
+
/** Result of deleting a webhook endpoint. */
|
|
267
|
+
interface WebhookDeleteResult {
|
|
268
|
+
id: string;
|
|
269
|
+
status: string;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* A delivery attempt record for a webhook endpoint.
|
|
273
|
+
*
|
|
274
|
+
* `id` is the same value sent as the `X-Medal-Delivery-Id` and
|
|
275
|
+
* `Idempotency-Key` headers on the outbound request, so you can join your own
|
|
276
|
+
* receiving log to this listing exactly.
|
|
277
|
+
*
|
|
278
|
+
* **Deliveries never carry payload bodies.** The event payload can contain
|
|
279
|
+
* customer PII, so it is not returned here — use the correlation fields below
|
|
280
|
+
* to look the subject up through the regular API instead.
|
|
281
|
+
*
|
|
282
|
+
* All correlation fields (`resource_id`, `conversation_id`, `message_id`,
|
|
283
|
+
* `connection_ref`, `channel`, `channel_connection_id`) are derived from the
|
|
284
|
+
* stored event and **fail closed to `null`** whenever no canonical event
|
|
285
|
+
* exists for the delivery — e.g. `test.ping` deliveries, or events that have
|
|
286
|
+
* aged out of retention. Always null-check before using them.
|
|
287
|
+
*/
|
|
288
|
+
interface WebhookDelivery {
|
|
289
|
+
id: string;
|
|
290
|
+
event_type: string;
|
|
291
|
+
/**
|
|
292
|
+
* Primary subject id of the announced event (a message id for message
|
|
293
|
+
* events, a connection id for channel lifecycle events, …), or `null`.
|
|
294
|
+
*/
|
|
295
|
+
resource_id: string | null;
|
|
296
|
+
/** Helpdesk conversation the event belongs to, or `null`. */
|
|
297
|
+
conversation_id: string | null;
|
|
298
|
+
/** Helpdesk message the event belongs to, or `null`. */
|
|
299
|
+
message_id: string | null;
|
|
300
|
+
/** Opaque connection reference carried by the event, or `null`. */
|
|
301
|
+
connection_ref: string | null;
|
|
302
|
+
/** Channel type (e.g. `telegram_inbox`, `widget`), or `null`. */
|
|
303
|
+
channel: string | null;
|
|
304
|
+
/** Channel connection the event belongs to, or `null`. */
|
|
305
|
+
channel_connection_id: string | null;
|
|
306
|
+
status: "pending" | "delivered" | "dead_letter";
|
|
307
|
+
attempt_count: number;
|
|
308
|
+
/** Unix timestamp in milliseconds of the next retry, or `null`. */
|
|
309
|
+
next_attempt_at: number | null;
|
|
310
|
+
response_status: number | null;
|
|
311
|
+
duration_ms: number | null;
|
|
312
|
+
last_error: string | null;
|
|
313
|
+
/** Unix timestamp in milliseconds, or `null` if not delivered. */
|
|
314
|
+
delivered_at: number | null;
|
|
315
|
+
created_at: number;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Options for listing recent deliveries.
|
|
319
|
+
*
|
|
320
|
+
* This endpoint is **not** cursor-paginated — it returns the most recent
|
|
321
|
+
* deliveries only, capped by `limit`. There is no `cursor` parameter; to keep
|
|
322
|
+
* a durable record, ingest deliveries as they arrive (or poll and de-duplicate
|
|
323
|
+
* on the delivery `id`).
|
|
324
|
+
*/
|
|
325
|
+
interface ListDeliveriesOptions {
|
|
326
|
+
limit?: number;
|
|
327
|
+
}
|
|
328
|
+
/** Result returned after queuing a test delivery (HTTP 202). */
|
|
329
|
+
interface WebhookTestResult {
|
|
330
|
+
delivery_id: string;
|
|
331
|
+
status: string;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Capability confirmation types.
|
|
336
|
+
*
|
|
337
|
+
* Medal's confirmable write routes require BOTH an `Idempotency-Key` and an
|
|
338
|
+
* `X-Capability-Confirmation` token whenever the calling credential holds the
|
|
339
|
+
* capability scope *directly* — which is the case for every correctly-scoped
|
|
340
|
+
* partner key and OAuth grant. (API keys carrying only legacy scopes are
|
|
341
|
+
* exempt.) The token is minted by `POST /api/v1/capability-confirmations` and
|
|
342
|
+
* is bound to the workspace, the auth subject, the HTTP method + path, the
|
|
343
|
+
* capability's required scopes, and the idempotency key.
|
|
344
|
+
*/
|
|
345
|
+
/**
|
|
346
|
+
* Confirmable capability ids backing the write routes this SDK exposes.
|
|
347
|
+
*
|
|
348
|
+
* Mirrors the server-side capability registry. Each id maps to exactly one
|
|
349
|
+
* method + path template — see {@link CAPABILITY_ROUTES}.
|
|
350
|
+
*/
|
|
351
|
+
declare const CAPABILITY_IDS: readonly ["channel.connect_link.create.execute", "channel.connect_link.revoke.execute", "channel.connection.disconnect.execute", "helpdesk.conversation.reply.execute", "helpdesk.conversation.update.execute", "helpdesk.webhook.create.execute", "helpdesk.webhook.update.execute", "helpdesk.webhook.delete.execute"];
|
|
352
|
+
/** A confirmable capability id backing an SDK write route. */
|
|
353
|
+
type CapabilityId = (typeof CAPABILITY_IDS)[number];
|
|
354
|
+
/** The API route a capability confirms, as registered server-side. */
|
|
355
|
+
interface CapabilityRoute {
|
|
356
|
+
method: "POST" | "PATCH" | "DELETE";
|
|
357
|
+
/** Path template; `{id}` is filled from `path_params.id`. */
|
|
358
|
+
path_template: string;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Method + path template for each confirmable capability.
|
|
362
|
+
*
|
|
363
|
+
* The server resolves the same mapping from its capability registry — this
|
|
364
|
+
* copy exists so the SDK can build human-readable previews and supply
|
|
365
|
+
* `path_params` without a round trip.
|
|
366
|
+
*/
|
|
367
|
+
declare const CAPABILITY_ROUTES: Record<CapabilityId, CapabilityRoute>;
|
|
368
|
+
/** Primitive accepted as a capability path parameter value. */
|
|
369
|
+
type CapabilityPathParamValue = string | number | boolean;
|
|
370
|
+
/** Input for `POST /api/v1/capability-confirmations`. */
|
|
371
|
+
interface IssueCapabilityConfirmationInput {
|
|
372
|
+
/**
|
|
373
|
+
* Capability to confirm. Unknown ids are rejected with
|
|
374
|
+
* `CAPABILITY_NOT_FOUND`; read-only or non-confirmable capabilities with
|
|
375
|
+
* `CAPABILITY_NOT_CONFIRMABLE`.
|
|
376
|
+
*/
|
|
377
|
+
capability_id: CapabilityId | (string & {});
|
|
378
|
+
/**
|
|
379
|
+
* Concrete `/api/v1/...` path the token should be bound to. Optional when
|
|
380
|
+
* the capability has exactly one API target (all capabilities in
|
|
381
|
+
* {@link CAPABILITY_ROUTES} do); required when it has several. Must match a
|
|
382
|
+
* path built from the capability's own templates.
|
|
383
|
+
*/
|
|
384
|
+
api_path?: string;
|
|
385
|
+
/** Values for the capability path template's parameters, e.g. `{ id: 'wh_1' }`. */
|
|
386
|
+
path_params?: Record<string, CapabilityPathParamValue>;
|
|
387
|
+
/**
|
|
388
|
+
* The exact `Idempotency-Key` you will send on the confirmed write. The
|
|
389
|
+
* token is bound to it — a mismatch is rejected. Required for every
|
|
390
|
+
* capability in {@link CAPABILITY_ROUTES}.
|
|
391
|
+
*/
|
|
392
|
+
idempotency_key?: string;
|
|
393
|
+
/**
|
|
394
|
+
* Human-readable description of the action being approved (1–4000 chars).
|
|
395
|
+
* This is the text your user saw and approved, and it is retained for audit.
|
|
396
|
+
*/
|
|
397
|
+
preview_summary: string;
|
|
398
|
+
/**
|
|
399
|
+
* Must be `true`.
|
|
400
|
+
*
|
|
401
|
+
* **This asserts that a human on your side approved this specific action.**
|
|
402
|
+
* Do not send it to rubber-stamp unattended writes — it is the audit record
|
|
403
|
+
* that a person, not a script, authorised the change.
|
|
404
|
+
*/
|
|
405
|
+
user_approved: true;
|
|
406
|
+
}
|
|
407
|
+
/** A minted capability confirmation token. */
|
|
408
|
+
interface CapabilityConfirmation {
|
|
409
|
+
/** Send this as the `X-Capability-Confirmation` header on the write. */
|
|
410
|
+
confirmation_token: string;
|
|
411
|
+
token_type: "medal_capability_confirmation";
|
|
412
|
+
capability_id: string;
|
|
413
|
+
/** HTTP method the token is bound to. */
|
|
414
|
+
method: string;
|
|
415
|
+
/** Concrete API path the token is bound to. */
|
|
416
|
+
path: string;
|
|
417
|
+
/** Capability scopes the token was minted against. */
|
|
418
|
+
required_scopes: string[];
|
|
419
|
+
/** Idempotency key the token is bound to, or `null` if it was minted unbound. */
|
|
420
|
+
idempotency_key: string | null;
|
|
421
|
+
/** Lifetime in seconds (60–900). */
|
|
422
|
+
expires_in: number;
|
|
423
|
+
/** ISO-8601 expiry timestamp. */
|
|
424
|
+
expires_at: string;
|
|
425
|
+
/** Echo of the submitted `preview_summary`. */
|
|
426
|
+
preview_summary: string;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Request body type for each confirmable capability.
|
|
430
|
+
*
|
|
431
|
+
* `undefined` for routes that take no request body (the `DELETE` routes).
|
|
432
|
+
*/
|
|
433
|
+
interface CapabilityWriteBodies {
|
|
434
|
+
"channel.connect_link.create.execute": CreateConnectLinkInput;
|
|
435
|
+
"channel.connect_link.revoke.execute": undefined;
|
|
436
|
+
"channel.connection.disconnect.execute": undefined;
|
|
437
|
+
"helpdesk.conversation.reply.execute": CreateReplyInput;
|
|
438
|
+
"helpdesk.conversation.update.execute": UpdateConversationInput;
|
|
439
|
+
"helpdesk.webhook.create.execute": CreateWebhookInput;
|
|
440
|
+
"helpdesk.webhook.update.execute": UpdateWebhookInput;
|
|
441
|
+
"helpdesk.webhook.delete.execute": undefined;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* A capability paired with the request body for that exact route.
|
|
445
|
+
*
|
|
446
|
+
* Modelled as a discriminated union rather than two independent parameters so
|
|
447
|
+
* the pair cannot be decoupled: passing a `helpdesk.conversation.reply.execute`
|
|
448
|
+
* id alongside a webhook payload is a compile error, even when the id's static
|
|
449
|
+
* type is the full {@link CapabilityId} union.
|
|
450
|
+
*/
|
|
451
|
+
type CapabilityWriteRequest = {
|
|
452
|
+
[K in CapabilityId]: {
|
|
453
|
+
/** Capability about to be confirmed. */
|
|
454
|
+
capabilityId: K;
|
|
455
|
+
/** The request body of the pending write, or `undefined` for `DELETE` routes. */
|
|
456
|
+
body: CapabilityWriteBodies[K];
|
|
133
457
|
};
|
|
458
|
+
}[CapabilityId];
|
|
459
|
+
/** Fields common to every {@link AutoConfirmContext} variant. */
|
|
460
|
+
interface AutoConfirmContextBase {
|
|
461
|
+
/** HTTP method of the write. */
|
|
462
|
+
method: string;
|
|
463
|
+
/** Resolved API path of the write (path params substituted + encoded). */
|
|
464
|
+
path: string;
|
|
465
|
+
/** Path parameters used to resolve `path`, if any. */
|
|
466
|
+
pathParams?: Record<string, CapabilityPathParamValue>;
|
|
467
|
+
/** Idempotency key that will be bound to the token and sent on the write. */
|
|
468
|
+
idempotencyKey: string;
|
|
134
469
|
}
|
|
135
|
-
/**
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
470
|
+
/**
|
|
471
|
+
* Context handed to an {@link AutoConfirmOptions.previewSummary} callback.
|
|
472
|
+
*
|
|
473
|
+
* A discriminated union on `capabilityId` — narrow on it to get the exact
|
|
474
|
+
* `body` type for that route:
|
|
475
|
+
*
|
|
476
|
+
* ```ts
|
|
477
|
+
* previewSummary: (ctx) => {
|
|
478
|
+
* if (ctx.capabilityId === 'helpdesk.conversation.reply.execute') {
|
|
479
|
+
* // ctx.body is CreateReplyInput here
|
|
480
|
+
* return `Reply to ${ctx.body.conversation_id}: ${ctx.body.body}`;
|
|
481
|
+
* }
|
|
482
|
+
* return `${ctx.method} ${ctx.path}`;
|
|
483
|
+
* }
|
|
484
|
+
* ```
|
|
485
|
+
*
|
|
486
|
+
* `body` is the **exact object you passed to the SDK method**, by reference
|
|
487
|
+
* and unmodified — it is your own payload, so there is nothing to redact and
|
|
488
|
+
* nothing crosses a tenant boundary. Treat it as read-only: mutating it from
|
|
489
|
+
* the callback would change what is actually sent.
|
|
490
|
+
*/
|
|
491
|
+
type AutoConfirmContext = AutoConfirmContextBase & CapabilityWriteRequest;
|
|
492
|
+
/**
|
|
493
|
+
* Opt-in auto-confirmation.
|
|
494
|
+
*
|
|
495
|
+
* When configured, the SDK mints an idempotency key and a confirmation token
|
|
496
|
+
* for you before each confirmable write, then attaches both headers.
|
|
497
|
+
*
|
|
498
|
+
* **This is not a bypass.** Every minted token carries
|
|
499
|
+
* `user_approved: true`, which asserts that *your own user* approved that
|
|
500
|
+
* specific action — the `preview_summary` you return is the audit record of
|
|
501
|
+
* what they approved. Only enable this on a code path where a human really did
|
|
502
|
+
* approve the write. Never wire it into unattended automation.
|
|
503
|
+
*/
|
|
504
|
+
interface AutoConfirmOptions {
|
|
505
|
+
/**
|
|
506
|
+
* Build the `preview_summary` for the pending write. Must return a
|
|
507
|
+
* non-empty string describing what the user approved; returning blank text
|
|
508
|
+
* throws instead of asserting an approval that has no description.
|
|
509
|
+
*
|
|
510
|
+
* The context includes the pending request `body`, so the summary can name
|
|
511
|
+
* the specific action rather than the route — narrow on
|
|
512
|
+
* `context.capabilityId` to get the exact payload type. Prefer a
|
|
513
|
+
* payload-aware summary: `"Reply to conv_1: 'Refund issued'"` is an audit
|
|
514
|
+
* record, `"POST /api/v1/helpdesk/replies"` is not.
|
|
515
|
+
*
|
|
516
|
+
* The server caps `preview_summary` at 4000 characters, so summarise the
|
|
517
|
+
* payload rather than serialising it wholesale.
|
|
518
|
+
*/
|
|
519
|
+
previewSummary: (context: AutoConfirmContext) => string;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/** Configuration for the low-level HTTP client. */
|
|
523
|
+
interface ClientConfig {
|
|
524
|
+
baseUrl: string;
|
|
525
|
+
token: string;
|
|
526
|
+
workspaceId?: string;
|
|
527
|
+
timeout: number;
|
|
528
|
+
userAgent: string;
|
|
529
|
+
}
|
|
530
|
+
/** Per-request options for write operations. */
|
|
531
|
+
interface RequestOptions {
|
|
532
|
+
/**
|
|
533
|
+
* Idempotency key sent as the `Idempotency-Key` header. Retries with the
|
|
534
|
+
* same key return the original result instead of repeating the operation.
|
|
535
|
+
* Required by some endpoints for capability-scoped tokens (e.g. helpdesk
|
|
536
|
+
* replies, webhook creation).
|
|
537
|
+
*/
|
|
538
|
+
idempotencyKey?: string;
|
|
539
|
+
/**
|
|
540
|
+
* Capability confirmation token sent as the `X-Capability-Confirmation`
|
|
541
|
+
* header. Required alongside `idempotencyKey` when a token granted a
|
|
542
|
+
* capability-style scope directly (e.g. `helpdesk.webhook.manage`) executes
|
|
543
|
+
* a confirmable write route. Obtain one from
|
|
544
|
+
* `POST /api/v1/capability-confirmations`. API keys with legacy scopes do
|
|
545
|
+
* not need it.
|
|
546
|
+
*/
|
|
547
|
+
capabilityConfirmation?: string;
|
|
548
|
+
/**
|
|
549
|
+
* Opt in to (or out of) automatic capability confirmation for this call.
|
|
550
|
+
*
|
|
551
|
+
* Supply `{ previewSummary }` to have the SDK mint the idempotency key and
|
|
552
|
+
* the `X-Capability-Confirmation` token itself; pass `false` to suppress a
|
|
553
|
+
* client-level `autoConfirmCapabilities` default. Defaults to the client
|
|
554
|
+
* setting, which itself defaults to OFF.
|
|
555
|
+
*
|
|
556
|
+
* Auto-confirmation sends `user_approved: true` on your behalf, asserting
|
|
557
|
+
* that a human on your side approved this exact action — only use it where
|
|
558
|
+
* that is true.
|
|
559
|
+
*
|
|
560
|
+
* Ignored on routes that do not require a capability confirmation.
|
|
561
|
+
*/
|
|
562
|
+
autoConfirm?: AutoConfirmOptions | false;
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Low-level HTTP client used by all resource classes.
|
|
566
|
+
* Handles authentication, retries, timeout, and error parsing.
|
|
567
|
+
*/
|
|
568
|
+
declare class BaseClient {
|
|
569
|
+
/** Resolved client configuration. */
|
|
570
|
+
readonly config: ClientConfig;
|
|
571
|
+
constructor(config: ClientConfig);
|
|
572
|
+
/** Execute an authenticated GET request and return the parsed JSON body. */
|
|
573
|
+
get<T>(path: string, params?: Record<string, string | undefined>): Promise<T>;
|
|
574
|
+
/** Execute an authenticated POST request with a JSON body. */
|
|
575
|
+
post<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
|
|
576
|
+
/**
|
|
577
|
+
* Execute a POST that must never execute twice, guaranteeing an
|
|
578
|
+
* `Idempotency-Key`.
|
|
579
|
+
*
|
|
580
|
+
* {@link BaseClient.post} retries 429 and 5xx automatically, so a write
|
|
581
|
+
* whose transaction committed before the gateway failed would otherwise be
|
|
582
|
+
* submitted a second time — booking the same slot twice. A key turns that
|
|
583
|
+
* retry into a replay: the server keys on the key, the workspace, and the
|
|
584
|
+
* method+path, and answers a repeat with the stored response, or 409 while
|
|
585
|
+
* the first attempt is still in flight. Either way the write happens once.
|
|
586
|
+
*
|
|
587
|
+
* The key is minted ONCE here, outside the retry loop in `request`, so every
|
|
588
|
+
* attempt of the same logical call carries the same value — a key minted per
|
|
589
|
+
* attempt would deduplicate nothing. A caller-supplied key always wins, so
|
|
590
|
+
* callers keeping their own records stay in control. See
|
|
591
|
+
* {@link resolveIdempotencyKey} for what counts as supplied.
|
|
592
|
+
*/
|
|
593
|
+
postOnce<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
|
|
594
|
+
/** Execute an authenticated PATCH request with a JSON body. */
|
|
595
|
+
patch<T>(path: string, body: unknown, options?: RequestOptions): Promise<T>;
|
|
596
|
+
/** Execute an authenticated DELETE request. */
|
|
597
|
+
delete<T>(path: string, options?: RequestOptions): Promise<T>;
|
|
598
|
+
private writeHeaders;
|
|
599
|
+
private buildUrl;
|
|
600
|
+
private request;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* A timestamp on the way IN to the booking API: Unix milliseconds, or an
|
|
605
|
+
* ISO 8601 date-time string.
|
|
606
|
+
*
|
|
607
|
+
* Both are accepted so a caller can book by echoing back the `start_ts` of a
|
|
608
|
+
* slot it just fetched — responses render every timestamp as ISO, requests
|
|
609
|
+
* take either. The API normalises to milliseconds server-side.
|
|
610
|
+
*/
|
|
611
|
+
type BookingTimestampInput = number | string;
|
|
612
|
+
/** Lifecycle state of a booking. */
|
|
613
|
+
type BookingStatus = "pending" | "confirmed" | "completed" | "cancelled" | "no_show";
|
|
614
|
+
/**
|
|
615
|
+
* Who cancelled a booking. `staff` is a cancel made through
|
|
616
|
+
* `bookings.cancel(id)`, `customer` one made through
|
|
617
|
+
* `bookings.manage.cancel(token)`, `system` an automated one.
|
|
618
|
+
*/
|
|
619
|
+
type BookingCancelledBy = "customer" | "staff" | "system";
|
|
620
|
+
/** Payment state of a booking. */
|
|
621
|
+
type BookingPaymentStatus = "none" | "reserved" | "captured" | "refunded";
|
|
622
|
+
/** Surface a booking was created through. API-created bookings are `api`. */
|
|
623
|
+
type BookingCreatedVia = "web" | "dashboard" | "walk_in" | "api";
|
|
624
|
+
/** Kind of bookable resource a booking lands on. */
|
|
625
|
+
type BookingResourceType = "staff" | "room" | "equipment";
|
|
626
|
+
/**
|
|
627
|
+
* One appointment: a contact holding a service slot on a resource.
|
|
628
|
+
*
|
|
629
|
+
* Money is `amount_ore` — an INTEGER number of øre, never a float and never
|
|
630
|
+
* kroner. Timestamps are ISO 8601 strings.
|
|
631
|
+
*/
|
|
632
|
+
interface Booking {
|
|
633
|
+
id: string;
|
|
634
|
+
contact_id: string | null;
|
|
635
|
+
service_id: string | null;
|
|
636
|
+
resource_id: string | null;
|
|
637
|
+
start_ts: string | null;
|
|
638
|
+
end_ts: string | null;
|
|
639
|
+
booked_for_name: string | null;
|
|
640
|
+
/** Birth year (not a birthdate) of whoever the appointment is for. */
|
|
641
|
+
booked_for_birth_year: number | null;
|
|
642
|
+
/** Shared by every booking created in the same party request. */
|
|
643
|
+
party_sequence_id: string | null;
|
|
644
|
+
status: BookingStatus;
|
|
645
|
+
cancelled_by: BookingCancelledBy | null;
|
|
646
|
+
cancel_reason: string | null;
|
|
647
|
+
/** Set on the booking a reschedule created, pointing at the one it replaced. */
|
|
648
|
+
rescheduled_from_id: string | null;
|
|
649
|
+
payment_status: BookingPaymentStatus;
|
|
650
|
+
/** Price in integer øre. */
|
|
651
|
+
amount_ore: number | null;
|
|
652
|
+
/** Customer-visible note. */
|
|
653
|
+
notes: string | null;
|
|
654
|
+
/** Staff-only note; never shown to the customer. */
|
|
655
|
+
internal_notes: string | null;
|
|
656
|
+
created_via: BookingCreatedVia | null;
|
|
657
|
+
created_at: string | null;
|
|
658
|
+
updated_at: string | null;
|
|
659
|
+
}
|
|
660
|
+
/** A bookable service in the workspace catalogue. */
|
|
661
|
+
interface BookingService {
|
|
662
|
+
id: string;
|
|
663
|
+
name: string | null;
|
|
664
|
+
description: string | null;
|
|
665
|
+
category: string | null;
|
|
666
|
+
duration_minutes: number | null;
|
|
667
|
+
buffer_before_minutes: number | null;
|
|
668
|
+
buffer_after_minutes: number | null;
|
|
669
|
+
/** Price in integer øre. */
|
|
670
|
+
price_ore: number | null;
|
|
671
|
+
weekend_surcharge_pct: number | null;
|
|
672
|
+
/** Resource types this service needs, e.g. `["staff"]`. */
|
|
673
|
+
resource_requirements: string[];
|
|
674
|
+
bookable_online: boolean;
|
|
675
|
+
max_per_booking: number | null;
|
|
676
|
+
color: string | null;
|
|
677
|
+
sort_order: number | null;
|
|
678
|
+
active: boolean;
|
|
679
|
+
created_at: string | null;
|
|
680
|
+
updated_at: string | null;
|
|
681
|
+
}
|
|
682
|
+
/** Who or what performs a service: a staff member, a room, or equipment. */
|
|
683
|
+
interface BookingResource {
|
|
684
|
+
id: string;
|
|
685
|
+
type: BookingResourceType | null;
|
|
686
|
+
name: string | null;
|
|
687
|
+
photo_url: string | null;
|
|
688
|
+
bio: string | null;
|
|
689
|
+
/** Services this resource can perform. */
|
|
690
|
+
service_ids: string[];
|
|
691
|
+
capacity: number | null;
|
|
692
|
+
sort_order: number | null;
|
|
693
|
+
active: boolean;
|
|
694
|
+
created_at: string | null;
|
|
695
|
+
updated_at: string | null;
|
|
696
|
+
}
|
|
697
|
+
/** One free slot returned by `bookings.availability(...)`. */
|
|
698
|
+
interface BookingSlot {
|
|
699
|
+
start_ts: string | null;
|
|
700
|
+
end_ts: string | null;
|
|
701
|
+
resource_id: string | null;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* One date a service can be booked on, from `bookings.schedule(...)`.
|
|
705
|
+
*
|
|
706
|
+
* `date` is the workspace's own calendar date (`YYYY-MM-DD`), not a timestamp.
|
|
707
|
+
* `last_start_ts` is the last start this service could occupy on that date —
|
|
708
|
+
* not the closing time; a 30-minute service at a salon closing 17:00 has its
|
|
709
|
+
* last start at 16:30 — and is `null` for a date with posted hours that is
|
|
710
|
+
* shut outright (a public holiday). A date ABSENT from the list is closed.
|
|
711
|
+
*/
|
|
712
|
+
interface BookingScheduleDay {
|
|
713
|
+
date: string | null;
|
|
714
|
+
opens_ts: string | null;
|
|
715
|
+
closes_ts: string | null;
|
|
716
|
+
last_start_ts: string | null;
|
|
717
|
+
}
|
|
718
|
+
/** One line of a party booking — a single service on a single slot. */
|
|
719
|
+
interface CreateBookingItemInput {
|
|
720
|
+
service_id: string;
|
|
721
|
+
/** Leave unset to let the engine pick a free resource. */
|
|
722
|
+
resource_id?: string;
|
|
723
|
+
start_ts: BookingTimestampInput;
|
|
724
|
+
booked_for_name?: string;
|
|
725
|
+
/** Birth year (not a birthdate) of whoever the appointment is for. */
|
|
726
|
+
booked_for_birth_year?: number;
|
|
727
|
+
}
|
|
728
|
+
/** The person the booking is made under. Phone is the CRM dedupe key. */
|
|
729
|
+
interface BookingContactInput {
|
|
730
|
+
phone: string;
|
|
731
|
+
email?: string;
|
|
732
|
+
name?: string;
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Input for `bookings.create(...)`. `items` is a PARTY — one request books a
|
|
736
|
+
* whole family in one all-or-nothing transaction (max 50 items).
|
|
737
|
+
*/
|
|
738
|
+
/** Provenance an API caller may claim. `dashboard` and `walk_in` are staff-only and rejected. */
|
|
739
|
+
type BookingClaimableCreatedVia = "web" | "api";
|
|
740
|
+
interface CreateBookingInput {
|
|
741
|
+
items: CreateBookingItemInput[];
|
|
742
|
+
contact: BookingContactInput;
|
|
743
|
+
notes?: string;
|
|
744
|
+
/**
|
|
745
|
+
* Defaults to `api`. A workspace's OWN website should send `web`, so "how
|
|
746
|
+
* many bookings did the site bring in" is answerable; integrations leave it
|
|
747
|
+
* unset. `dashboard` and `walk_in` are refused with 400 — a bearer token
|
|
748
|
+
* proves which workspace is calling, not that a member typed it in.
|
|
749
|
+
*/
|
|
750
|
+
created_via?: BookingClaimableCreatedVia;
|
|
751
|
+
}
|
|
752
|
+
/** One entry of `BookingCreateResult.bookings`, in request order. */
|
|
753
|
+
interface CreatedBooking {
|
|
754
|
+
id: string;
|
|
755
|
+
/**
|
|
756
|
+
* Show-once secret for the customer's manage link.
|
|
757
|
+
*
|
|
758
|
+
* Only its SHA-256 hash is stored, so this response is the ONLY place it
|
|
759
|
+
* ever appears — persist it here or it is gone. It is **absent** (the key is
|
|
760
|
+
* dropped, not nulled) when the response is replayed from an
|
|
761
|
+
* `Idempotency-Key`, since tokens are redacted from replays.
|
|
762
|
+
*
|
|
763
|
+
* A lost token cannot be recovered: {@link Booking} carries no token field,
|
|
764
|
+
* so re-reading the booking will not produce it. Either reschedule the
|
|
765
|
+
* booking (which mints a fresh token) or have staff act on it by id.
|
|
766
|
+
*/
|
|
767
|
+
manage_token?: string;
|
|
768
|
+
}
|
|
769
|
+
/** Result returned after creating a booking or party. */
|
|
770
|
+
interface BookingCreateResult {
|
|
771
|
+
bookings: CreatedBooking[];
|
|
772
|
+
contact_id: string;
|
|
773
|
+
}
|
|
774
|
+
/** Result of a cancel or no-show. */
|
|
775
|
+
interface BookingActionResult {
|
|
776
|
+
success: true;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Result of a reschedule. A reschedule cancels the old booking and inserts a
|
|
780
|
+
* new one, so `booking_id` is a NEW id — the one you passed in is now cancelled.
|
|
781
|
+
*/
|
|
782
|
+
interface BookingRescheduleResult {
|
|
783
|
+
success: true;
|
|
784
|
+
booking_id: string;
|
|
785
|
+
/**
|
|
786
|
+
* Freshly minted manage token for the new booking. Same show-once rule as
|
|
787
|
+
* {@link CreatedBooking.manage_token}: absent on an idempotent replay.
|
|
788
|
+
*/
|
|
789
|
+
manage_token?: string;
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* What the holder of a manage token may see and do — the payload behind a
|
|
793
|
+
* customer's "manage my booking" link.
|
|
794
|
+
*
|
|
795
|
+
* `can_cancel` / `can_reschedule` already account for the workspace's policy
|
|
796
|
+
* windows, so honour them rather than re-deriving from the window hours.
|
|
797
|
+
*/
|
|
798
|
+
interface ManageSummary {
|
|
799
|
+
booking_id: string;
|
|
800
|
+
contact_id: string | null;
|
|
801
|
+
status: BookingStatus | null;
|
|
802
|
+
cancelled_by: BookingCancelledBy | null;
|
|
803
|
+
cancel_reason: string | null;
|
|
804
|
+
rescheduled_from_id: string | null;
|
|
805
|
+
start_ts: string | null;
|
|
806
|
+
end_ts: string | null;
|
|
807
|
+
service_id: string | null;
|
|
808
|
+
service_name: string | null;
|
|
809
|
+
resource_id: string | null;
|
|
810
|
+
resource_name: string | null;
|
|
811
|
+
booked_for_name: string | null;
|
|
812
|
+
party_sequence_id: string | null;
|
|
813
|
+
/** Price in integer øre. */
|
|
814
|
+
amount_ore: number | null;
|
|
815
|
+
payment_status: BookingPaymentStatus | null;
|
|
816
|
+
/** IANA zone the booking's local times should be rendered in. */
|
|
817
|
+
time_zone: string | null;
|
|
818
|
+
cancel_window_hours: number | null;
|
|
819
|
+
reschedule_window_hours: number | null;
|
|
820
|
+
can_cancel: boolean;
|
|
821
|
+
can_reschedule: boolean;
|
|
822
|
+
}
|
|
823
|
+
/** The annotation fields a booking accepts. */
|
|
824
|
+
interface BookingNoteFields {
|
|
825
|
+
/** Customer-visible note. Pass `""` to clear it. */
|
|
826
|
+
notes?: string;
|
|
827
|
+
/** Staff-only note; never shown to the customer. Pass `""` to clear it. */
|
|
828
|
+
internal_notes?: string;
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Input for annotating a booking.
|
|
832
|
+
*
|
|
833
|
+
* At least one of `notes` or `internal_notes` must be present: the API's
|
|
834
|
+
* `updateBookingSchema` refuses a body carrying neither with a 400, so the
|
|
835
|
+
* union turns `update(id, {})` into a compile error rather than a wasted round
|
|
836
|
+
* trip. Note that `""` is a meaningful value — it clears the field — which is
|
|
837
|
+
* why the constraint is on presence, not on emptiness.
|
|
838
|
+
*/
|
|
839
|
+
type UpdateBookingInput = (BookingNoteFields & {
|
|
840
|
+
notes: string;
|
|
841
|
+
}) | (BookingNoteFields & {
|
|
842
|
+
internal_notes: string;
|
|
843
|
+
});
|
|
844
|
+
/** Optional reason recorded against a cancellation. */
|
|
845
|
+
interface CancelBookingInput {
|
|
846
|
+
reason?: string;
|
|
847
|
+
}
|
|
848
|
+
/** Input for moving a booking to a new slot, and optionally a new resource. */
|
|
849
|
+
interface RescheduleBookingInput {
|
|
850
|
+
new_start_ts: BookingTimestampInput;
|
|
851
|
+
new_resource_id?: string;
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Pagination for a bookings page.
|
|
855
|
+
*
|
|
856
|
+
* `truncated` is the extra statement this list carries: the underlying read is
|
|
857
|
+
* capped, and when the cap binds there are matching bookings that no cursor
|
|
858
|
+
* from this call reaches. Narrow `from_ts`/`to_ts` when you see it.
|
|
859
|
+
*/
|
|
860
|
+
interface BookingsPagination {
|
|
861
|
+
has_more: boolean;
|
|
862
|
+
next_cursor: string | null;
|
|
863
|
+
truncated: boolean;
|
|
864
|
+
}
|
|
865
|
+
/** A page of bookings. Carries `truncated` on top of the usual pagination. */
|
|
866
|
+
interface BookingsPage {
|
|
867
|
+
data: Booking[];
|
|
868
|
+
pagination: BookingsPagination;
|
|
869
|
+
}
|
|
870
|
+
/** Options for listing bookings with pagination and filters. */
|
|
871
|
+
interface ListBookingsOptions extends PaginationOptions {
|
|
872
|
+
from_ts?: BookingTimestampInput;
|
|
873
|
+
to_ts?: BookingTimestampInput;
|
|
874
|
+
status?: BookingStatus;
|
|
875
|
+
resource_id?: string;
|
|
876
|
+
}
|
|
877
|
+
/** Options for listing the service catalogue. The endpoint is not paginated. */
|
|
878
|
+
interface ListBookingServicesOptions {
|
|
879
|
+
/** Include services with `active: false`. Defaults to active-only. */
|
|
880
|
+
include_inactive?: boolean;
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Options for `bookings.schedule(...)`. Same shape as availability, and for
|
|
884
|
+
* the same reason: the last bookable start depends on the service's duration
|
|
885
|
+
* and buffers, so a 30-minute cut and a 90-minute colour run out at different
|
|
886
|
+
* hours of the same afternoon.
|
|
887
|
+
*/
|
|
888
|
+
interface BookingScheduleOptions {
|
|
889
|
+
service_id: string;
|
|
890
|
+
from_ts: BookingTimestampInput;
|
|
891
|
+
/** Must be after `from_ts`. */
|
|
892
|
+
to_ts: BookingTimestampInput;
|
|
893
|
+
/** Restrict to one resource's hours. */
|
|
894
|
+
resource_id?: string;
|
|
895
|
+
}
|
|
896
|
+
/** Options for querying free slots. The window is required and half-open. */
|
|
897
|
+
interface BookingAvailabilityOptions {
|
|
898
|
+
service_id: string;
|
|
899
|
+
from_ts: BookingTimestampInput;
|
|
900
|
+
/** Must be after `from_ts`. */
|
|
901
|
+
to_ts: BookingTimestampInput;
|
|
902
|
+
/** Restrict slots to one resource. Defaults to every capable resource. */
|
|
903
|
+
resource_id?: string;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* Customer-side booking management, addressed by the show-once manage token
|
|
908
|
+
* from `bookings.create(...)` rather than by booking id.
|
|
909
|
+
*
|
|
910
|
+
* These are NOT the staff routes with a different lookup key: possession of
|
|
911
|
+
* the token is the customer's own authorization, so the workspace's cancel and
|
|
912
|
+
* reschedule windows are ENFORCED here (they are bypassed on
|
|
913
|
+
* `bookings.cancel` / `bookings.reschedule`), and a cancel is attributed to
|
|
914
|
+
* the customer rather than to staff. Relay a customer's click on their
|
|
915
|
+
* confirmation-email link through these; act as the business through the
|
|
916
|
+
* id-addressed methods.
|
|
917
|
+
*/
|
|
918
|
+
declare class BookingsManage {
|
|
919
|
+
private client;
|
|
920
|
+
constructor(client: BaseClient);
|
|
921
|
+
/**
|
|
922
|
+
* Read what the holder of a manage token may see and do. Honour
|
|
923
|
+
* `can_cancel` / `can_reschedule` — they already apply the policy windows.
|
|
924
|
+
*/
|
|
925
|
+
get(token: string): Promise<ApiResponse<ManageSummary>>;
|
|
926
|
+
/** Cancel on the customer's behalf. Rejected outside the cancel window. */
|
|
927
|
+
cancel(token: string, input?: CancelBookingInput, options?: RequestOptions): Promise<ApiResponse<BookingActionResult>>;
|
|
928
|
+
/**
|
|
929
|
+
* Move the booking on the customer's behalf. Rejected outside the reschedule
|
|
930
|
+
* window. Returns a NEW booking id and a new manage token — the old token
|
|
931
|
+
* stops working, so relay the new one into whatever link you send next.
|
|
932
|
+
*/
|
|
933
|
+
reschedule(token: string, input: RescheduleBookingInput, options?: RequestOptions): Promise<ApiResponse<BookingRescheduleResult>>;
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Appointment bookings: the service catalogue, free slots, and the bookings
|
|
937
|
+
* themselves.
|
|
938
|
+
*
|
|
939
|
+
* Every method here acts as the BUSINESS — policy windows are bypassed and a
|
|
940
|
+
* cancel is recorded against staff. To relay a customer's own action on their
|
|
941
|
+
* confirmation-email link, use {@link Bookings.manage} instead.
|
|
942
|
+
*
|
|
943
|
+
* Money is always integer øre (`amount_ore`, `price_ore`). Timestamps come
|
|
944
|
+
* back as ISO 8601 strings; on the way in, either Unix milliseconds or an ISO
|
|
945
|
+
* string is accepted.
|
|
946
|
+
*
|
|
947
|
+
* @example
|
|
948
|
+
* ```ts
|
|
949
|
+
* const { data: slots } = await medal.bookings.availability({
|
|
950
|
+
* service_id: "svc_1",
|
|
951
|
+
* from_ts: Date.now(),
|
|
952
|
+
* to_ts: Date.now() + 7 * 86_400_000,
|
|
953
|
+
* });
|
|
954
|
+
* const { data } = await medal.bookings.create({
|
|
955
|
+
* items: [{ service_id: "svc_1", start_ts: slots[0].start_ts! }],
|
|
956
|
+
* contact: { phone: "+4790000000", name: "Ida" },
|
|
957
|
+
* });
|
|
958
|
+
* ```
|
|
959
|
+
*/
|
|
960
|
+
declare class Bookings {
|
|
961
|
+
private client;
|
|
962
|
+
/** Customer-side actions addressed by manage token. */
|
|
963
|
+
readonly manage: BookingsManage;
|
|
964
|
+
constructor(client: BaseClient);
|
|
965
|
+
/** List the bookable service catalogue. Active-only unless asked otherwise. */
|
|
966
|
+
listServices(options?: ListBookingServicesOptions): Promise<ApiResponse<BookingService[]>>;
|
|
967
|
+
/** List the bookable resources — staff, rooms, and equipment. */
|
|
968
|
+
listResources(): Promise<ApiResponse<BookingResource[]>>;
|
|
969
|
+
/**
|
|
970
|
+
* List free slots for a service over a window. Slots reflect opening hours,
|
|
971
|
+
* time off, buffers, and existing bookings at the moment of the call — they
|
|
972
|
+
* are not held, so a slot can be taken before you book it.
|
|
973
|
+
*/
|
|
974
|
+
availability(options: BookingAvailabilityOptions): Promise<ApiResponse<BookingSlot[]>>;
|
|
975
|
+
/**
|
|
976
|
+
* The dates a service can be booked on — the half `availability` cannot
|
|
977
|
+
* answer. Availability returns free slots and nothing else, so a closed day,
|
|
978
|
+
* an evening past closing and a fully booked day are all the same empty
|
|
979
|
+
* array. A date absent from this list is closed; on a listed date, compare
|
|
980
|
+
* `last_start_ts` against the clock to tell "too late today" from "full".
|
|
981
|
+
*/
|
|
982
|
+
schedule(options: BookingScheduleOptions): Promise<ApiResponse<BookingScheduleDay[]>>;
|
|
983
|
+
/**
|
|
984
|
+
* List bookings with cursor-based pagination and optional filters.
|
|
985
|
+
*
|
|
986
|
+
* Check `pagination.truncated`: when true the read window was clipped and
|
|
987
|
+
* matching bookings exist that no cursor reaches — narrow `from_ts`/`to_ts`.
|
|
988
|
+
*/
|
|
989
|
+
list(options?: ListBookingsOptions): Promise<BookingsPage>;
|
|
990
|
+
/**
|
|
991
|
+
* Book a party — every item succeeds or none do (max 50).
|
|
992
|
+
*
|
|
993
|
+
* Each created booking comes back with a `manage_token` exactly once; only
|
|
994
|
+
* its hash is stored, so persist it if you need the customer's manage link.
|
|
995
|
+
*
|
|
996
|
+
* Automatically idempotent: the SDK mints an `Idempotency-Key` so its own
|
|
997
|
+
* 5xx retries replay rather than book the slot twice. Supply
|
|
998
|
+
* `options.idempotencyKey` to deduplicate across your OWN retries too — the
|
|
999
|
+
* server keys on it for 24 hours, so re-sending the same key after a network
|
|
1000
|
+
* timeout returns the original bookings instead of a second set.
|
|
1001
|
+
*/
|
|
1002
|
+
create(input: CreateBookingInput, options?: RequestOptions): Promise<ApiResponse<BookingCreateResult>>;
|
|
1003
|
+
/** Get a booking by ID. */
|
|
1004
|
+
get(id: string): Promise<ApiResponse<Booking>>;
|
|
1005
|
+
/**
|
|
1006
|
+
* Annotate a booking. At least one of `notes` (customer-visible) or
|
|
1007
|
+
* `internal_notes` (staff-only) is required; `""` clears a field.
|
|
1008
|
+
*/
|
|
1009
|
+
update(id: string, input: UpdateBookingInput, options?: RequestOptions): Promise<ApiResponse<Booking>>;
|
|
1010
|
+
/** Cancel as the business — the cancel window is bypassed. */
|
|
1011
|
+
cancel(id: string, input?: CancelBookingInput, options?: RequestOptions): Promise<ApiResponse<BookingActionResult>>;
|
|
1012
|
+
/**
|
|
1013
|
+
* Move a booking as the business — the reschedule window is bypassed.
|
|
1014
|
+
* Returns a NEW booking id and a new manage token; the old booking is
|
|
1015
|
+
* cancelled and its token stops working.
|
|
1016
|
+
*/
|
|
1017
|
+
reschedule(id: string, input: RescheduleBookingInput, options?: RequestOptions): Promise<ApiResponse<BookingRescheduleResult>>;
|
|
1018
|
+
/** Mark a booking as a no-show. */
|
|
1019
|
+
markNoShow(id: string, options?: RequestOptions): Promise<ApiResponse<BookingActionResult>>;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* Mint short-lived capability confirmation tokens.
|
|
1024
|
+
*
|
|
1025
|
+
* Medal's confirmable write routes (connect links, channel connections,
|
|
1026
|
+
* helpdesk replies/updates, webhook endpoint writes) require BOTH an
|
|
1027
|
+
* `Idempotency-Key` and an `X-Capability-Confirmation` header when the calling
|
|
1028
|
+
* credential holds the capability scope *directly* — which is the case for
|
|
1029
|
+
* every correctly-scoped partner key. This resource issues that header value.
|
|
1030
|
+
*
|
|
1031
|
+
* @example Explicit flow
|
|
1032
|
+
* ```ts
|
|
1033
|
+
* const idempotencyKey = crypto.randomUUID();
|
|
1034
|
+
* const { data: confirmation } = await medal.capabilityConfirmations.create({
|
|
1035
|
+
* capability_id: 'channel.connect_link.create.execute',
|
|
1036
|
+
* idempotency_key: idempotencyKey,
|
|
1037
|
+
* preview_summary: 'Mint a Telegram connect link for Acme Support',
|
|
1038
|
+
* user_approved: true, // a human on your side approved this exact action
|
|
1039
|
+
* });
|
|
1040
|
+
*
|
|
1041
|
+
* await medal.channels.connectLinks.create(
|
|
1042
|
+
* { channel_type: 'telegram_inbox', label: 'Acme Support' },
|
|
1043
|
+
* { idempotencyKey, capabilityConfirmation: confirmation.confirmation_token },
|
|
1044
|
+
* );
|
|
1045
|
+
* ```
|
|
1046
|
+
*/
|
|
1047
|
+
declare class CapabilityConfirmations {
|
|
1048
|
+
private client;
|
|
1049
|
+
constructor(client: BaseClient);
|
|
1050
|
+
/**
|
|
1051
|
+
* Issue a confirmation token for one pending write.
|
|
1052
|
+
*
|
|
1053
|
+
* The token is bound to the workspace, the auth subject, the capability's
|
|
1054
|
+
* method + path, its required scopes, and `idempotency_key` — so it is
|
|
1055
|
+
* usable exactly once, for exactly the write it describes, and expires
|
|
1056
|
+
* within 15 minutes.
|
|
1057
|
+
*
|
|
1058
|
+
* Setting `user_approved: true` asserts that a human on your side approved
|
|
1059
|
+
* this specific action. `preview_summary` is what they approved, and is
|
|
1060
|
+
* retained for audit — write it for a human reader, not a log parser.
|
|
1061
|
+
*
|
|
1062
|
+
* Deliberately unkeyed, unlike the writes it authorizes. Minting is not the
|
|
1063
|
+
* state change the guarantee exists to protect: the write itself is already
|
|
1064
|
+
* bound to `idempotency_key`, so a retry that mints a second token cannot
|
|
1065
|
+
* produce a second write. Keying this call would instead park a credential
|
|
1066
|
+
* designed to expire in 15 minutes inside a replay cache that answers for 24
|
|
1067
|
+
* hours — a worse trade than the duplicate token it would avoid.
|
|
1068
|
+
*/
|
|
1069
|
+
create(input: IssueCapabilityConfirmationInput): Promise<ApiResponse<CapabilityConfirmation>>;
|
|
141
1070
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
1071
|
+
|
|
1072
|
+
/**
|
|
1073
|
+
* Resolves the `Idempotency-Key` + `X-Capability-Confirmation` pair required
|
|
1074
|
+
* by confirmable write routes.
|
|
1075
|
+
*
|
|
1076
|
+
* Auto-confirmation is OFF unless the integrator opts in — either globally via
|
|
1077
|
+
* the `Medal` constructor's `autoConfirmCapabilities`, or per call via
|
|
1078
|
+
* `{ autoConfirm: { previewSummary } }`. When it is off this is a pass-through:
|
|
1079
|
+
* whatever headers the caller supplied are what gets sent.
|
|
1080
|
+
*/
|
|
1081
|
+
declare class CapabilityConfirmer {
|
|
1082
|
+
private confirmations;
|
|
1083
|
+
private defaults?;
|
|
1084
|
+
constructor(confirmations: CapabilityConfirmations, defaults?: AutoConfirmOptions | undefined);
|
|
1085
|
+
/**
|
|
1086
|
+
* Return the request options to use for a confirmable write, minting the
|
|
1087
|
+
* idempotency key and confirmation token first when auto-confirm is active.
|
|
1088
|
+
*
|
|
1089
|
+
* `body` is the pending request payload (`undefined` for `DELETE` routes).
|
|
1090
|
+
* It is handed to the `previewSummary` callback by reference so the summary
|
|
1091
|
+
* can describe the specific action, not just the route — it is the caller's
|
|
1092
|
+
* own payload, so it is passed through unmodified and unredacted.
|
|
1093
|
+
*/
|
|
1094
|
+
prepare(request: CapabilityWriteRequest, pathParams?: Record<string, CapabilityPathParamValue>, options?: RequestOptions): Promise<RequestOptions | undefined>;
|
|
146
1095
|
}
|
|
147
1096
|
|
|
148
1097
|
/** Mint, list, and revoke hosted connect links. */
|
|
149
1098
|
declare class ChannelConnectLinks {
|
|
150
1099
|
private client;
|
|
151
|
-
|
|
1100
|
+
private confirmer;
|
|
1101
|
+
constructor(client: BaseClient, confirmer: CapabilityConfirmer);
|
|
152
1102
|
/**
|
|
153
1103
|
* Mint a single-use hosted connect link. Returns HTTP 201.
|
|
154
1104
|
*
|
|
@@ -159,19 +1109,46 @@ declare class ChannelConnectLinks {
|
|
|
159
1109
|
*
|
|
160
1110
|
* Requires the `channel.connect.manage` scope; OAuth callers additionally
|
|
161
1111
|
* need the workspace `admin` role.
|
|
1112
|
+
*
|
|
1113
|
+
* Automatically idempotent: an unkeyed retry mints a SECOND live single-use
|
|
1114
|
+
* link for the same person, and only one of the two can ever be consumed —
|
|
1115
|
+
* the other stays outstanding until it is revoked or expires. The key the
|
|
1116
|
+
* confirmer chose is the key that goes out — a capability confirmation is
|
|
1117
|
+
* bound to its idempotency key, so minting a fresh one here would invalidate
|
|
1118
|
+
* the confirmation.
|
|
162
1119
|
*/
|
|
163
1120
|
create(input: CreateConnectLinkInput, options?: RequestOptions): Promise<ApiResponse<ConnectLinkCreateResult>>;
|
|
164
|
-
/**
|
|
165
|
-
|
|
1121
|
+
/**
|
|
1122
|
+
* List the workspace's connect links (tokens are never returned), newest
|
|
1123
|
+
* first, with cursor-based pagination.
|
|
1124
|
+
*
|
|
1125
|
+
* `limit` defaults to 50 server-side and is capped at 100. Follow
|
|
1126
|
+
* `pagination.next_cursor` while `pagination.has_more` is true.
|
|
1127
|
+
*
|
|
1128
|
+
* The `channel_type` / `status` filters are applied **within** each page,
|
|
1129
|
+
* so a page may hold fewer than `limit` items while `has_more` is still
|
|
1130
|
+
* true — drive the loop off `has_more`, never off the item count.
|
|
1131
|
+
*/
|
|
1132
|
+
list(options?: ListConnectLinksOptions): Promise<PaginatedResponse<ConnectLink>>;
|
|
166
1133
|
/** Revoke a pending connect link so it can no longer be consumed. */
|
|
167
1134
|
revoke(id: string, options?: RequestOptions): Promise<ApiResponse<ConnectLinkRevokeResult>>;
|
|
168
1135
|
}
|
|
169
1136
|
/** List and disconnect the workspace's channel connections. */
|
|
170
1137
|
declare class ChannelConnections {
|
|
171
1138
|
private client;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
1139
|
+
private confirmer;
|
|
1140
|
+
constructor(client: BaseClient, confirmer: CapabilityConfirmer);
|
|
1141
|
+
/**
|
|
1142
|
+
* List the workspace's channel connections (generic, channel-agnostic
|
|
1143
|
+
* shape), newest first, with cursor-based pagination.
|
|
1144
|
+
*
|
|
1145
|
+
* `limit` defaults to 50 server-side and is capped at 100. Follow
|
|
1146
|
+
* `pagination.next_cursor` while `pagination.has_more` is true. Rows that
|
|
1147
|
+
* are not projectable as connections are dropped within the page, so a page
|
|
1148
|
+
* may hold fewer than `limit` items while `has_more` is still true — drive
|
|
1149
|
+
* the loop off `has_more`, never off the item count.
|
|
1150
|
+
*/
|
|
1151
|
+
list(options?: PaginationOptions): Promise<PaginatedResponse<ChannelConnection>>;
|
|
175
1152
|
/**
|
|
176
1153
|
* Disconnect a connected channel account (best-effort platform logout, then
|
|
177
1154
|
* local revoke). Emits a `helpdesk.channel_disconnected` webhook event with
|
|
@@ -188,7 +1165,7 @@ declare class ChannelConnections {
|
|
|
188
1165
|
declare class Channels {
|
|
189
1166
|
readonly connectLinks: ChannelConnectLinks;
|
|
190
1167
|
readonly connections: ChannelConnections;
|
|
191
|
-
constructor(client: BaseClient);
|
|
1168
|
+
constructor(client: BaseClient, confirmer?: CapabilityConfirmer);
|
|
192
1169
|
}
|
|
193
1170
|
|
|
194
1171
|
/** A contact in the workspace CRM. */
|
|
@@ -315,8 +1292,16 @@ declare class Contacts {
|
|
|
315
1292
|
constructor(client: BaseClient);
|
|
316
1293
|
/** List contacts with cursor-based pagination and optional filters. */
|
|
317
1294
|
list(options?: ListContactsOptions): Promise<PaginatedResponse<Contact>>;
|
|
318
|
-
/**
|
|
319
|
-
|
|
1295
|
+
/**
|
|
1296
|
+
* Create a new contact. Email must be unique in the workspace.
|
|
1297
|
+
*
|
|
1298
|
+
* Automatically idempotent: the SDK mints an `Idempotency-Key` so its own
|
|
1299
|
+
* 5xx retries replay rather than run the create a second time. Uniqueness
|
|
1300
|
+
* alone would not save you here — it turns the retry of a committed create
|
|
1301
|
+
* into a spurious conflict, which reads as "the contact was not created".
|
|
1302
|
+
* Supply `options.idempotencyKey` to deduplicate across your OWN retries too.
|
|
1303
|
+
*/
|
|
1304
|
+
create(input: CreateContactInput, options?: RequestOptions): Promise<ApiResponse<ContactCreateResult>>;
|
|
320
1305
|
/** Get a contact by ID. */
|
|
321
1306
|
get(id: string): Promise<ApiResponse<Contact>>;
|
|
322
1307
|
/** Update one or more fields on a contact. */
|
|
@@ -325,10 +1310,23 @@ declare class Contacts {
|
|
|
325
1310
|
remove(id: string): Promise<ApiResponse<ContactRemoveResult>>;
|
|
326
1311
|
/** Get the activity timeline for a contact. */
|
|
327
1312
|
activities(id: string, options?: PaginationOptions): Promise<PaginatedResponse<Activity>>;
|
|
328
|
-
/**
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
1313
|
+
/**
|
|
1314
|
+
* Add a note to a contact's timeline.
|
|
1315
|
+
*
|
|
1316
|
+
* Automatically idempotent: nothing about a note is unique, so an unkeyed
|
|
1317
|
+
* retry appends the same text to the timeline twice. Supply
|
|
1318
|
+
* `options.idempotencyKey` to deduplicate across your OWN retries too.
|
|
1319
|
+
*/
|
|
1320
|
+
addNote(id: string, input: AddNoteInput, options?: RequestOptions): Promise<ApiResponse<ContactNoteResult>>;
|
|
1321
|
+
/**
|
|
1322
|
+
* Bulk import contacts (max 500). Duplicates are skipped.
|
|
1323
|
+
*
|
|
1324
|
+
* Automatically idempotent: the import is processed in chunks, so a retry
|
|
1325
|
+
* after a partial failure re-walks the whole batch and reports `added` /
|
|
1326
|
+
* `skipped` counts for a run that was not the first. Supply
|
|
1327
|
+
* `options.idempotencyKey` to deduplicate across your OWN retries too.
|
|
1328
|
+
*/
|
|
1329
|
+
import(contacts: ImportContactInput[], options?: RequestOptions): Promise<ApiResponse<ImportContactsResult>>;
|
|
332
1330
|
}
|
|
333
1331
|
|
|
334
1332
|
/** A sponsorship or brand deal in the workspace. */
|
|
@@ -407,8 +1405,14 @@ declare class Deals {
|
|
|
407
1405
|
constructor(client: BaseClient);
|
|
408
1406
|
/** List deals with cursor-based pagination and optional filters. */
|
|
409
1407
|
list(options?: ListDealsOptions): Promise<PaginatedResponse<Deal>>;
|
|
410
|
-
/**
|
|
411
|
-
|
|
1408
|
+
/**
|
|
1409
|
+
* Create a new deal.
|
|
1410
|
+
*
|
|
1411
|
+
* Automatically idempotent: nothing about a deal is unique, so an unkeyed
|
|
1412
|
+
* retry puts a second identical deal in the pipeline. Supply
|
|
1413
|
+
* `options.idempotencyKey` to deduplicate across your OWN retries too.
|
|
1414
|
+
*/
|
|
1415
|
+
create(input: CreateDealInput, options?: RequestOptions): Promise<ApiResponse<DealCreateResult>>;
|
|
412
1416
|
/** Get a deal by ID. */
|
|
413
1417
|
get(id: string): Promise<ApiResponse<Deal>>;
|
|
414
1418
|
/** Update one or more fields on a deal. Set contact_id to null to unlink. */
|
|
@@ -550,15 +1554,29 @@ declare class Emails {
|
|
|
550
1554
|
/**
|
|
551
1555
|
* Send a transactional email using a template (HTTP 202). The returned `id`
|
|
552
1556
|
* is an email send id — poll `emails.get(id)` with it to track delivery.
|
|
1557
|
+
*
|
|
1558
|
+
* Automatically idempotent: the SDK mints an `Idempotency-Key` so its own
|
|
1559
|
+
* 5xx retries replay rather than queue a second copy into someone's inbox —
|
|
1560
|
+
* a send that already committed cannot be un-sent. Supply
|
|
1561
|
+
* `options.idempotencyKey` to deduplicate across your OWN retries too.
|
|
1562
|
+
*
|
|
1563
|
+
* `input.idempotency_key` is the older, body-level form of the same control
|
|
1564
|
+
* and still takes precedence server-side, so setting it keeps working
|
|
1565
|
+
* unchanged.
|
|
553
1566
|
*/
|
|
554
|
-
send(input: SendEmailInput): Promise<ApiResponse<EmailSendResult>>;
|
|
1567
|
+
send(input: SendEmailInput, options?: RequestOptions): Promise<ApiResponse<EmailSendResult>>;
|
|
555
1568
|
/** Get the delivery status of a sent email. */
|
|
556
1569
|
get(id: string): Promise<ApiResponse<EmailSend>>;
|
|
557
1570
|
/**
|
|
558
1571
|
* Send the same template to multiple recipients (max 100, HTTP 202). Each
|
|
559
1572
|
* queued recipient gets its own send id in `results` for `emails.get(id)`.
|
|
1573
|
+
*
|
|
1574
|
+
* Automatically idempotent — and this is the call where it matters most: an
|
|
1575
|
+
* unkeyed retry of a batch that already committed sends up to 100 duplicate
|
|
1576
|
+
* emails. Supply `options.idempotencyKey` to deduplicate across your OWN
|
|
1577
|
+
* retries too.
|
|
560
1578
|
*/
|
|
561
|
-
batch(input: BatchSendInput): Promise<ApiResponse<BatchSendSummary>>;
|
|
1579
|
+
batch(input: BatchSendInput, options?: RequestOptions): Promise<ApiResponse<BatchSendSummary>>;
|
|
562
1580
|
}
|
|
563
1581
|
|
|
564
1582
|
/** A workspace data export request and its current status. */
|
|
@@ -630,8 +1648,16 @@ interface CookieCategoryConsent {
|
|
|
630
1648
|
declare class Gdpr {
|
|
631
1649
|
private client;
|
|
632
1650
|
constructor(client: BaseClient);
|
|
633
|
-
/**
|
|
634
|
-
|
|
1651
|
+
/**
|
|
1652
|
+
* Request a workspace data export. Runs asynchronously.
|
|
1653
|
+
*
|
|
1654
|
+
* Automatically idempotent: the request is recorded and the export is
|
|
1655
|
+
* scheduled in one step with no de-duplication of its own, so an unkeyed
|
|
1656
|
+
* retry files a second subject-access request and runs a second full export
|
|
1657
|
+
* of the workspace. Supply `options.idempotencyKey` to deduplicate across
|
|
1658
|
+
* your OWN retries too.
|
|
1659
|
+
*/
|
|
1660
|
+
requestExport(options?: RequestOptions): Promise<ApiResponse<{
|
|
635
1661
|
request_id: string;
|
|
636
1662
|
status: string;
|
|
637
1663
|
}>>;
|
|
@@ -639,105 +1665,36 @@ declare class Gdpr {
|
|
|
639
1665
|
listExports(): Promise<ApiResponse<GdprExport[]>>;
|
|
640
1666
|
/** Get the status of a specific export. */
|
|
641
1667
|
getExport(id: string): Promise<ApiResponse<GdprExport>>;
|
|
642
|
-
/**
|
|
1668
|
+
/**
|
|
1669
|
+
* Record a GDPR consent decision for a contact by email.
|
|
1670
|
+
*
|
|
1671
|
+
* Deliberately unkeyed: a decision is stored once per
|
|
1672
|
+
* (workspace, email, consent type) and overwritten in place, so re-sending
|
|
1673
|
+
* the same body reaches the same state and returns the same record id.
|
|
1674
|
+
*/
|
|
643
1675
|
recordConsent(input: RecordConsentInput): Promise<ApiResponse<ConsentResult>>;
|
|
644
1676
|
/** Get all consent records for a contact by email. */
|
|
645
1677
|
getConsent(email: string): Promise<ApiResponse<ConsentRecord[]>>;
|
|
646
|
-
/**
|
|
1678
|
+
/**
|
|
1679
|
+
* Record cookie consent from an external site (legacy endpoint).
|
|
1680
|
+
*
|
|
1681
|
+
* Deliberately unkeyed: this legacy route predates the versioned API and
|
|
1682
|
+
* does not run the `Idempotency-Key` machinery, so a key here would be a
|
|
1683
|
+
* header that changes nothing while implying a guarantee the endpoint cannot
|
|
1684
|
+
* make. Treat a failed call as "unknown" and re-send only if a missing
|
|
1685
|
+
* consent log matters more to you than a duplicate one.
|
|
1686
|
+
*/
|
|
647
1687
|
cookieConsent(input: CookieConsentInput): Promise<{
|
|
648
1688
|
success: boolean;
|
|
649
1689
|
logId?: string;
|
|
650
1690
|
}>;
|
|
651
1691
|
}
|
|
652
1692
|
|
|
653
|
-
/** Lifecycle status of a helpdesk conversation. */
|
|
654
|
-
type ConversationStatus = "open" | "snoozed" | "closed";
|
|
655
|
-
/** Who authored a helpdesk message. */
|
|
656
|
-
type MessageAuthorType = "visitor" | "operator" | "ai" | "system";
|
|
657
|
-
/** Kind of helpdesk message. `note` is operator-internal and never delivered to the customer. */
|
|
658
|
-
type HelpdeskMessageType = "chat" | "email" | "note";
|
|
659
|
-
/** A helpdesk conversation across any connected channel (widget, email, social DMs, …). */
|
|
660
|
-
interface Conversation {
|
|
661
|
-
id: string;
|
|
662
|
-
/** Channel type, e.g. 'widget', 'instagram', 'messenger', 'whatsapp', 'email'. */
|
|
663
|
-
channel: string;
|
|
664
|
-
channel_connection_id: string | null;
|
|
665
|
-
status: ConversationStatus;
|
|
666
|
-
subject: string | null;
|
|
667
|
-
assignee_user_id: string | null;
|
|
668
|
-
contact_id: string | null;
|
|
669
|
-
visitor_name: string | null;
|
|
670
|
-
visitor_email: string | null;
|
|
671
|
-
external_conversation_id: string | null;
|
|
672
|
-
channel_account_id: string | null;
|
|
673
|
-
message_count: number;
|
|
674
|
-
unread_for_operator: number;
|
|
675
|
-
/** Unix timestamp in milliseconds. */
|
|
676
|
-
last_message_at: number;
|
|
677
|
-
last_message_preview: string | null;
|
|
678
|
-
last_message_author_type: MessageAuthorType | null;
|
|
679
|
-
/** Unix timestamp in milliseconds. */
|
|
680
|
-
created_at: number;
|
|
681
|
-
/** Unix timestamp in milliseconds. */
|
|
682
|
-
updated_at: number;
|
|
683
|
-
}
|
|
684
|
-
/** A single message inside a helpdesk conversation. */
|
|
685
|
-
interface ConversationMessage {
|
|
686
|
-
id: string;
|
|
687
|
-
conversation_id: string;
|
|
688
|
-
author_type: MessageAuthorType;
|
|
689
|
-
message_type: HelpdeskMessageType;
|
|
690
|
-
author_user_id: string | null;
|
|
691
|
-
author_name: string | null;
|
|
692
|
-
body: string;
|
|
693
|
-
/** Unix timestamp in milliseconds. */
|
|
694
|
-
created_at: number;
|
|
695
|
-
}
|
|
696
|
-
/** Filters for listing/searching helpdesk conversations. */
|
|
697
|
-
interface ListConversationsOptions extends PaginationOptions {
|
|
698
|
-
status?: ConversationStatus;
|
|
699
|
-
/** Only conversations assigned to this user. */
|
|
700
|
-
assignee_user_id?: string;
|
|
701
|
-
/** Match against visitor name/email. */
|
|
702
|
-
requester?: string;
|
|
703
|
-
/** Free-text search query. */
|
|
704
|
-
query?: string;
|
|
705
|
-
/** Only conversations on these channels (serialized as CSV). */
|
|
706
|
-
channels?: string[];
|
|
707
|
-
}
|
|
708
|
-
/** Input for updating a conversation's status and/or assignee. At least one field is required. */
|
|
709
|
-
interface UpdateConversationInput {
|
|
710
|
-
status?: ConversationStatus;
|
|
711
|
-
/** User ID to assign, or `null` to unassign. */
|
|
712
|
-
assignee_user_id?: string | null;
|
|
713
|
-
}
|
|
714
|
-
/** Result of a conversation update. */
|
|
715
|
-
interface ConversationUpdateResult {
|
|
716
|
-
id: string;
|
|
717
|
-
status: ConversationStatus;
|
|
718
|
-
assignee_user_id: string | null;
|
|
719
|
-
}
|
|
720
|
-
/** Input for sending an operator reply (or internal note) into a conversation. */
|
|
721
|
-
interface CreateReplyInput {
|
|
722
|
-
conversation_id: string;
|
|
723
|
-
/** Message body (max 20,000 characters). */
|
|
724
|
-
body: string;
|
|
725
|
-
/** `note` = operator-internal note (not delivered to the customer). Default `chat`. */
|
|
726
|
-
message_type?: "chat" | "note";
|
|
727
|
-
/** Agent display name for bridged replies (shown in widget + inbox). */
|
|
728
|
-
author_name?: string;
|
|
729
|
-
}
|
|
730
|
-
/** Result returned after creating a reply (HTTP 201). */
|
|
731
|
-
interface ReplyCreateResult {
|
|
732
|
-
id: string;
|
|
733
|
-
conversation_id: string;
|
|
734
|
-
status: string;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
1693
|
/** Browse and manage helpdesk conversations. */
|
|
738
1694
|
declare class HelpdeskConversations {
|
|
739
1695
|
private client;
|
|
740
|
-
|
|
1696
|
+
private confirmer;
|
|
1697
|
+
constructor(client: BaseClient, confirmer: CapabilityConfirmer);
|
|
741
1698
|
/** List/search conversations with cursor-based pagination and optional filters. */
|
|
742
1699
|
list(options?: ListConversationsOptions): Promise<PaginatedResponse<Conversation>>;
|
|
743
1700
|
/** Get a conversation by ID. */
|
|
@@ -750,12 +1707,21 @@ declare class HelpdeskConversations {
|
|
|
750
1707
|
/** Send operator replies (or internal notes) into conversations. */
|
|
751
1708
|
declare class HelpdeskReplies {
|
|
752
1709
|
private client;
|
|
753
|
-
|
|
1710
|
+
private confirmer;
|
|
1711
|
+
constructor(client: BaseClient, confirmer: CapabilityConfirmer);
|
|
754
1712
|
/**
|
|
755
1713
|
* Send an operator reply or internal note. Returns HTTP 201.
|
|
756
1714
|
*
|
|
757
|
-
*
|
|
758
|
-
*
|
|
1715
|
+
* Automatically idempotent: a reply is a message to a real person, and an
|
|
1716
|
+
* unkeyed retry sends it to them twice. The key the confirmer chose is the
|
|
1717
|
+
* key that goes out — a capability confirmation is bound to its idempotency
|
|
1718
|
+
* key, so minting a fresh one here would invalidate the confirmation.
|
|
1719
|
+
*
|
|
1720
|
+
* Pass `options.idempotencyKey` to deduplicate across your OWN retries too.
|
|
1721
|
+
* It is REQUIRED for capability-scoped tokens, which need it paired with a
|
|
1722
|
+
* `capabilityConfirmation` — a generated key satisfies the pairing's key
|
|
1723
|
+
* half only; the confirmation is still yours to supply (or to let
|
|
1724
|
+
* `autoConfirm` mint).
|
|
759
1725
|
*/
|
|
760
1726
|
create(input: CreateReplyInput, options?: RequestOptions): Promise<ApiResponse<ReplyCreateResult>>;
|
|
761
1727
|
}
|
|
@@ -763,7 +1729,7 @@ declare class HelpdeskReplies {
|
|
|
763
1729
|
declare class Helpdesk {
|
|
764
1730
|
readonly conversations: HelpdeskConversations;
|
|
765
1731
|
readonly replies: HelpdeskReplies;
|
|
766
|
-
constructor(client: BaseClient);
|
|
1732
|
+
constructor(client: BaseClient, confirmer?: CapabilityConfirmer);
|
|
767
1733
|
}
|
|
768
1734
|
|
|
769
1735
|
/** A post in the workspace (list view). */
|
|
@@ -850,8 +1816,14 @@ declare class Posts {
|
|
|
850
1816
|
constructor(client: BaseClient);
|
|
851
1817
|
/** List posts with cursor-based pagination and optional filters. */
|
|
852
1818
|
list(options?: ListPostsOptions): Promise<PaginatedResponse<Post>>;
|
|
853
|
-
/**
|
|
854
|
-
|
|
1819
|
+
/**
|
|
1820
|
+
* Create a new post with content and target channels.
|
|
1821
|
+
*
|
|
1822
|
+
* Automatically idempotent: the SDK mints an `Idempotency-Key` so its own
|
|
1823
|
+
* 5xx retries replay rather than draft the post twice. Supply
|
|
1824
|
+
* `options.idempotencyKey` to deduplicate across your OWN retries too.
|
|
1825
|
+
*/
|
|
1826
|
+
create(input: CreatePostInput, options?: RequestOptions): Promise<ApiResponse<{
|
|
855
1827
|
id: string;
|
|
856
1828
|
}>>;
|
|
857
1829
|
/** Get a post by ID, including its per-channel variants. */
|
|
@@ -864,105 +1836,196 @@ declare class Posts {
|
|
|
864
1836
|
remove(id: string): Promise<ApiResponse<{
|
|
865
1837
|
success: boolean;
|
|
866
1838
|
}>>;
|
|
867
|
-
/**
|
|
1839
|
+
/**
|
|
1840
|
+
* Schedule a post for future publication.
|
|
1841
|
+
*
|
|
1842
|
+
* Deliberately unkeyed: re-sending the same `scheduled_at` for an
|
|
1843
|
+
* already-scheduled post returns the original `workflow_id` rather than
|
|
1844
|
+
* starting a second one, so a retried schedule cannot double-publish. A
|
|
1845
|
+
* *different* time is rejected — unschedule first.
|
|
1846
|
+
*/
|
|
868
1847
|
schedule(id: string, input: SchedulePostInput): Promise<ApiResponse<ScheduleResult>>;
|
|
869
|
-
/**
|
|
1848
|
+
/**
|
|
1849
|
+
* Publish a post immediately to all target channels.
|
|
1850
|
+
*
|
|
1851
|
+
* Deliberately unkeyed: publishing moves the post out of the set of statuses
|
|
1852
|
+
* that may be published, so the retry of a publish that already committed is
|
|
1853
|
+
* refused rather than posting a second time. It is refused with a 400 though
|
|
1854
|
+
* — treat an error here as "check the post's status", not as "nothing
|
|
1855
|
+
* happened".
|
|
1856
|
+
*/
|
|
870
1857
|
publish(id: string): Promise<ApiResponse<PublishResult>>;
|
|
871
1858
|
/** List connected publishing channels for this workspace. */
|
|
872
1859
|
channels(): Promise<ApiResponse<Channel[]>>;
|
|
873
1860
|
}
|
|
874
1861
|
|
|
875
|
-
/**
|
|
876
|
-
interface
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
/**
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
event_types: string[];
|
|
884
|
-
/** Channel-type filter (e.g. ['widget', 'whatsapp']), or `null` for all channels. */
|
|
885
|
-
channels: string[] | null;
|
|
886
|
-
/** Channel-connection filter, or `null` for all connections. */
|
|
887
|
-
channel_connection_ids: string[] | null;
|
|
888
|
-
/** Last 4 characters of the signing secret, for identification. */
|
|
889
|
-
secret_last4: string;
|
|
890
|
-
consecutive_failures: number;
|
|
891
|
-
/** Unix timestamps in milliseconds, or `null` if never. */
|
|
892
|
-
last_delivery_at: number | null;
|
|
893
|
-
last_success_at: number | null;
|
|
894
|
-
last_error_at: number | null;
|
|
895
|
-
last_error: string | null;
|
|
896
|
-
created_at: number;
|
|
897
|
-
updated_at: number;
|
|
898
|
-
/**
|
|
899
|
-
* Full signing secret (`whsec_…`) — present ONLY in the `create` response.
|
|
900
|
-
* It is returned exactly once and can never be retrieved again. Store it
|
|
901
|
-
* securely immediately; you need it to verify delivery signatures.
|
|
902
|
-
*/
|
|
903
|
-
secret?: string;
|
|
1862
|
+
/** Input for creating a scan — provide exactly ONE of `url`, `orgnr`, or `name`. */
|
|
1863
|
+
interface ScanCreateInput {
|
|
1864
|
+
/** Website URL to scan directly (https is assumed when the scheme is missing). */
|
|
1865
|
+
url?: string;
|
|
1866
|
+
/** 9-digit Norwegian organisation number — resolved via the public registry. */
|
|
1867
|
+
orgnr?: string;
|
|
1868
|
+
/** Company name — resolved to the best registry match before scanning. */
|
|
1869
|
+
name?: string;
|
|
904
1870
|
}
|
|
905
|
-
/**
|
|
906
|
-
interface
|
|
907
|
-
|
|
1871
|
+
/** Reference returned when a scan job is queued (HTTP 202). */
|
|
1872
|
+
interface ScanCreateResult {
|
|
1873
|
+
id: string;
|
|
1874
|
+
status: "pending" | string;
|
|
1875
|
+
message?: string;
|
|
1876
|
+
}
|
|
1877
|
+
/** Lifecycle of an asynchronous scan job. */
|
|
1878
|
+
type ScanStatus = "pending" | "running" | "done" | "failed";
|
|
1879
|
+
/** A company-registry search hit from `scan.companies()`. */
|
|
1880
|
+
interface ScanCompany {
|
|
1881
|
+
orgnr: string;
|
|
908
1882
|
name: string;
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1883
|
+
org_form: string | null;
|
|
1884
|
+
industry: string | null;
|
|
1885
|
+
city: string | null;
|
|
1886
|
+
website: string | null;
|
|
1887
|
+
}
|
|
1888
|
+
/** Nettskår sub-scores (0–100); null = the axis could not be measured. */
|
|
1889
|
+
interface ScanSubScores {
|
|
1890
|
+
fart: number | null;
|
|
1891
|
+
google: number | null;
|
|
1892
|
+
ai: number | null;
|
|
1893
|
+
trygghet: number | null;
|
|
1894
|
+
omdomme: number | null;
|
|
917
1895
|
}
|
|
918
1896
|
/**
|
|
919
|
-
*
|
|
920
|
-
*
|
|
921
|
-
*
|
|
922
|
-
* leaves the current filter unchanged.
|
|
1897
|
+
* The versioned scan findings payload. Shapes within are additive per
|
|
1898
|
+
* `version`; string unions stay open (`| string`) so new detections never
|
|
1899
|
+
* break consumers.
|
|
923
1900
|
*/
|
|
924
|
-
interface
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
1901
|
+
interface ScanResultPayload {
|
|
1902
|
+
version: number;
|
|
1903
|
+
/** Weighted composite score (0–100); null when nothing could be measured. */
|
|
1904
|
+
nettskaar: number | null;
|
|
1905
|
+
subScores: ScanSubScores;
|
|
1906
|
+
registry?: {
|
|
1907
|
+
orgnr: string;
|
|
1908
|
+
navn: string;
|
|
1909
|
+
organisasjonsform?: string;
|
|
1910
|
+
naeringBeskrivelse?: string;
|
|
1911
|
+
antallAnsatte?: number;
|
|
1912
|
+
poststed?: string;
|
|
1913
|
+
};
|
|
1914
|
+
signals: {
|
|
1915
|
+
version: number;
|
|
1916
|
+
tech: string[];
|
|
1917
|
+
pixels: string[];
|
|
1918
|
+
consent: string[];
|
|
1919
|
+
commerce: string[];
|
|
1920
|
+
marketing?: string[];
|
|
1921
|
+
social: Record<string, string | undefined>;
|
|
1922
|
+
emailProvider?: string;
|
|
1923
|
+
unreachable?: boolean;
|
|
1924
|
+
inconclusive?: boolean;
|
|
1925
|
+
dnsPending?: boolean;
|
|
1926
|
+
};
|
|
1927
|
+
pagespeed?: {
|
|
1928
|
+
mobileScore?: number;
|
|
1929
|
+
lcpMs?: number;
|
|
1930
|
+
cls?: number;
|
|
1931
|
+
inpMs?: number;
|
|
1932
|
+
error?: string;
|
|
1933
|
+
};
|
|
1934
|
+
seo: {
|
|
1935
|
+
titleLength: number;
|
|
1936
|
+
metaDescriptionLength: number;
|
|
1937
|
+
h1Count: number;
|
|
1938
|
+
hasOg: boolean;
|
|
1939
|
+
hasCanonical: boolean;
|
|
1940
|
+
hreflangCount: number;
|
|
1941
|
+
hasViewport: boolean;
|
|
1942
|
+
};
|
|
1943
|
+
ai: {
|
|
1944
|
+
/** null = llms.txt could not be checked (unknown), false = confirmed absent. */
|
|
1945
|
+
llmsTxtFound: boolean | null;
|
|
1946
|
+
/** null = robots.txt could not be read (unknown, not "none blocked"). */
|
|
1947
|
+
blockedBots: string[] | null;
|
|
1948
|
+
jsonLdTypes: string[];
|
|
1949
|
+
faqFound: boolean;
|
|
1950
|
+
};
|
|
1951
|
+
gdpr: {
|
|
1952
|
+
/**
|
|
1953
|
+
* true = tracking pixels load with no consent platform; false = clean or
|
|
1954
|
+
* a consent platform is present; null = unknown (unreachable / JS-only).
|
|
1955
|
+
*/
|
|
1956
|
+
trackingBeforeConsent: boolean | null;
|
|
1957
|
+
cmp: string | null;
|
|
1958
|
+
privacyPageFound: boolean;
|
|
1959
|
+
};
|
|
1960
|
+
mailAuth: {
|
|
1961
|
+
spf: "ok" | "missing" | "unknown" | string;
|
|
1962
|
+
dmarc: "ok" | "missing" | "unknown" | string;
|
|
1963
|
+
dmarcPolicy?: string;
|
|
1964
|
+
};
|
|
1965
|
+
httpsOk: boolean;
|
|
936
1966
|
}
|
|
937
|
-
/** A
|
|
938
|
-
interface
|
|
1967
|
+
/** A scan job as returned by `scan.get()`. */
|
|
1968
|
+
interface ScanJob {
|
|
939
1969
|
id: string;
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
/**
|
|
949
|
-
|
|
950
|
-
created_at:
|
|
1970
|
+
status: ScanStatus | string;
|
|
1971
|
+
input: ScanCreateInput;
|
|
1972
|
+
resolved: {
|
|
1973
|
+
orgnr?: string;
|
|
1974
|
+
companyName?: string;
|
|
1975
|
+
websiteUrl?: string;
|
|
1976
|
+
} | null;
|
|
1977
|
+
result: ScanResultPayload | null;
|
|
1978
|
+
/** Public-safe failure code (`company_not_found`, `no_website`, …). */
|
|
1979
|
+
error: string | null;
|
|
1980
|
+
created_at: string | null;
|
|
1981
|
+
finished_at: string | null;
|
|
951
1982
|
}
|
|
952
|
-
/** Options for
|
|
953
|
-
interface
|
|
954
|
-
|
|
1983
|
+
/** Options for `scan.waitForResult()`. */
|
|
1984
|
+
interface WaitForScanOptions {
|
|
1985
|
+
/** Poll interval in milliseconds. Default 2500. */
|
|
1986
|
+
intervalMs?: number;
|
|
1987
|
+
/** Give up after this long. Default 120000 (scans normally finish in ~30 s). */
|
|
1988
|
+
timeoutMs?: number;
|
|
955
1989
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1990
|
+
|
|
1991
|
+
/**
|
|
1992
|
+
* Company & website scans (Nettsjekk) — score a Norwegian company's web
|
|
1993
|
+
* presence (performance, SEO, GDPR consent, AI visibility, mail auth) from a
|
|
1994
|
+
* URL, an organisation number, or a company name.
|
|
1995
|
+
*/
|
|
1996
|
+
declare class Scan {
|
|
1997
|
+
private client;
|
|
1998
|
+
constructor(client: BaseClient);
|
|
1999
|
+
/**
|
|
2000
|
+
* Queue a scan. Provide exactly one of `url`, `orgnr`, or `name`.
|
|
2001
|
+
* Runs asynchronously — poll with `get()` or use `waitForResult()`.
|
|
2002
|
+
*
|
|
2003
|
+
* Automatically idempotent: a scan job is queued the moment it is created,
|
|
2004
|
+
* so an unkeyed retry starts a second crawl of the same site and returns an
|
|
2005
|
+
* id for a job that duplicates one already running. Supply
|
|
2006
|
+
* `options.idempotencyKey` to deduplicate across your OWN retries too.
|
|
2007
|
+
*
|
|
2008
|
+
* @throws Error before any request when zero or several selectors are set —
|
|
2009
|
+
* the server would reject the body anyway; failing locally is clearer.
|
|
2010
|
+
*/
|
|
2011
|
+
create(input: ScanCreateInput, options?: RequestOptions): Promise<ApiResponse<ScanCreateResult>>;
|
|
2012
|
+
/** Get a scan job's status and, once done, its findings payload. */
|
|
2013
|
+
get(id: string): Promise<ApiResponse<ScanJob>>;
|
|
2014
|
+
/** Search the Norwegian company registry by name (typeahead, top 5 hits). */
|
|
2015
|
+
companies(q: string): Promise<ApiResponse<ScanCompany[]>>;
|
|
2016
|
+
/**
|
|
2017
|
+
* Poll a scan until it settles. Resolves with the job for both `done` and
|
|
2018
|
+
* `failed` (check `job.error`); throws only when the deadline passes while
|
|
2019
|
+
* the scan is still pending/running.
|
|
2020
|
+
*/
|
|
2021
|
+
waitForResult(id: string, options?: WaitForScanOptions): Promise<ScanJob>;
|
|
960
2022
|
}
|
|
961
2023
|
|
|
962
2024
|
/** Manage webhook endpoints and inspect their deliveries. */
|
|
963
2025
|
declare class Webhooks {
|
|
964
2026
|
private client;
|
|
965
|
-
|
|
2027
|
+
private confirmer;
|
|
2028
|
+
constructor(client: BaseClient, confirmer?: CapabilityConfirmer);
|
|
966
2029
|
/** List all webhook endpoints in the workspace. */
|
|
967
2030
|
list(): Promise<ApiResponse<WebhookEndpoint[]>>;
|
|
968
2031
|
/**
|
|
@@ -976,6 +2039,13 @@ declare class Webhooks {
|
|
|
976
2039
|
* `secret` is typed optional because an idempotent replay (retrying with the
|
|
977
2040
|
* same `Idempotency-Key`, `X-Idempotent-Replayed: true`) returns the existing
|
|
978
2041
|
* endpoint WITHOUT the secret — handle that case (rotate if you lost it).
|
|
2042
|
+
*
|
|
2043
|
+
* Automatically idempotent: a duplicate endpoint is not a stray row, it is a
|
|
2044
|
+
* second copy of every future delivery to the same URL, forever. The key the
|
|
2045
|
+
* confirmer chose is the key that goes out — a capability confirmation is
|
|
2046
|
+
* bound to its idempotency key, so minting a fresh one here would invalidate
|
|
2047
|
+
* the confirmation. That the SDK now always sends a key is also what makes
|
|
2048
|
+
* the replay-without-secret case above reachable on a plain 5xx retry.
|
|
979
2049
|
*/
|
|
980
2050
|
create(input: CreateWebhookInput, options?: RequestOptions): Promise<ApiResponse<WebhookEndpoint>>;
|
|
981
2051
|
/** Get a webhook endpoint by ID. */
|
|
@@ -991,7 +2061,14 @@ declare class Webhooks {
|
|
|
991
2061
|
delete(id: string, options?: RequestOptions): Promise<ApiResponse<WebhookDeleteResult>>;
|
|
992
2062
|
/** List recent deliveries for an endpoint (most recent first). */
|
|
993
2063
|
deliveries(id: string, options?: ListDeliveriesOptions): Promise<ApiResponse<WebhookDelivery[]>>;
|
|
994
|
-
/**
|
|
2064
|
+
/**
|
|
2065
|
+
* Queue a signed `test.ping` delivery to the endpoint. Returns HTTP 202.
|
|
2066
|
+
*
|
|
2067
|
+
* Deliberately unkeyed: a duplicate ping is the one duplicate that costs
|
|
2068
|
+
* nothing. Real deliveries are retried too, so any endpoint worth pointing at
|
|
2069
|
+
* already tolerates receiving the same event twice — that is what this call
|
|
2070
|
+
* exists to prove.
|
|
2071
|
+
*/
|
|
995
2072
|
test(id: string): Promise<ApiResponse<WebhookTestResult>>;
|
|
996
2073
|
}
|
|
997
2074
|
|
|
@@ -1227,6 +2304,37 @@ interface MedalOptions {
|
|
|
1227
2304
|
* OAuth tokens can access multiple workspaces, so you must specify which one.
|
|
1228
2305
|
*/
|
|
1229
2306
|
workspaceId?: string;
|
|
2307
|
+
/**
|
|
2308
|
+
* Opt in to automatic capability confirmation for confirmable writes.
|
|
2309
|
+
* **Defaults to OFF.**
|
|
2310
|
+
*
|
|
2311
|
+
* Medal's confirmable write routes (connect links, channel connections,
|
|
2312
|
+
* helpdesk replies/updates, webhook endpoint writes) require BOTH an
|
|
2313
|
+
* `Idempotency-Key` and an `X-Capability-Confirmation` token whenever the
|
|
2314
|
+
* credential holds the capability scope directly — which is the case for
|
|
2315
|
+
* every correctly-scoped partner key. With this option set, the SDK mints
|
|
2316
|
+
* both for you before each such write instead of making you hand-roll
|
|
2317
|
+
* `POST /api/v1/capability-confirmations`.
|
|
2318
|
+
*
|
|
2319
|
+
* **Read before enabling:** each minted token carries `user_approved: true`,
|
|
2320
|
+
* which asserts to Medal that *a human on your side approved that specific
|
|
2321
|
+
* action*, and the `previewSummary` you return is retained as the audit
|
|
2322
|
+
* record of what they approved. Enable it only on code paths where that is
|
|
2323
|
+
* genuinely true — never to rubber-stamp unattended writes. Pass
|
|
2324
|
+
* `{ autoConfirm: false }` on an individual call to opt out again, or use
|
|
2325
|
+
* `medal.capabilityConfirmations.create(...)` for full manual control.
|
|
2326
|
+
*
|
|
2327
|
+
* @example
|
|
2328
|
+
* ```ts
|
|
2329
|
+
* const medal = new Medal('medal_xxx', {
|
|
2330
|
+
* autoConfirmCapabilities: {
|
|
2331
|
+
* previewSummary: (ctx) =>
|
|
2332
|
+
* `${operator.email} approved ${ctx.method} ${ctx.path}`,
|
|
2333
|
+
* },
|
|
2334
|
+
* });
|
|
2335
|
+
* ```
|
|
2336
|
+
*/
|
|
2337
|
+
autoConfirmCapabilities?: AutoConfirmOptions;
|
|
1230
2338
|
}
|
|
1231
2339
|
/**
|
|
1232
2340
|
* Medal Social SDK client.
|
|
@@ -1267,6 +2375,13 @@ interface MedalOptions {
|
|
|
1267
2375
|
* variables: { name: 'John' },
|
|
1268
2376
|
* });
|
|
1269
2377
|
*
|
|
2378
|
+
* // Bookings — free slots, then book a party (money is integer øre)
|
|
2379
|
+
* const { data: slots } = await medal.bookings.availability({
|
|
2380
|
+
* service_id: 'svc_1',
|
|
2381
|
+
* from_ts: Date.now(),
|
|
2382
|
+
* to_ts: Date.now() + 7 * 86_400_000,
|
|
2383
|
+
* });
|
|
2384
|
+
*
|
|
1270
2385
|
* // Contacts, Deals, GDPR, Workspaces
|
|
1271
2386
|
* const contacts = await medal.contacts.list({ status: 'lead' });
|
|
1272
2387
|
* const { data: deal } = await medal.deals.create({ title: 'Acme', value: 50000 });
|
|
@@ -1275,6 +2390,8 @@ interface MedalOptions {
|
|
|
1275
2390
|
* ```
|
|
1276
2391
|
*/
|
|
1277
2392
|
declare class Medal {
|
|
2393
|
+
readonly bookings: Bookings;
|
|
2394
|
+
readonly capabilityConfirmations: CapabilityConfirmations;
|
|
1278
2395
|
readonly channels: Channels;
|
|
1279
2396
|
readonly emails: Emails;
|
|
1280
2397
|
readonly contacts: Contacts;
|
|
@@ -1282,6 +2399,7 @@ declare class Medal {
|
|
|
1282
2399
|
readonly gdpr: Gdpr;
|
|
1283
2400
|
readonly helpdesk: Helpdesk;
|
|
1284
2401
|
readonly posts: Posts;
|
|
2402
|
+
readonly scan: Scan;
|
|
1285
2403
|
readonly webhooks: Webhooks;
|
|
1286
2404
|
readonly workspaces: Workspaces;
|
|
1287
2405
|
constructor(token: string, options?: MedalOptions);
|
|
@@ -1290,4 +2408,4 @@ declare class Medal {
|
|
|
1290
2408
|
/** Convenience factory — equivalent to `new Medal(apiKey, options)`. */
|
|
1291
2409
|
declare function createMedalClient(apiKey: string, options?: MedalOptions): Medal;
|
|
1292
2410
|
|
|
1293
|
-
export { type Activity, type AddNoteInput, type ApiResponse, BaseClient, type BatchSendInput, type BatchSendResult, type BatchSendSummary, type Channel, type ChannelConnectedEvent, type ChannelConnection, type ChannelConnectionDisconnectResult, type ChannelConnectionState, type ChannelDisconnectReason, type ChannelDisconnectedEvent, Channels, type ConnectLink, type ConnectLinkCreateResult, type ConnectLinkRevokeResult, type ConnectLinkStatus, type ConsentRecord, type ConsentResult, type ConsentType, type Contact, type ContactConsents, type ContactCreateResult, type ContactNoteResult, type ContactRemoveResult, type ContactStatus, type ContactUpdateResult, Contacts, type Conversation, type ConversationAssignedEvent, type ConversationCreatedEvent, type ConversationMessage, type ConversationStatus, type ConversationStatusChangedEvent, type ConversationUpdateResult, type CookieCategoryConsent, type CookieConsentInput, type CreateConnectLinkInput, type CreateContactInput, type CreateDealInput, type CreatePostInput, type CreateReplyInput, type CreateWebhookInput, DEFAULT_WEBHOOK_TOLERANCE_MS, type Deal, type DealCreateResult, type DealRemoveResult, type DealStatus, type DealUpdateResult, Deals, type EmailSend, type EmailSendResult, type EmailStatus, type EmailTemplate, type EmailTemplateDetail, Emails, Gdpr, type GdprExport, type GetTemplateOptions, Helpdesk, type HelpdeskMessageType, type ImportContactInput, type ImportContactsResult, type ListConnectLinksOptions, type ListContactsOptions, type ListConversationsOptions, type ListDealsOptions, type ListDeliveriesOptions, type ListPostsOptions, Medal, MedalApiError, type MedalOptions, type MessageAuthorType, type MessageDeliveryUpdatedEvent, type MessageReceivedEvent, type MessageSentEvent, type PaginatedResponse, type PaginationOptions, type Post, type PostDetail, type PostType, type PostVariant, Posts, type PublishResult, type RecordConsentInput, type ReplyCreateResult, type RequestOptions, type SchedulePostInput, type ScheduleResult, type SendEmailInput, type TestPingEvent, type UpdateContactInput, type UpdateConversationInput, type UpdateDealInput, type UpdatePostInput, type UpdateWebhookInput, type VerifyWebhookSignatureInput, type WebhookChannelLifecycleData, type WebhookConversationSnapshot, type WebhookDeleteResult, type WebhookDelivery, type WebhookEndpoint, type WebhookEvent, type WebhookMessageSnapshot, type WebhookTestResult, WebhookVerificationError, type WebhookVerificationErrorCode, Webhooks, type Workspace, Workspaces, createMedalClient, Medal as default, verifyWebhookSignature };
|
|
2411
|
+
export { type Activity, type AddNoteInput, type ApiResponse, type AutoConfirmContext, type AutoConfirmOptions, BaseClient, type BatchSendInput, type BatchSendResult, type BatchSendSummary, type Booking, type BookingActionResult, type BookingAvailabilityOptions, type BookingCancelledBy, type BookingClaimableCreatedVia, type BookingContactInput, type BookingCreateResult, type BookingCreatedVia, type BookingPaymentStatus, type BookingRescheduleResult, type BookingResource, type BookingResourceType, type BookingScheduleDay, type BookingScheduleOptions, type BookingService, type BookingSlot, type BookingStatus, type BookingTimestampInput, Bookings, type BookingsPage, type BookingsPagination, CAPABILITY_IDS, CAPABILITY_ROUTES, type CancelBookingInput, type CapabilityConfirmation, CapabilityConfirmations, CapabilityConfirmer, type CapabilityId, type CapabilityPathParamValue, type CapabilityRoute, type CapabilityWriteBodies, type CapabilityWriteRequest, type Channel, type ChannelConnectedEvent, type ChannelConnection, type ChannelConnectionDisconnectResult, type ChannelConnectionState, type ChannelDisconnectReason, type ChannelDisconnectedEvent, Channels, type ConnectLink, type ConnectLinkCreateResult, type ConnectLinkRevokeResult, type ConnectLinkStatus, type ConsentRecord, type ConsentResult, type ConsentType, type Contact, type ContactConsents, type ContactCreateResult, type ContactNoteResult, type ContactRemoveResult, type ContactStatus, type ContactUpdateResult, Contacts, type Conversation, type ConversationAssignedEvent, type ConversationCreatedEvent, type ConversationMessage, type ConversationStatus, type ConversationStatusChangedEvent, type ConversationUpdateResult, type CookieCategoryConsent, type CookieConsentInput, type CreateBookingInput, type CreateBookingItemInput, type CreateConnectLinkInput, type CreateContactInput, type CreateDealInput, type CreatePostInput, type CreateReplyInput, type CreateWebhookInput, type CreatedBooking, DEFAULT_WEBHOOK_TOLERANCE_MS, type Deal, type DealCreateResult, type DealRemoveResult, type DealStatus, type DealUpdateResult, Deals, type EmailSend, type EmailSendResult, type EmailStatus, type EmailTemplate, type EmailTemplateDetail, Emails, Gdpr, type GdprExport, type GetTemplateOptions, Helpdesk, type HelpdeskMessageType, type ImportContactInput, type ImportContactsResult, type IssueCapabilityConfirmationInput, type ListBookingServicesOptions, type ListBookingsOptions, type ListConnectLinksOptions, type ListContactsOptions, type ListConversationsOptions, type ListDealsOptions, type ListDeliveriesOptions, type ListPostsOptions, type ManageSummary, Medal, MedalApiError, type MedalOptions, type MessageAuthorType, type MessageDeliveryStatus, type MessageDeliveryUpdatedEvent, type MessageReceivedEvent, type MessageSentEvent, type PaginatedResponse, type PaginationOptions, type Post, type PostDetail, type PostType, type PostVariant, Posts, type PublishResult, type RecordConsentInput, type ReplyCreateResult, type RequestOptions, type RescheduleBookingInput, Scan, type ScanCompany, type ScanCreateInput, type ScanCreateResult, type ScanJob, type ScanResultPayload, type ScanStatus, type ScanSubScores, type SchedulePostInput, type ScheduleResult, type SendEmailInput, type TestPingEvent, type UpdateBookingInput, type UpdateContactInput, type UpdateConversationInput, type UpdateDealInput, type UpdatePostInput, type UpdateWebhookInput, type VerifyWebhookSignatureInput, type WaitForScanOptions, type WebhookChannelLifecycleData, type WebhookConversationSnapshot, type WebhookDeleteResult, type WebhookDelivery, type WebhookEndpoint, type WebhookEvent, type WebhookMessageSnapshot, type WebhookTestResult, WebhookVerificationError, type WebhookVerificationErrorCode, Webhooks, type Workspace, Workspaces, createMedalClient, Medal as default, verifyWebhookSignature };
|