@dispatchtickets/sdk 0.3.0 → 0.6.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/dist/index.d.cts CHANGED
@@ -2,6 +2,105 @@
2
2
  * Custom fetch function type
3
3
  */
4
4
  type FetchFunction = typeof fetch;
5
+ /**
6
+ * Request context passed to hooks
7
+ */
8
+ interface RequestContext {
9
+ /** HTTP method */
10
+ method: string;
11
+ /** Full URL being requested */
12
+ url: string;
13
+ /** Request headers */
14
+ headers: Record<string, string>;
15
+ /** Request body (if any) */
16
+ body?: unknown;
17
+ /** Retry attempt number (0 = first attempt) */
18
+ attempt: number;
19
+ }
20
+ /**
21
+ * Response context passed to hooks
22
+ */
23
+ interface ResponseContext {
24
+ /** The request that was made */
25
+ request: RequestContext;
26
+ /** HTTP status code */
27
+ status: number;
28
+ /** Response headers */
29
+ headers: Headers;
30
+ /** Request ID from server */
31
+ requestId?: string;
32
+ /** Rate limit info from server */
33
+ rateLimit?: RateLimitInfo;
34
+ /** Duration of the request in milliseconds */
35
+ durationMs: number;
36
+ }
37
+ /**
38
+ * Retry configuration options
39
+ */
40
+ interface RetryConfig {
41
+ /**
42
+ * Maximum number of retry attempts
43
+ * @default 3
44
+ */
45
+ maxRetries?: number;
46
+ /**
47
+ * HTTP status codes that should trigger a retry
48
+ * @default [429, 500, 502, 503, 504]
49
+ */
50
+ retryableStatuses?: number[];
51
+ /**
52
+ * Whether to retry on network errors
53
+ * @default true
54
+ */
55
+ retryOnNetworkError?: boolean;
56
+ /**
57
+ * Whether to retry on timeout errors
58
+ * @default true
59
+ */
60
+ retryOnTimeout?: boolean;
61
+ /**
62
+ * Initial delay between retries in milliseconds
63
+ * @default 1000
64
+ */
65
+ initialDelayMs?: number;
66
+ /**
67
+ * Maximum delay between retries in milliseconds
68
+ * @default 30000
69
+ */
70
+ maxDelayMs?: number;
71
+ /**
72
+ * Multiplier for exponential backoff
73
+ * @default 2
74
+ */
75
+ backoffMultiplier?: number;
76
+ /**
77
+ * Jitter factor (0-1) to add randomness to delays
78
+ * @default 0.25
79
+ */
80
+ jitter?: number;
81
+ }
82
+ /**
83
+ * Hooks for observability and customization
84
+ */
85
+ interface Hooks {
86
+ /**
87
+ * Called before each request is sent
88
+ * Can modify the request or throw to abort
89
+ */
90
+ onRequest?: (context: RequestContext) => void | Promise<void>;
91
+ /**
92
+ * Called after each successful response
93
+ */
94
+ onResponse?: (context: ResponseContext) => void | Promise<void>;
95
+ /**
96
+ * Called when an error occurs (before retry)
97
+ */
98
+ onError?: (error: Error, context: RequestContext) => void | Promise<void>;
99
+ /**
100
+ * Called before each retry attempt
101
+ */
102
+ onRetry?: (context: RequestContext, error: Error, delayMs: number) => void | Promise<void>;
103
+ }
5
104
  interface HttpClientConfig {
6
105
  baseUrl: string;
7
106
  apiKey: string;
@@ -12,6 +111,14 @@ interface HttpClientConfig {
12
111
  * Custom fetch implementation for testing/mocking
13
112
  */
14
113
  fetch?: FetchFunction;
114
+ /**
115
+ * Fine-grained retry configuration
116
+ */
117
+ retry?: RetryConfig;
118
+ /**
119
+ * Hooks for observability
120
+ */
121
+ hooks?: Hooks;
15
122
  }
16
123
  interface RequestOptions {
17
124
  method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
@@ -20,40 +127,103 @@ interface RequestOptions {
20
127
  query?: Record<string, string | number | boolean | undefined>;
21
128
  headers?: Record<string, string>;
22
129
  idempotencyKey?: string;
130
+ /**
131
+ * AbortSignal to cancel the request
132
+ */
133
+ signal?: AbortSignal;
23
134
  }
24
135
  /**
25
- * HTTP client with retry logic and error handling
136
+ * Rate limit information from response headers
137
+ */
138
+ interface RateLimitInfo {
139
+ /** Maximum requests allowed in the current window */
140
+ limit: number;
141
+ /** Remaining requests in the current window */
142
+ remaining: number;
143
+ /** Unix timestamp (seconds) when the rate limit resets */
144
+ reset: number;
145
+ }
146
+ /**
147
+ * Response wrapper with rate limit info
148
+ */
149
+ interface ResponseWithRateLimit<T> {
150
+ data: T;
151
+ rateLimit?: RateLimitInfo;
152
+ requestId?: string;
153
+ }
154
+ /**
155
+ * HTTP client with retry logic, hooks, and error handling
26
156
  */
27
157
  declare class HttpClient {
28
158
  private readonly config;
29
159
  private readonly fetchFn;
160
+ private readonly retryConfig;
161
+ /** Rate limit info from the last response */
162
+ private _lastRateLimit?;
163
+ /** Request ID from the last response */
164
+ private _lastRequestId?;
30
165
  constructor(config: HttpClientConfig);
166
+ /**
167
+ * Get rate limit info from the last response
168
+ */
169
+ get lastRateLimit(): RateLimitInfo | undefined;
170
+ /**
171
+ * Get request ID from the last response
172
+ */
173
+ get lastRequestId(): string | undefined;
31
174
  /**
32
175
  * Execute an HTTP request with retry logic
33
176
  */
34
177
  request<T>(options: RequestOptions): Promise<T>;
178
+ /**
179
+ * Execute request and return response with rate limit info
180
+ */
181
+ requestWithRateLimit<T>(options: RequestOptions): Promise<ResponseWithRateLimit<T>>;
182
+ private shouldRetry;
183
+ private calculateDelay;
35
184
  private buildUrl;
36
185
  private buildHeaders;
37
186
  private executeRequest;
187
+ private extractRateLimitInfo;
38
188
  private handleResponse;
39
- private calculateBackoff;
40
189
  private sleep;
41
190
  }
42
191
 
192
+ /**
193
+ * Options for API requests
194
+ */
195
+ interface ApiRequestOptions {
196
+ /**
197
+ * AbortSignal to cancel the request
198
+ *
199
+ * @example
200
+ * ```typescript
201
+ * const controller = new AbortController();
202
+ * setTimeout(() => controller.abort(), 5000);
203
+ *
204
+ * const ticket = await client.tickets.get('ws_abc', 'tkt_xyz', {
205
+ * signal: controller.signal,
206
+ * });
207
+ * ```
208
+ */
209
+ signal?: AbortSignal;
210
+ }
43
211
  /**
44
212
  * Base class for all resource classes
213
+ * @internal
45
214
  */
46
215
  declare abstract class BaseResource {
47
216
  protected readonly http: HttpClient;
48
217
  constructor(http: HttpClient);
49
- protected _get<T>(path: string, query?: RequestOptions['query']): Promise<T>;
218
+ protected _get<T>(path: string, query?: RequestOptions['query'], options?: ApiRequestOptions): Promise<T>;
50
219
  protected _post<T>(path: string, body?: unknown, options?: {
51
220
  idempotencyKey?: string;
52
221
  query?: RequestOptions['query'];
222
+ signal?: AbortSignal;
53
223
  }): Promise<T>;
54
- protected _patch<T>(path: string, body?: unknown): Promise<T>;
55
- protected _put<T>(path: string, body?: unknown): Promise<T>;
56
- protected _delete<T>(path: string, query?: RequestOptions['query']): Promise<T>;
224
+ protected _patch<T>(path: string, body?: unknown, options?: ApiRequestOptions): Promise<T>;
225
+ protected _put<T>(path: string, body?: unknown, options?: ApiRequestOptions): Promise<T>;
226
+ protected _delete<T>(path: string, query?: RequestOptions['query'], options?: ApiRequestOptions): Promise<T>;
57
227
  }
58
228
 
59
229
  /**
@@ -217,6 +387,134 @@ interface DeleteBrandPreview {
217
387
  tagCount: number;
218
388
  }
219
389
 
390
+ /**
391
+ * Portal API Types
392
+ *
393
+ * Types for the customer-facing portal API.
394
+ * Used with the DispatchPortal client.
395
+ */
396
+ /**
397
+ * Response from portal token generation or verification
398
+ */
399
+ interface PortalTokenResponse {
400
+ /** JWT portal token for Authorization header */
401
+ token: string;
402
+ /** ISO 8601 expiration timestamp */
403
+ expiresAt: string;
404
+ /** Customer ID */
405
+ customerId: string;
406
+ /** Customer email */
407
+ email: string;
408
+ /** Customer name (if provided) */
409
+ name?: string;
410
+ }
411
+ /**
412
+ * Input for generating a portal token
413
+ */
414
+ interface GeneratePortalTokenInput {
415
+ /** Customer email address */
416
+ email: string;
417
+ /** Customer name (optional) */
418
+ name?: string;
419
+ }
420
+ /**
421
+ * Input for sending a magic link email
422
+ */
423
+ interface SendMagicLinkInput {
424
+ /** Customer email address */
425
+ email: string;
426
+ /** URL to redirect to after verification (your portal URL) */
427
+ portalUrl: string;
428
+ }
429
+ /**
430
+ * Ticket summary (returned in list views)
431
+ */
432
+ interface PortalTicket {
433
+ /** Ticket ID */
434
+ id: string;
435
+ /** Formatted ticket number (e.g., "TKT-1001") */
436
+ ticketNumber: string;
437
+ /** Ticket title */
438
+ title: string;
439
+ /** Current status */
440
+ status: string;
441
+ /** Priority level */
442
+ priority: string;
443
+ /** Number of comments on the ticket */
444
+ commentCount: number;
445
+ /** Creation timestamp */
446
+ createdAt: string;
447
+ /** Last update timestamp */
448
+ updatedAt: string;
449
+ }
450
+ /**
451
+ * Ticket detail with body and comments
452
+ */
453
+ interface PortalTicketDetail extends PortalTicket {
454
+ /** Ticket body/description */
455
+ body: string;
456
+ /** Brand information */
457
+ brand: {
458
+ /** Brand name */
459
+ name: string;
460
+ };
461
+ /** Comments on the ticket (excludes internal comments) */
462
+ comments: PortalComment[];
463
+ }
464
+ /**
465
+ * Comment on a ticket
466
+ */
467
+ interface PortalComment {
468
+ /** Comment ID */
469
+ id: string;
470
+ /** Comment body */
471
+ body: string;
472
+ /** Author type */
473
+ authorType: 'CUSTOMER' | 'STAFF';
474
+ /** Author display name */
475
+ authorName: string;
476
+ /** Creation timestamp */
477
+ createdAt: string;
478
+ }
479
+ /**
480
+ * Input for creating a ticket via portal
481
+ */
482
+ interface PortalCreateTicketInput {
483
+ /** Ticket title */
484
+ title: string;
485
+ /** Ticket body/description (optional) */
486
+ body?: string;
487
+ }
488
+ /**
489
+ * Filters for listing tickets
490
+ */
491
+ interface PortalListTicketsFilters {
492
+ /** Filter by status */
493
+ status?: string;
494
+ /** Sort field */
495
+ sort?: 'createdAt' | 'updatedAt';
496
+ /** Sort order */
497
+ order?: 'asc' | 'desc';
498
+ /** Maximum results per page (1-50, default 20) */
499
+ limit?: number;
500
+ /** Pagination cursor */
501
+ cursor?: string;
502
+ }
503
+ /**
504
+ * Paginated list of tickets
505
+ */
506
+ interface PortalTicketListResponse {
507
+ /** Array of tickets */
508
+ data: PortalTicket[];
509
+ /** Pagination info */
510
+ pagination: {
511
+ /** Whether more results are available */
512
+ hasMore: boolean;
513
+ /** Cursor for next page (null if no more) */
514
+ nextCursor: string | null;
515
+ };
516
+ }
517
+
220
518
  /**
221
519
  * Brands resource for managing workspaces
222
520
  */
@@ -271,6 +569,61 @@ declare class BrandsResource extends BaseResource {
271
569
  * ```
272
570
  */
273
571
  getInboundEmail(brandId: string, domain?: string): string;
572
+ /**
573
+ * Generate a portal token for a customer
574
+ *
575
+ * Use this for "authenticated mode" where your backend already knows who the
576
+ * customer is (they're logged into your app). Generate a portal token and
577
+ * pass it to your frontend to initialize the DispatchPortal client.
578
+ *
579
+ * @param brandId - The brand ID
580
+ * @param data - Customer email and optional name
581
+ * @returns Portal token response with JWT token and expiry
582
+ *
583
+ * @example
584
+ * ```typescript
585
+ * // On your backend (Node.js, Next.js API route, etc.)
586
+ * const { token, expiresAt } = await client.brands.generatePortalToken(
587
+ * 'br_abc123',
588
+ * {
589
+ * email: req.user.email,
590
+ * name: req.user.name,
591
+ * }
592
+ * );
593
+ *
594
+ * // Return token to your frontend
595
+ * res.json({ portalToken: token });
596
+ * ```
597
+ */
598
+ generatePortalToken(brandId: string, data: GeneratePortalTokenInput): Promise<PortalTokenResponse>;
599
+ /**
600
+ * Send a magic link email to a customer
601
+ *
602
+ * Use this for "self-auth mode" where customers access the portal without
603
+ * being logged into your app. They receive an email with a link that
604
+ * authenticates them directly.
605
+ *
606
+ * @param brandId - The brand ID
607
+ * @param data - Customer email and portal URL to redirect to
608
+ * @returns Success status
609
+ *
610
+ * @example
611
+ * ```typescript
612
+ * // Customer requests access via your portal login form
613
+ * await client.brands.sendPortalMagicLink('br_abc123', {
614
+ * email: 'customer@example.com',
615
+ * portalUrl: 'https://yourapp.com/support/portal',
616
+ * });
617
+ *
618
+ * // Customer receives email with link like:
619
+ * // https://yourapp.com/support/portal?token=xyz...
620
+ *
621
+ * // Your portal page then calls DispatchPortal.verify(token)
622
+ * ```
623
+ */
624
+ sendPortalMagicLink(brandId: string, data: SendMagicLinkInput): Promise<{
625
+ success: boolean;
626
+ }>;
274
627
  }
275
628
 
276
629
  /**
@@ -1024,10 +1377,30 @@ declare class FieldsResource extends BaseResource {
1024
1377
 
1025
1378
  /**
1026
1379
  * Configuration options for the Dispatch Tickets client
1380
+ *
1381
+ * @example
1382
+ * ```typescript
1383
+ * const client = new DispatchTickets({
1384
+ * apiKey: 'sk_live_...',
1385
+ * timeout: 30000,
1386
+ * retry: {
1387
+ * maxRetries: 5,
1388
+ * retryableStatuses: [429, 500, 502, 503, 504],
1389
+ * },
1390
+ * hooks: {
1391
+ * onRequest: (ctx) => console.log(`${ctx.method} ${ctx.url}`),
1392
+ * onResponse: (ctx) => console.log(`${ctx.status} in ${ctx.durationMs}ms`),
1393
+ * },
1394
+ * });
1395
+ * ```
1027
1396
  */
1028
1397
  interface DispatchTicketsConfig {
1029
1398
  /**
1030
1399
  * Your API key (required)
1400
+ *
1401
+ * Get your API key from the Dispatch Tickets dashboard.
1402
+ * Keys starting with `sk_live_` are production keys.
1403
+ * Keys starting with `sk_test_` are test keys.
1031
1404
  */
1032
1405
  apiKey: string;
1033
1406
  /**
@@ -1043,22 +1416,90 @@ interface DispatchTicketsConfig {
1043
1416
  /**
1044
1417
  * Maximum number of retries for failed requests
1045
1418
  * @default 3
1419
+ * @deprecated Use `retry.maxRetries` instead for fine-grained control
1046
1420
  */
1047
1421
  maxRetries?: number;
1048
1422
  /**
1049
1423
  * Enable debug logging
1424
+ *
1425
+ * When enabled, logs all requests, responses, and request IDs to console.
1050
1426
  * @default false
1051
1427
  */
1052
1428
  debug?: boolean;
1053
1429
  /**
1054
1430
  * Custom fetch implementation for testing/mocking
1431
+ *
1432
+ * @example
1433
+ * ```typescript
1434
+ * const mockFetch = vi.fn().mockResolvedValue({
1435
+ * ok: true,
1436
+ * status: 200,
1437
+ * headers: { get: () => 'application/json' },
1438
+ * json: () => Promise.resolve({ id: 'test' }),
1439
+ * });
1440
+ *
1441
+ * const client = new DispatchTickets({
1442
+ * apiKey: 'sk_test_...',
1443
+ * fetch: mockFetch,
1444
+ * });
1445
+ * ```
1055
1446
  */
1056
1447
  fetch?: FetchFunction;
1448
+ /**
1449
+ * Fine-grained retry configuration
1450
+ *
1451
+ * @example
1452
+ * ```typescript
1453
+ * const client = new DispatchTickets({
1454
+ * apiKey: 'sk_live_...',
1455
+ * retry: {
1456
+ * maxRetries: 5,
1457
+ * retryableStatuses: [429, 500, 502, 503, 504],
1458
+ * initialDelayMs: 500,
1459
+ * maxDelayMs: 60000,
1460
+ * backoffMultiplier: 2,
1461
+ * jitter: 0.25,
1462
+ * },
1463
+ * });
1464
+ * ```
1465
+ */
1466
+ retry?: RetryConfig;
1467
+ /**
1468
+ * Hooks for observability and customization
1469
+ *
1470
+ * @example
1471
+ * ```typescript
1472
+ * const client = new DispatchTickets({
1473
+ * apiKey: 'sk_live_...',
1474
+ * hooks: {
1475
+ * onRequest: (ctx) => {
1476
+ * console.log(`[${new Date().toISOString()}] ${ctx.method} ${ctx.url}`);
1477
+ * },
1478
+ * onResponse: (ctx) => {
1479
+ * console.log(`[${ctx.requestId}] ${ctx.status} in ${ctx.durationMs}ms`);
1480
+ * },
1481
+ * onError: (error, ctx) => {
1482
+ * console.error(`Request failed: ${error.message}`);
1483
+ * // Send to error tracking service
1484
+ * Sentry.captureException(error);
1485
+ * },
1486
+ * onRetry: (ctx, error, delayMs) => {
1487
+ * console.log(`Retrying in ${delayMs}ms (attempt ${ctx.attempt + 1})`);
1488
+ * },
1489
+ * },
1490
+ * });
1491
+ * ```
1492
+ */
1493
+ hooks?: Hooks;
1057
1494
  }
1058
1495
  /**
1059
1496
  * Dispatch Tickets SDK client
1060
1497
  *
1061
- * @example
1498
+ * The main entry point for interacting with the Dispatch Tickets API.
1499
+ * Create a client instance with your API key and use the resource methods
1500
+ * to manage tickets, comments, attachments, and more.
1501
+ *
1502
+ * @example Basic usage
1062
1503
  * ```typescript
1063
1504
  * import { DispatchTickets } from '@dispatchtickets/sdk';
1064
1505
  *
@@ -1080,59 +1521,530 @@ interface DispatchTicketsConfig {
1080
1521
  * console.log(ticket.title);
1081
1522
  * }
1082
1523
  * ```
1524
+ *
1525
+ * @example With request cancellation
1526
+ * ```typescript
1527
+ * const controller = new AbortController();
1528
+ *
1529
+ * // Cancel after 5 seconds
1530
+ * setTimeout(() => controller.abort(), 5000);
1531
+ *
1532
+ * try {
1533
+ * const tickets = await client.tickets.listPage('ws_abc123', {}, {
1534
+ * signal: controller.signal,
1535
+ * });
1536
+ * } catch (error) {
1537
+ * if (error.message === 'Request aborted by user') {
1538
+ * console.log('Request was cancelled');
1539
+ * }
1540
+ * }
1541
+ * ```
1542
+ *
1543
+ * @example With hooks for logging
1544
+ * ```typescript
1545
+ * const client = new DispatchTickets({
1546
+ * apiKey: 'sk_live_...',
1547
+ * hooks: {
1548
+ * onRequest: (ctx) => console.log(`→ ${ctx.method} ${ctx.url}`),
1549
+ * onResponse: (ctx) => console.log(`← ${ctx.status} (${ctx.durationMs}ms)`),
1550
+ * },
1551
+ * });
1552
+ * ```
1083
1553
  */
1084
1554
  declare class DispatchTickets {
1085
1555
  private readonly http;
1086
1556
  /**
1087
1557
  * Accounts resource for managing the current account and API keys
1558
+ *
1559
+ * @example
1560
+ * ```typescript
1561
+ * // Get current account
1562
+ * const account = await client.accounts.me();
1563
+ *
1564
+ * // Get usage statistics
1565
+ * const usage = await client.accounts.getUsage();
1566
+ *
1567
+ * // Create a new API key
1568
+ * const newKey = await client.accounts.createApiKey({
1569
+ * name: 'Production',
1570
+ * allBrands: true,
1571
+ * });
1572
+ * ```
1088
1573
  */
1089
1574
  readonly accounts: AccountsResource;
1090
1575
  /**
1091
1576
  * Brands (workspaces) resource
1577
+ *
1578
+ * Brands are isolated containers for tickets. Each brand can have its own
1579
+ * email address, categories, tags, and settings.
1580
+ *
1581
+ * @example
1582
+ * ```typescript
1583
+ * // List all brands
1584
+ * const brands = await client.brands.list();
1585
+ *
1586
+ * // Create a new brand
1587
+ * const brand = await client.brands.create({
1588
+ * name: 'Acme Support',
1589
+ * slug: 'acme',
1590
+ * });
1591
+ *
1592
+ * // Get inbound email address
1593
+ * const email = client.brands.getInboundEmail('br_abc123');
1594
+ * // Returns: br_abc123@inbound.dispatchtickets.com
1595
+ * ```
1092
1596
  */
1093
1597
  readonly brands: BrandsResource;
1094
1598
  /**
1095
1599
  * Tickets resource
1600
+ *
1601
+ * @example
1602
+ * ```typescript
1603
+ * // Create a ticket
1604
+ * const ticket = await client.tickets.create('ws_abc123', {
1605
+ * title: 'Issue with billing',
1606
+ * body: 'I was charged twice...',
1607
+ * priority: 'high',
1608
+ * });
1609
+ *
1610
+ * // Iterate through all tickets
1611
+ * for await (const ticket of client.tickets.list('ws_abc123', { status: 'open' })) {
1612
+ * console.log(ticket.title);
1613
+ * }
1614
+ * ```
1096
1615
  */
1097
1616
  readonly tickets: TicketsResource;
1098
1617
  /**
1099
1618
  * Comments resource
1619
+ *
1620
+ * @example
1621
+ * ```typescript
1622
+ * // Add a comment
1623
+ * const comment = await client.comments.create('ws_abc123', 'tkt_xyz', {
1624
+ * body: 'Thanks for your patience!',
1625
+ * authorType: 'AGENT',
1626
+ * });
1627
+ *
1628
+ * // List comments
1629
+ * const comments = await client.comments.list('ws_abc123', 'tkt_xyz');
1630
+ * ```
1100
1631
  */
1101
1632
  readonly comments: CommentsResource;
1102
1633
  /**
1103
1634
  * Attachments resource
1635
+ *
1636
+ * @example
1637
+ * ```typescript
1638
+ * // Simple upload
1639
+ * const attachment = await client.attachments.upload(
1640
+ * 'ws_abc123',
1641
+ * 'tkt_xyz',
1642
+ * fileBuffer,
1643
+ * 'document.pdf',
1644
+ * 'application/pdf'
1645
+ * );
1646
+ *
1647
+ * // Get download URL
1648
+ * const { downloadUrl } = await client.attachments.get('ws_abc123', 'tkt_xyz', 'att_abc');
1649
+ * ```
1104
1650
  */
1105
1651
  readonly attachments: AttachmentsResource;
1106
1652
  /**
1107
1653
  * Webhooks resource
1654
+ *
1655
+ * @example
1656
+ * ```typescript
1657
+ * // Create a webhook
1658
+ * const webhook = await client.webhooks.create('ws_abc123', {
1659
+ * url: 'https://example.com/webhook',
1660
+ * secret: 'your-secret',
1661
+ * events: ['ticket.created', 'ticket.updated'],
1662
+ * });
1663
+ * ```
1108
1664
  */
1109
1665
  readonly webhooks: WebhooksResource;
1110
1666
  /**
1111
1667
  * Categories resource
1668
+ *
1669
+ * @example
1670
+ * ```typescript
1671
+ * // Create a category
1672
+ * await client.categories.create('ws_abc123', { name: 'Billing', color: '#ef4444' });
1673
+ *
1674
+ * // Get category stats
1675
+ * const stats = await client.categories.getStats('ws_abc123');
1676
+ * ```
1112
1677
  */
1113
1678
  readonly categories: CategoriesResource;
1114
1679
  /**
1115
1680
  * Tags resource
1681
+ *
1682
+ * @example
1683
+ * ```typescript
1684
+ * // Create a tag
1685
+ * await client.tags.create('ws_abc123', { name: 'urgent', color: '#f59e0b' });
1686
+ *
1687
+ * // Merge tags
1688
+ * await client.tags.merge('ws_abc123', 'tag_target', ['tag_source1', 'tag_source2']);
1689
+ * ```
1116
1690
  */
1117
1691
  readonly tags: TagsResource;
1118
1692
  /**
1119
1693
  * Customers resource
1694
+ *
1695
+ * @example
1696
+ * ```typescript
1697
+ * // Create a customer
1698
+ * const customer = await client.customers.create('ws_abc123', {
1699
+ * email: 'user@example.com',
1700
+ * name: 'Jane Doe',
1701
+ * });
1702
+ *
1703
+ * // Search customers
1704
+ * const results = await client.customers.search('ws_abc123', 'jane');
1705
+ * ```
1120
1706
  */
1121
1707
  readonly customers: CustomersResource;
1122
1708
  /**
1123
1709
  * Custom fields resource
1710
+ *
1711
+ * @example
1712
+ * ```typescript
1713
+ * // Get all field definitions
1714
+ * const fields = await client.fields.getAll('ws_abc123');
1715
+ *
1716
+ * // Create a field
1717
+ * await client.fields.create('ws_abc123', 'ticket', {
1718
+ * key: 'order_id',
1719
+ * label: 'Order ID',
1720
+ * type: 'text',
1721
+ * required: true,
1722
+ * });
1723
+ * ```
1124
1724
  */
1125
1725
  readonly fields: FieldsResource;
1126
1726
  /**
1127
1727
  * Static webhook utilities
1728
+ *
1729
+ * @example
1730
+ * ```typescript
1731
+ * // Verify webhook signature
1732
+ * const isValid = DispatchTickets.webhooks.verifySignature(
1733
+ * rawBody,
1734
+ * req.headers['x-dispatch-signature'],
1735
+ * 'your-secret'
1736
+ * );
1737
+ *
1738
+ * // Generate signature for testing
1739
+ * const signature = DispatchTickets.webhooks.generateSignature(
1740
+ * JSON.stringify(payload),
1741
+ * 'your-secret'
1742
+ * );
1743
+ * ```
1128
1744
  */
1129
1745
  static readonly webhooks: {
1130
1746
  verifySignature(payload: string, signature: string, secret: string): boolean;
1131
1747
  generateSignature(payload: string, secret: string): string;
1132
1748
  };
1749
+ /**
1750
+ * Create a new Dispatch Tickets client
1751
+ *
1752
+ * @param config - Client configuration options
1753
+ * @throws Error if API key is not provided
1754
+ *
1755
+ * @example
1756
+ * ```typescript
1757
+ * const client = new DispatchTickets({
1758
+ * apiKey: 'sk_live_...',
1759
+ * });
1760
+ * ```
1761
+ */
1133
1762
  constructor(config: DispatchTicketsConfig);
1134
1763
  }
1135
1764
 
1765
+ /**
1766
+ * Options for creating a ticket via portal
1767
+ */
1768
+ interface PortalCreateTicketOptions extends ApiRequestOptions {
1769
+ /** Idempotency key to prevent duplicate creation */
1770
+ idempotencyKey?: string;
1771
+ }
1772
+ /**
1773
+ * Options for adding a comment via portal
1774
+ */
1775
+ interface PortalAddCommentOptions extends ApiRequestOptions {
1776
+ /** Idempotency key to prevent duplicate creation */
1777
+ idempotencyKey?: string;
1778
+ }
1779
+ /**
1780
+ * Portal tickets resource
1781
+ *
1782
+ * Provides methods for customers to manage their tickets via the portal API.
1783
+ *
1784
+ * @example
1785
+ * ```typescript
1786
+ * const portal = new DispatchPortal({ token: 'portal_token...' });
1787
+ *
1788
+ * // List tickets
1789
+ * const { data: tickets } = await portal.tickets.list();
1790
+ *
1791
+ * // Create a ticket
1792
+ * const ticket = await portal.tickets.create({
1793
+ * title: 'Need help with my order',
1794
+ * body: 'Order #12345 has not arrived...',
1795
+ * });
1796
+ *
1797
+ * // Add a comment
1798
+ * await portal.tickets.addComment(ticket.id, 'Any update on this?');
1799
+ * ```
1800
+ */
1801
+ declare class PortalTicketsResource extends BaseResource {
1802
+ /**
1803
+ * List the customer's tickets
1804
+ *
1805
+ * Returns a paginated list of tickets belonging to the authenticated customer.
1806
+ *
1807
+ * @param filters - Optional filters and pagination
1808
+ * @param options - Request options
1809
+ * @returns Paginated ticket list
1810
+ *
1811
+ * @example
1812
+ * ```typescript
1813
+ * // List all tickets
1814
+ * const { data: tickets, pagination } = await portal.tickets.list();
1815
+ *
1816
+ * // Filter by status
1817
+ * const openTickets = await portal.tickets.list({ status: 'open' });
1818
+ *
1819
+ * // Paginate
1820
+ * const page2 = await portal.tickets.list({ cursor: pagination.nextCursor });
1821
+ * ```
1822
+ */
1823
+ list(filters?: PortalListTicketsFilters, options?: ApiRequestOptions): Promise<PortalTicketListResponse>;
1824
+ /**
1825
+ * Iterate through all tickets with automatic pagination
1826
+ *
1827
+ * @param filters - Optional filters (cursor is managed automatically)
1828
+ * @returns Async iterator of tickets
1829
+ *
1830
+ * @example
1831
+ * ```typescript
1832
+ * for await (const ticket of portal.tickets.listAll({ status: 'open' })) {
1833
+ * console.log(ticket.title);
1834
+ * }
1835
+ * ```
1836
+ */
1837
+ listAll(filters?: Omit<PortalListTicketsFilters, 'cursor'>): AsyncIterable<PortalTicket>;
1838
+ /**
1839
+ * Get a ticket by ID
1840
+ *
1841
+ * Returns the ticket with its comments (excludes internal comments).
1842
+ *
1843
+ * @param ticketId - Ticket ID
1844
+ * @param options - Request options
1845
+ * @returns Ticket detail with comments
1846
+ *
1847
+ * @example
1848
+ * ```typescript
1849
+ * const ticket = await portal.tickets.get('tkt_abc123');
1850
+ * console.log(ticket.title);
1851
+ * console.log(`${ticket.comments.length} comments`);
1852
+ * ```
1853
+ */
1854
+ get(ticketId: string, options?: ApiRequestOptions): Promise<PortalTicketDetail>;
1855
+ /**
1856
+ * Create a new ticket
1857
+ *
1858
+ * @param data - Ticket data
1859
+ * @param options - Request options including idempotency key
1860
+ * @returns Created ticket
1861
+ *
1862
+ * @example
1863
+ * ```typescript
1864
+ * const ticket = await portal.tickets.create({
1865
+ * title: 'Need help with billing',
1866
+ * body: 'I was charged twice for my subscription...',
1867
+ * });
1868
+ * ```
1869
+ */
1870
+ create(data: PortalCreateTicketInput, options?: PortalCreateTicketOptions): Promise<PortalTicket>;
1871
+ /**
1872
+ * Add a comment to a ticket
1873
+ *
1874
+ * @param ticketId - Ticket ID
1875
+ * @param body - Comment text
1876
+ * @param options - Request options including idempotency key
1877
+ * @returns Created comment
1878
+ *
1879
+ * @example
1880
+ * ```typescript
1881
+ * const comment = await portal.tickets.addComment(
1882
+ * 'tkt_abc123',
1883
+ * 'Here is the additional information you requested...'
1884
+ * );
1885
+ * ```
1886
+ */
1887
+ addComment(ticketId: string, body: string, options?: PortalAddCommentOptions): Promise<PortalComment>;
1888
+ /**
1889
+ * Build query parameters from filters
1890
+ */
1891
+ private buildListQuery;
1892
+ }
1893
+
1894
+ /**
1895
+ * Configuration options for the Dispatch Portal client
1896
+ *
1897
+ * @example
1898
+ * ```typescript
1899
+ * const portal = new DispatchPortal({
1900
+ * token: 'portal_token_from_backend...',
1901
+ * });
1902
+ * ```
1903
+ */
1904
+ interface DispatchPortalConfig {
1905
+ /**
1906
+ * Portal token (required)
1907
+ *
1908
+ * Obtain this token by:
1909
+ * 1. Your backend calls `client.brands.generatePortalToken()` with the customer's email
1910
+ * 2. Pass the token to your frontend
1911
+ * 3. Frontend creates DispatchPortal with this token
1912
+ *
1913
+ * Or for self-auth mode:
1914
+ * 1. Customer clicks magic link email
1915
+ * 2. Your portal page calls `DispatchPortal.verify()` with the URL token
1916
+ * 3. Use the returned token to create DispatchPortal
1917
+ */
1918
+ token: string;
1919
+ /**
1920
+ * Base URL for the API
1921
+ * @default 'https://dispatch-tickets-api.onrender.com/v1'
1922
+ */
1923
+ baseUrl?: string;
1924
+ /**
1925
+ * Request timeout in milliseconds
1926
+ * @default 30000
1927
+ */
1928
+ timeout?: number;
1929
+ /**
1930
+ * Custom fetch implementation (for testing)
1931
+ */
1932
+ fetch?: FetchFunction;
1933
+ }
1934
+ /**
1935
+ * Dispatch Portal SDK client
1936
+ *
1937
+ * Customer-facing client for interacting with the portal API.
1938
+ * Use this to let customers view and manage their own tickets.
1939
+ *
1940
+ * @example Authenticated mode (backend generates token)
1941
+ * ```typescript
1942
+ * // On your backend:
1943
+ * const client = new DispatchTickets({ apiKey: 'sk_live_...' });
1944
+ * const { token } = await client.brands.generatePortalToken('br_abc123', {
1945
+ * email: 'customer@example.com',
1946
+ * name: 'Jane Doe',
1947
+ * });
1948
+ * // Pass token to frontend...
1949
+ *
1950
+ * // On your frontend:
1951
+ * const portal = new DispatchPortal({ token });
1952
+ * const { data: tickets } = await portal.tickets.list();
1953
+ * ```
1954
+ *
1955
+ * @example Self-auth mode (magic link)
1956
+ * ```typescript
1957
+ * // Customer clicks magic link, lands on your portal with ?token=xyz
1958
+ * const urlToken = new URLSearchParams(window.location.search).get('token');
1959
+ *
1960
+ * // Verify the magic link token
1961
+ * const { token } = await DispatchPortal.verify(urlToken);
1962
+ *
1963
+ * // Now use the portal token
1964
+ * const portal = new DispatchPortal({ token });
1965
+ * const { data: tickets } = await portal.tickets.list();
1966
+ * ```
1967
+ */
1968
+ declare class DispatchPortal {
1969
+ private readonly http;
1970
+ private readonly config;
1971
+ /**
1972
+ * Tickets resource for viewing and creating tickets
1973
+ *
1974
+ * @example
1975
+ * ```typescript
1976
+ * // List tickets
1977
+ * const { data: tickets } = await portal.tickets.list();
1978
+ *
1979
+ * // Create a ticket
1980
+ * const ticket = await portal.tickets.create({
1981
+ * title: 'Need help',
1982
+ * body: 'Something is broken...',
1983
+ * });
1984
+ *
1985
+ * // Add a comment
1986
+ * await portal.tickets.addComment(ticket.id, 'Here is more info...');
1987
+ * ```
1988
+ */
1989
+ readonly tickets: PortalTicketsResource;
1990
+ /**
1991
+ * Create a new Dispatch Portal client
1992
+ *
1993
+ * @param config - Portal client configuration
1994
+ * @throws Error if token is not provided
1995
+ */
1996
+ constructor(config: DispatchPortalConfig);
1997
+ /**
1998
+ * Verify a magic link token and get a portal token
1999
+ *
2000
+ * Call this when the customer clicks the magic link and lands on your portal.
2001
+ * The magic link contains a short-lived token that can be exchanged for a
2002
+ * longer-lived portal token.
2003
+ *
2004
+ * @param magicLinkToken - The token from the magic link URL
2005
+ * @param baseUrl - Optional API base URL
2006
+ * @param fetchFn - Optional custom fetch function (for testing)
2007
+ * @returns Portal token response
2008
+ *
2009
+ * @example
2010
+ * ```typescript
2011
+ * // Customer lands on: https://yourapp.com/portal?token=xyz
2012
+ * const urlToken = new URLSearchParams(window.location.search).get('token');
2013
+ *
2014
+ * const { token, email, name } = await DispatchPortal.verify(urlToken);
2015
+ *
2016
+ * // Store token and create client
2017
+ * localStorage.setItem('portalToken', token);
2018
+ * const portal = new DispatchPortal({ token });
2019
+ * ```
2020
+ */
2021
+ static verify(magicLinkToken: string, baseUrl?: string, fetchFn?: FetchFunction): Promise<PortalTokenResponse>;
2022
+ /**
2023
+ * Refresh the current portal token
2024
+ *
2025
+ * Call this before the token expires to get a new token with extended expiry.
2026
+ * The new token will have the same customer context.
2027
+ *
2028
+ * @returns New portal token response
2029
+ *
2030
+ * @example
2031
+ * ```typescript
2032
+ * // Check if token is close to expiry and refresh
2033
+ * const { token: newToken, expiresAt } = await portal.refresh();
2034
+ *
2035
+ * // Create new client with refreshed token
2036
+ * const newPortal = new DispatchPortal({ token: newToken });
2037
+ * ```
2038
+ */
2039
+ refresh(): Promise<PortalTokenResponse>;
2040
+ /**
2041
+ * Get the current token
2042
+ *
2043
+ * Useful for storing/passing the token.
2044
+ */
2045
+ get token(): string;
2046
+ }
2047
+
1136
2048
  /**
1137
2049
  * Base error class for all Dispatch Tickets SDK errors
1138
2050
  */
@@ -1140,20 +2052,32 @@ declare class DispatchTicketsError extends Error {
1140
2052
  readonly code: string;
1141
2053
  readonly statusCode?: number;
1142
2054
  readonly details?: Record<string, unknown>;
1143
- constructor(message: string, code: string, statusCode?: number, details?: Record<string, unknown>);
2055
+ /** Request ID for debugging with support */
2056
+ readonly requestId?: string;
2057
+ constructor(message: string, code: string, statusCode?: number, details?: Record<string, unknown>, requestId?: string);
1144
2058
  }
1145
2059
  /**
1146
2060
  * Thrown when API key is missing or invalid
1147
2061
  */
1148
2062
  declare class AuthenticationError extends DispatchTicketsError {
1149
- constructor(message?: string);
2063
+ constructor(message?: string, requestId?: string);
1150
2064
  }
1151
2065
  /**
1152
2066
  * Thrown when rate limit is exceeded
1153
2067
  */
1154
2068
  declare class RateLimitError extends DispatchTicketsError {
1155
2069
  readonly retryAfter?: number;
1156
- constructor(message?: string, retryAfter?: number);
2070
+ /** Rate limit ceiling */
2071
+ readonly limit?: number;
2072
+ /** Remaining requests in current window */
2073
+ readonly remaining?: number;
2074
+ /** Unix timestamp when rate limit resets */
2075
+ readonly reset?: number;
2076
+ constructor(message?: string, retryAfter?: number, requestId?: string, rateLimitInfo?: {
2077
+ limit?: number;
2078
+ remaining?: number;
2079
+ reset?: number;
2080
+ });
1157
2081
  }
1158
2082
  /**
1159
2083
  * Thrown when request validation fails
@@ -1166,7 +2090,7 @@ declare class ValidationError extends DispatchTicketsError {
1166
2090
  constructor(message?: string, errors?: Array<{
1167
2091
  field: string;
1168
2092
  message: string;
1169
- }>);
2093
+ }>, requestId?: string);
1170
2094
  }
1171
2095
  /**
1172
2096
  * Thrown when a resource is not found
@@ -1174,19 +2098,19 @@ declare class ValidationError extends DispatchTicketsError {
1174
2098
  declare class NotFoundError extends DispatchTicketsError {
1175
2099
  readonly resourceType?: string;
1176
2100
  readonly resourceId?: string;
1177
- constructor(message?: string, resourceType?: string, resourceId?: string);
2101
+ constructor(message?: string, resourceType?: string, resourceId?: string, requestId?: string);
1178
2102
  }
1179
2103
  /**
1180
2104
  * Thrown when there's a conflict (e.g., duplicate resource)
1181
2105
  */
1182
2106
  declare class ConflictError extends DispatchTicketsError {
1183
- constructor(message?: string);
2107
+ constructor(message?: string, requestId?: string);
1184
2108
  }
1185
2109
  /**
1186
2110
  * Thrown when the server returns an unexpected error
1187
2111
  */
1188
2112
  declare class ServerError extends DispatchTicketsError {
1189
- constructor(message?: string, statusCode?: number);
2113
+ constructor(message?: string, statusCode?: number, requestId?: string);
1190
2114
  }
1191
2115
  /**
1192
2116
  * Thrown when request times out
@@ -1200,6 +2124,42 @@ declare class TimeoutError extends DispatchTicketsError {
1200
2124
  declare class NetworkError extends DispatchTicketsError {
1201
2125
  constructor(message?: string);
1202
2126
  }
2127
+ /**
2128
+ * Check if an error is a DispatchTicketsError
2129
+ */
2130
+ declare function isDispatchTicketsError(error: unknown): error is DispatchTicketsError;
2131
+ /**
2132
+ * Check if an error is an AuthenticationError
2133
+ */
2134
+ declare function isAuthenticationError(error: unknown): error is AuthenticationError;
2135
+ /**
2136
+ * Check if an error is a RateLimitError
2137
+ */
2138
+ declare function isRateLimitError(error: unknown): error is RateLimitError;
2139
+ /**
2140
+ * Check if an error is a ValidationError
2141
+ */
2142
+ declare function isValidationError(error: unknown): error is ValidationError;
2143
+ /**
2144
+ * Check if an error is a NotFoundError
2145
+ */
2146
+ declare function isNotFoundError(error: unknown): error is NotFoundError;
2147
+ /**
2148
+ * Check if an error is a ConflictError
2149
+ */
2150
+ declare function isConflictError(error: unknown): error is ConflictError;
2151
+ /**
2152
+ * Check if an error is a ServerError
2153
+ */
2154
+ declare function isServerError(error: unknown): error is ServerError;
2155
+ /**
2156
+ * Check if an error is a TimeoutError
2157
+ */
2158
+ declare function isTimeoutError(error: unknown): error is TimeoutError;
2159
+ /**
2160
+ * Check if an error is a NetworkError
2161
+ */
2162
+ declare function isNetworkError(error: unknown): error is NetworkError;
1203
2163
 
1204
2164
  /**
1205
2165
  * All supported webhook event types
@@ -1379,4 +2339,4 @@ declare const webhookUtils: {
1379
2339
  */
1380
2340
  declare function collectAll<T>(iterable: AsyncIterable<T>): Promise<T[]>;
1381
2341
 
1382
- export { type Account, type AccountUsage, type ApiKey, type ApiKeyWithSecret, type Attachment, type AttachmentStatus, type AttachmentWithUrl, AuthenticationError, type AuthorType, type Brand, type BulkAction, type BulkActionResult, type Category, type CategoryStats, type Comment, type CommentCreatedData, type CommentCreatedEvent, type Company, ConflictError, type CreateApiKeyInput, type CreateBrandInput, type CreateCategoryInput, type CreateCommentInput, type CreateCommentOptions, type CreateCustomerInput, type CreateFieldInput, type CreateTagInput, type CreateTicketInput, type CreateTicketOptions, type CreateWebhookInput, type Customer, type DeleteBrandPreview, DispatchTickets, type DispatchTicketsConfig, DispatchTicketsError, type EntityType, type EventCommentData, type EventCustomerInfo, type FieldDefinition, type FieldDefinitions, type FieldType, type InitiateUploadInput, type InitiateUploadResponse, type Link, type ListCustomersFilters, type ListTicketsFilters, type MergeTagsInput, type MergeTicketsInput, NetworkError, NotFoundError, type PaginatedResponse, RateLimitError, ServerError, type SortOrder, type Tag, type Ticket, type TicketCreatedData, type TicketCreatedEvent, type TicketPriority, type TicketSource, type TicketStatus, type TicketUpdatedData, type TicketUpdatedEvent, TimeoutError, type UpdateApiKeyScopeInput, type UpdateBrandInput, type UpdateCategoryInput, type UpdateCommentInput, type UpdateCustomerInput, type UpdateFieldInput, type UpdateTagInput, type UpdateTicketInput, ValidationError, type Webhook, type WebhookDelivery, type WebhookEvent, type WebhookEventEnvelope, type WebhookEventMap, type WebhookEventName, type WebhookEventType, collectAll, isCommentCreatedEvent, isTicketCreatedEvent, isTicketUpdatedEvent, parseWebhookEvent, webhookUtils };
2342
+ export { type Account, type AccountUsage, type ApiKey, type ApiKeyWithSecret, type ApiRequestOptions, type Attachment, type AttachmentStatus, type AttachmentWithUrl, AuthenticationError, type AuthorType, type Brand, type BulkAction, type BulkActionResult, type Category, type CategoryStats, type Comment, type CommentCreatedData, type CommentCreatedEvent, type Company, ConflictError, type CreateApiKeyInput, type CreateBrandInput, type CreateCategoryInput, type CreateCommentInput, type CreateCommentOptions, type CreateCustomerInput, type CreateFieldInput, type CreateTagInput, type CreateTicketInput, type CreateTicketOptions, type CreateWebhookInput, type Customer, type DeleteBrandPreview, DispatchPortal, type DispatchPortalConfig, DispatchTickets, type DispatchTicketsConfig, DispatchTicketsError, type EntityType, type EventCommentData, type EventCustomerInfo, type FieldDefinition, type FieldDefinitions, type FieldType, type GeneratePortalTokenInput, type Hooks, type InitiateUploadInput, type InitiateUploadResponse, type Link, type ListCustomersFilters, type ListTicketsFilters, type MergeTagsInput, type MergeTicketsInput, NetworkError, NotFoundError, type PaginatedResponse, type PortalComment, type PortalCreateTicketInput, type PortalListTicketsFilters, type PortalTicket, type PortalTicketDetail, type PortalTicketListResponse, type PortalTokenResponse, RateLimitError, type RateLimitInfo, type RequestContext, type ResponseContext, type RetryConfig, type SendMagicLinkInput, ServerError, type SortOrder, type Tag, type Ticket, type TicketCreatedData, type TicketCreatedEvent, type TicketPriority, type TicketSource, type TicketStatus, type TicketUpdatedData, type TicketUpdatedEvent, TimeoutError, type UpdateApiKeyScopeInput, type UpdateBrandInput, type UpdateCategoryInput, type UpdateCommentInput, type UpdateCustomerInput, type UpdateFieldInput, type UpdateTagInput, type UpdateTicketInput, ValidationError, type Webhook, type WebhookDelivery, type WebhookEvent, type WebhookEventEnvelope, type WebhookEventMap, type WebhookEventName, type WebhookEventType, collectAll, isAuthenticationError, isCommentCreatedEvent, isConflictError, isDispatchTicketsError, isNetworkError, isNotFoundError, isRateLimitError, isServerError, isTicketCreatedEvent, isTicketUpdatedEvent, isTimeoutError, isValidationError, parseWebhookEvent, webhookUtils };