@dispatchtickets/sdk 0.1.0 → 0.5.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.ts CHANGED
@@ -1,9 +1,124 @@
1
+ /**
2
+ * Custom fetch function type
3
+ */
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
+ }
1
104
  interface HttpClientConfig {
2
105
  baseUrl: string;
3
106
  apiKey: string;
4
107
  timeout: number;
5
108
  maxRetries: number;
6
109
  debug?: boolean;
110
+ /**
111
+ * Custom fetch implementation for testing/mocking
112
+ */
113
+ fetch?: FetchFunction;
114
+ /**
115
+ * Fine-grained retry configuration
116
+ */
117
+ retry?: RetryConfig;
118
+ /**
119
+ * Hooks for observability
120
+ */
121
+ hooks?: Hooks;
7
122
  }
8
123
  interface RequestOptions {
9
124
  method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
@@ -12,39 +127,201 @@ interface RequestOptions {
12
127
  query?: Record<string, string | number | boolean | undefined>;
13
128
  headers?: Record<string, string>;
14
129
  idempotencyKey?: string;
130
+ /**
131
+ * AbortSignal to cancel the request
132
+ */
133
+ signal?: AbortSignal;
134
+ }
135
+ /**
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;
15
145
  }
16
146
  /**
17
- * HTTP client with retry logic and error handling
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
18
156
  */
19
157
  declare class HttpClient {
20
158
  private readonly config;
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?;
21
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;
22
174
  /**
23
175
  * Execute an HTTP request with retry logic
24
176
  */
25
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;
26
184
  private buildUrl;
27
185
  private buildHeaders;
28
186
  private executeRequest;
187
+ private extractRateLimitInfo;
29
188
  private handleResponse;
30
- private calculateBackoff;
31
189
  private sleep;
32
190
  }
33
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
+ }
34
211
  /**
35
212
  * Base class for all resource classes
213
+ * @internal
36
214
  */
37
215
  declare abstract class BaseResource {
38
216
  protected readonly http: HttpClient;
39
217
  constructor(http: HttpClient);
40
- protected _get<T>(path: string, query?: RequestOptions['query']): Promise<T>;
218
+ protected _get<T>(path: string, query?: RequestOptions['query'], options?: ApiRequestOptions): Promise<T>;
41
219
  protected _post<T>(path: string, body?: unknown, options?: {
42
220
  idempotencyKey?: string;
43
221
  query?: RequestOptions['query'];
222
+ signal?: AbortSignal;
44
223
  }): Promise<T>;
45
- protected _patch<T>(path: string, body?: unknown): Promise<T>;
46
- protected _put<T>(path: string, body?: unknown): Promise<T>;
47
- 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>;
227
+ }
228
+
229
+ /**
230
+ * Account represents the organization/company using Dispatch Tickets
231
+ */
232
+ interface Account {
233
+ id: string;
234
+ stackbeCustomerId: string;
235
+ stackbeOrganizationId: string;
236
+ createdAt: string;
237
+ updatedAt: string;
238
+ }
239
+ /**
240
+ * Usage statistics for the account
241
+ */
242
+ interface AccountUsage {
243
+ ticketsThisMonth: number;
244
+ ticketsTotal: number;
245
+ brandsCount: number;
246
+ plan?: {
247
+ name: string;
248
+ ticketLimit: number;
249
+ brandLimit: number | null;
250
+ };
251
+ }
252
+ /**
253
+ * API key for programmatic access
254
+ */
255
+ interface ApiKey {
256
+ id: string;
257
+ name: string;
258
+ keyPreview: string;
259
+ allBrands: boolean;
260
+ brandIds: string[];
261
+ lastUsedAt: string | null;
262
+ createdAt: string;
263
+ }
264
+ /**
265
+ * API key with the full key value (only returned on creation)
266
+ */
267
+ interface ApiKeyWithSecret extends ApiKey {
268
+ key: string;
269
+ }
270
+ /**
271
+ * Input for creating an API key
272
+ */
273
+ interface CreateApiKeyInput {
274
+ name: string;
275
+ /**
276
+ * If true, the API key can access all brands (current and future)
277
+ */
278
+ allBrands?: boolean;
279
+ /**
280
+ * Specific brand IDs this key can access (ignored if allBrands is true)
281
+ */
282
+ brandIds?: string[];
283
+ }
284
+ /**
285
+ * Input for updating API key scope
286
+ */
287
+ interface UpdateApiKeyScopeInput {
288
+ allBrands?: boolean;
289
+ brandIds?: string[];
290
+ }
291
+
292
+ /**
293
+ * Accounts resource for managing the current account
294
+ */
295
+ declare class AccountsResource extends BaseResource {
296
+ /**
297
+ * Get the current account
298
+ */
299
+ me(): Promise<Account>;
300
+ /**
301
+ * Get usage statistics for the current account
302
+ */
303
+ getUsage(): Promise<AccountUsage>;
304
+ /**
305
+ * List all API keys for the current account
306
+ */
307
+ listApiKeys(): Promise<ApiKey[]>;
308
+ /**
309
+ * Create a new API key
310
+ *
311
+ * Note: The full key value is only returned once on creation.
312
+ * Store it securely as it cannot be retrieved again.
313
+ */
314
+ createApiKey(data: CreateApiKeyInput): Promise<ApiKeyWithSecret>;
315
+ /**
316
+ * Update the brand scope for an API key
317
+ */
318
+ updateApiKeyScope(keyId: string, data: UpdateApiKeyScopeInput): Promise<{
319
+ success: boolean;
320
+ }>;
321
+ /**
322
+ * Revoke an API key
323
+ */
324
+ revokeApiKey(keyId: string): Promise<void>;
48
325
  }
49
326
 
50
327
  /**
@@ -144,6 +421,26 @@ declare class BrandsResource extends BaseResource {
144
421
  * Update the ticket schema for a brand
145
422
  */
146
423
  updateSchema(brandId: string, schema: Record<string, unknown>): Promise<Record<string, unknown>>;
424
+ /**
425
+ * Get the inbound email address for a brand
426
+ *
427
+ * Emails sent to this address will automatically create tickets.
428
+ *
429
+ * @param brandId - The brand ID
430
+ * @param domain - Optional custom inbound domain (default: inbound.dispatchtickets.com)
431
+ * @returns The inbound email address
432
+ *
433
+ * @example
434
+ * ```typescript
435
+ * const email = client.brands.getInboundEmail('br_abc123');
436
+ * // Returns: br_abc123@inbound.dispatchtickets.com
437
+ *
438
+ * // With custom domain:
439
+ * const customEmail = client.brands.getInboundEmail('br_abc123', 'support.mycompany.com');
440
+ * // Returns: br_abc123@support.mycompany.com
441
+ * ```
442
+ */
443
+ getInboundEmail(brandId: string, domain?: string): string;
147
444
  }
148
445
 
149
446
  /**
@@ -185,9 +482,10 @@ type AuthorType = 'CUSTOMER' | 'AGENT' | 'SYSTEM';
185
482
  */
186
483
  type AttachmentStatus = 'PENDING' | 'UPLOADED' | 'FAILED';
187
484
  /**
188
- * Webhook event types
485
+ * Webhook event type strings (for subscription)
486
+ * @deprecated Use WebhookEventType from events.ts for typed event handling
189
487
  */
190
- type WebhookEvent = 'ticket.created' | 'ticket.updated' | 'ticket.deleted' | 'comment.created' | 'comment.updated' | 'comment.deleted' | 'attachment.created' | 'attachment.deleted';
488
+ type WebhookEventName = 'ticket.created' | 'ticket.updated' | 'ticket.deleted' | 'ticket.comment.created' | 'comment.created' | 'comment.updated' | 'comment.deleted' | 'attachment.created' | 'attachment.deleted';
191
489
  /**
192
490
  * Custom field types
193
491
  */
@@ -655,7 +953,7 @@ interface Webhook {
655
953
  id: string;
656
954
  brandId: string;
657
955
  url: string;
658
- events: WebhookEvent[];
956
+ events: WebhookEventName[];
659
957
  enabled: boolean;
660
958
  failureCount: number;
661
959
  lastTriggered?: string;
@@ -669,7 +967,7 @@ interface Webhook {
669
967
  interface CreateWebhookInput {
670
968
  url: string;
671
969
  secret: string;
672
- events: WebhookEvent[];
970
+ events: WebhookEventName[];
673
971
  }
674
972
  /**
675
973
  * Webhook delivery record
@@ -677,7 +975,7 @@ interface CreateWebhookInput {
677
975
  interface WebhookDelivery {
678
976
  id: string;
679
977
  webhookId: string;
680
- event: WebhookEvent;
978
+ event: WebhookEventName;
681
979
  payload: Record<string, unknown>;
682
980
  status: 'PENDING' | 'SUCCESS' | 'FAILED' | 'RETRYING';
683
981
  attempts: number;
@@ -896,10 +1194,30 @@ declare class FieldsResource extends BaseResource {
896
1194
 
897
1195
  /**
898
1196
  * Configuration options for the Dispatch Tickets client
1197
+ *
1198
+ * @example
1199
+ * ```typescript
1200
+ * const client = new DispatchTickets({
1201
+ * apiKey: 'sk_live_...',
1202
+ * timeout: 30000,
1203
+ * retry: {
1204
+ * maxRetries: 5,
1205
+ * retryableStatuses: [429, 500, 502, 503, 504],
1206
+ * },
1207
+ * hooks: {
1208
+ * onRequest: (ctx) => console.log(`${ctx.method} ${ctx.url}`),
1209
+ * onResponse: (ctx) => console.log(`${ctx.status} in ${ctx.durationMs}ms`),
1210
+ * },
1211
+ * });
1212
+ * ```
899
1213
  */
900
1214
  interface DispatchTicketsConfig {
901
1215
  /**
902
1216
  * Your API key (required)
1217
+ *
1218
+ * Get your API key from the Dispatch Tickets dashboard.
1219
+ * Keys starting with `sk_live_` are production keys.
1220
+ * Keys starting with `sk_test_` are test keys.
903
1221
  */
904
1222
  apiKey: string;
905
1223
  /**
@@ -915,18 +1233,90 @@ interface DispatchTicketsConfig {
915
1233
  /**
916
1234
  * Maximum number of retries for failed requests
917
1235
  * @default 3
1236
+ * @deprecated Use `retry.maxRetries` instead for fine-grained control
918
1237
  */
919
1238
  maxRetries?: number;
920
1239
  /**
921
1240
  * Enable debug logging
1241
+ *
1242
+ * When enabled, logs all requests, responses, and request IDs to console.
922
1243
  * @default false
923
1244
  */
924
1245
  debug?: boolean;
1246
+ /**
1247
+ * Custom fetch implementation for testing/mocking
1248
+ *
1249
+ * @example
1250
+ * ```typescript
1251
+ * const mockFetch = vi.fn().mockResolvedValue({
1252
+ * ok: true,
1253
+ * status: 200,
1254
+ * headers: { get: () => 'application/json' },
1255
+ * json: () => Promise.resolve({ id: 'test' }),
1256
+ * });
1257
+ *
1258
+ * const client = new DispatchTickets({
1259
+ * apiKey: 'sk_test_...',
1260
+ * fetch: mockFetch,
1261
+ * });
1262
+ * ```
1263
+ */
1264
+ fetch?: FetchFunction;
1265
+ /**
1266
+ * Fine-grained retry configuration
1267
+ *
1268
+ * @example
1269
+ * ```typescript
1270
+ * const client = new DispatchTickets({
1271
+ * apiKey: 'sk_live_...',
1272
+ * retry: {
1273
+ * maxRetries: 5,
1274
+ * retryableStatuses: [429, 500, 502, 503, 504],
1275
+ * initialDelayMs: 500,
1276
+ * maxDelayMs: 60000,
1277
+ * backoffMultiplier: 2,
1278
+ * jitter: 0.25,
1279
+ * },
1280
+ * });
1281
+ * ```
1282
+ */
1283
+ retry?: RetryConfig;
1284
+ /**
1285
+ * Hooks for observability and customization
1286
+ *
1287
+ * @example
1288
+ * ```typescript
1289
+ * const client = new DispatchTickets({
1290
+ * apiKey: 'sk_live_...',
1291
+ * hooks: {
1292
+ * onRequest: (ctx) => {
1293
+ * console.log(`[${new Date().toISOString()}] ${ctx.method} ${ctx.url}`);
1294
+ * },
1295
+ * onResponse: (ctx) => {
1296
+ * console.log(`[${ctx.requestId}] ${ctx.status} in ${ctx.durationMs}ms`);
1297
+ * },
1298
+ * onError: (error, ctx) => {
1299
+ * console.error(`Request failed: ${error.message}`);
1300
+ * // Send to error tracking service
1301
+ * Sentry.captureException(error);
1302
+ * },
1303
+ * onRetry: (ctx, error, delayMs) => {
1304
+ * console.log(`Retrying in ${delayMs}ms (attempt ${ctx.attempt + 1})`);
1305
+ * },
1306
+ * },
1307
+ * });
1308
+ * ```
1309
+ */
1310
+ hooks?: Hooks;
925
1311
  }
926
1312
  /**
927
1313
  * Dispatch Tickets SDK client
928
1314
  *
929
- * @example
1315
+ * The main entry point for interacting with the Dispatch Tickets API.
1316
+ * Create a client instance with your API key and use the resource methods
1317
+ * to manage tickets, comments, attachments, and more.
1318
+ *
1319
+ * @example Basic usage
930
1320
  * ```typescript
931
1321
  * import { DispatchTickets } from '@dispatchtickets/sdk';
932
1322
  *
@@ -948,52 +1338,244 @@ interface DispatchTicketsConfig {
948
1338
  * console.log(ticket.title);
949
1339
  * }
950
1340
  * ```
1341
+ *
1342
+ * @example With request cancellation
1343
+ * ```typescript
1344
+ * const controller = new AbortController();
1345
+ *
1346
+ * // Cancel after 5 seconds
1347
+ * setTimeout(() => controller.abort(), 5000);
1348
+ *
1349
+ * try {
1350
+ * const tickets = await client.tickets.listPage('ws_abc123', {}, {
1351
+ * signal: controller.signal,
1352
+ * });
1353
+ * } catch (error) {
1354
+ * if (error.message === 'Request aborted by user') {
1355
+ * console.log('Request was cancelled');
1356
+ * }
1357
+ * }
1358
+ * ```
1359
+ *
1360
+ * @example With hooks for logging
1361
+ * ```typescript
1362
+ * const client = new DispatchTickets({
1363
+ * apiKey: 'sk_live_...',
1364
+ * hooks: {
1365
+ * onRequest: (ctx) => console.log(`→ ${ctx.method} ${ctx.url}`),
1366
+ * onResponse: (ctx) => console.log(`← ${ctx.status} (${ctx.durationMs}ms)`),
1367
+ * },
1368
+ * });
1369
+ * ```
951
1370
  */
952
1371
  declare class DispatchTickets {
953
1372
  private readonly http;
1373
+ /**
1374
+ * Accounts resource for managing the current account and API keys
1375
+ *
1376
+ * @example
1377
+ * ```typescript
1378
+ * // Get current account
1379
+ * const account = await client.accounts.me();
1380
+ *
1381
+ * // Get usage statistics
1382
+ * const usage = await client.accounts.getUsage();
1383
+ *
1384
+ * // Create a new API key
1385
+ * const newKey = await client.accounts.createApiKey({
1386
+ * name: 'Production',
1387
+ * allBrands: true,
1388
+ * });
1389
+ * ```
1390
+ */
1391
+ readonly accounts: AccountsResource;
954
1392
  /**
955
1393
  * Brands (workspaces) resource
1394
+ *
1395
+ * Brands are isolated containers for tickets. Each brand can have its own
1396
+ * email address, categories, tags, and settings.
1397
+ *
1398
+ * @example
1399
+ * ```typescript
1400
+ * // List all brands
1401
+ * const brands = await client.brands.list();
1402
+ *
1403
+ * // Create a new brand
1404
+ * const brand = await client.brands.create({
1405
+ * name: 'Acme Support',
1406
+ * slug: 'acme',
1407
+ * });
1408
+ *
1409
+ * // Get inbound email address
1410
+ * const email = client.brands.getInboundEmail('br_abc123');
1411
+ * // Returns: br_abc123@inbound.dispatchtickets.com
1412
+ * ```
956
1413
  */
957
1414
  readonly brands: BrandsResource;
958
1415
  /**
959
1416
  * Tickets resource
1417
+ *
1418
+ * @example
1419
+ * ```typescript
1420
+ * // Create a ticket
1421
+ * const ticket = await client.tickets.create('ws_abc123', {
1422
+ * title: 'Issue with billing',
1423
+ * body: 'I was charged twice...',
1424
+ * priority: 'high',
1425
+ * });
1426
+ *
1427
+ * // Iterate through all tickets
1428
+ * for await (const ticket of client.tickets.list('ws_abc123', { status: 'open' })) {
1429
+ * console.log(ticket.title);
1430
+ * }
1431
+ * ```
960
1432
  */
961
1433
  readonly tickets: TicketsResource;
962
1434
  /**
963
1435
  * Comments resource
1436
+ *
1437
+ * @example
1438
+ * ```typescript
1439
+ * // Add a comment
1440
+ * const comment = await client.comments.create('ws_abc123', 'tkt_xyz', {
1441
+ * body: 'Thanks for your patience!',
1442
+ * authorType: 'AGENT',
1443
+ * });
1444
+ *
1445
+ * // List comments
1446
+ * const comments = await client.comments.list('ws_abc123', 'tkt_xyz');
1447
+ * ```
964
1448
  */
965
1449
  readonly comments: CommentsResource;
966
1450
  /**
967
1451
  * Attachments resource
1452
+ *
1453
+ * @example
1454
+ * ```typescript
1455
+ * // Simple upload
1456
+ * const attachment = await client.attachments.upload(
1457
+ * 'ws_abc123',
1458
+ * 'tkt_xyz',
1459
+ * fileBuffer,
1460
+ * 'document.pdf',
1461
+ * 'application/pdf'
1462
+ * );
1463
+ *
1464
+ * // Get download URL
1465
+ * const { downloadUrl } = await client.attachments.get('ws_abc123', 'tkt_xyz', 'att_abc');
1466
+ * ```
968
1467
  */
969
1468
  readonly attachments: AttachmentsResource;
970
1469
  /**
971
1470
  * Webhooks resource
1471
+ *
1472
+ * @example
1473
+ * ```typescript
1474
+ * // Create a webhook
1475
+ * const webhook = await client.webhooks.create('ws_abc123', {
1476
+ * url: 'https://example.com/webhook',
1477
+ * secret: 'your-secret',
1478
+ * events: ['ticket.created', 'ticket.updated'],
1479
+ * });
1480
+ * ```
972
1481
  */
973
1482
  readonly webhooks: WebhooksResource;
974
1483
  /**
975
1484
  * Categories resource
1485
+ *
1486
+ * @example
1487
+ * ```typescript
1488
+ * // Create a category
1489
+ * await client.categories.create('ws_abc123', { name: 'Billing', color: '#ef4444' });
1490
+ *
1491
+ * // Get category stats
1492
+ * const stats = await client.categories.getStats('ws_abc123');
1493
+ * ```
976
1494
  */
977
1495
  readonly categories: CategoriesResource;
978
1496
  /**
979
1497
  * Tags resource
1498
+ *
1499
+ * @example
1500
+ * ```typescript
1501
+ * // Create a tag
1502
+ * await client.tags.create('ws_abc123', { name: 'urgent', color: '#f59e0b' });
1503
+ *
1504
+ * // Merge tags
1505
+ * await client.tags.merge('ws_abc123', 'tag_target', ['tag_source1', 'tag_source2']);
1506
+ * ```
980
1507
  */
981
1508
  readonly tags: TagsResource;
982
1509
  /**
983
1510
  * Customers resource
1511
+ *
1512
+ * @example
1513
+ * ```typescript
1514
+ * // Create a customer
1515
+ * const customer = await client.customers.create('ws_abc123', {
1516
+ * email: 'user@example.com',
1517
+ * name: 'Jane Doe',
1518
+ * });
1519
+ *
1520
+ * // Search customers
1521
+ * const results = await client.customers.search('ws_abc123', 'jane');
1522
+ * ```
984
1523
  */
985
1524
  readonly customers: CustomersResource;
986
1525
  /**
987
1526
  * Custom fields resource
1527
+ *
1528
+ * @example
1529
+ * ```typescript
1530
+ * // Get all field definitions
1531
+ * const fields = await client.fields.getAll('ws_abc123');
1532
+ *
1533
+ * // Create a field
1534
+ * await client.fields.create('ws_abc123', 'ticket', {
1535
+ * key: 'order_id',
1536
+ * label: 'Order ID',
1537
+ * type: 'text',
1538
+ * required: true,
1539
+ * });
1540
+ * ```
988
1541
  */
989
1542
  readonly fields: FieldsResource;
990
1543
  /**
991
1544
  * Static webhook utilities
1545
+ *
1546
+ * @example
1547
+ * ```typescript
1548
+ * // Verify webhook signature
1549
+ * const isValid = DispatchTickets.webhooks.verifySignature(
1550
+ * rawBody,
1551
+ * req.headers['x-dispatch-signature'],
1552
+ * 'your-secret'
1553
+ * );
1554
+ *
1555
+ * // Generate signature for testing
1556
+ * const signature = DispatchTickets.webhooks.generateSignature(
1557
+ * JSON.stringify(payload),
1558
+ * 'your-secret'
1559
+ * );
1560
+ * ```
992
1561
  */
993
1562
  static readonly webhooks: {
994
1563
  verifySignature(payload: string, signature: string, secret: string): boolean;
995
1564
  generateSignature(payload: string, secret: string): string;
996
1565
  };
1566
+ /**
1567
+ * Create a new Dispatch Tickets client
1568
+ *
1569
+ * @param config - Client configuration options
1570
+ * @throws Error if API key is not provided
1571
+ *
1572
+ * @example
1573
+ * ```typescript
1574
+ * const client = new DispatchTickets({
1575
+ * apiKey: 'sk_live_...',
1576
+ * });
1577
+ * ```
1578
+ */
997
1579
  constructor(config: DispatchTicketsConfig);
998
1580
  }
999
1581
 
@@ -1004,20 +1586,32 @@ declare class DispatchTicketsError extends Error {
1004
1586
  readonly code: string;
1005
1587
  readonly statusCode?: number;
1006
1588
  readonly details?: Record<string, unknown>;
1007
- constructor(message: string, code: string, statusCode?: number, details?: Record<string, unknown>);
1589
+ /** Request ID for debugging with support */
1590
+ readonly requestId?: string;
1591
+ constructor(message: string, code: string, statusCode?: number, details?: Record<string, unknown>, requestId?: string);
1008
1592
  }
1009
1593
  /**
1010
1594
  * Thrown when API key is missing or invalid
1011
1595
  */
1012
1596
  declare class AuthenticationError extends DispatchTicketsError {
1013
- constructor(message?: string);
1597
+ constructor(message?: string, requestId?: string);
1014
1598
  }
1015
1599
  /**
1016
1600
  * Thrown when rate limit is exceeded
1017
1601
  */
1018
1602
  declare class RateLimitError extends DispatchTicketsError {
1019
1603
  readonly retryAfter?: number;
1020
- constructor(message?: string, retryAfter?: number);
1604
+ /** Rate limit ceiling */
1605
+ readonly limit?: number;
1606
+ /** Remaining requests in current window */
1607
+ readonly remaining?: number;
1608
+ /** Unix timestamp when rate limit resets */
1609
+ readonly reset?: number;
1610
+ constructor(message?: string, retryAfter?: number, requestId?: string, rateLimitInfo?: {
1611
+ limit?: number;
1612
+ remaining?: number;
1613
+ reset?: number;
1614
+ });
1021
1615
  }
1022
1616
  /**
1023
1617
  * Thrown when request validation fails
@@ -1030,7 +1624,7 @@ declare class ValidationError extends DispatchTicketsError {
1030
1624
  constructor(message?: string, errors?: Array<{
1031
1625
  field: string;
1032
1626
  message: string;
1033
- }>);
1627
+ }>, requestId?: string);
1034
1628
  }
1035
1629
  /**
1036
1630
  * Thrown when a resource is not found
@@ -1038,19 +1632,19 @@ declare class ValidationError extends DispatchTicketsError {
1038
1632
  declare class NotFoundError extends DispatchTicketsError {
1039
1633
  readonly resourceType?: string;
1040
1634
  readonly resourceId?: string;
1041
- constructor(message?: string, resourceType?: string, resourceId?: string);
1635
+ constructor(message?: string, resourceType?: string, resourceId?: string, requestId?: string);
1042
1636
  }
1043
1637
  /**
1044
1638
  * Thrown when there's a conflict (e.g., duplicate resource)
1045
1639
  */
1046
1640
  declare class ConflictError extends DispatchTicketsError {
1047
- constructor(message?: string);
1641
+ constructor(message?: string, requestId?: string);
1048
1642
  }
1049
1643
  /**
1050
1644
  * Thrown when the server returns an unexpected error
1051
1645
  */
1052
1646
  declare class ServerError extends DispatchTicketsError {
1053
- constructor(message?: string, statusCode?: number);
1647
+ constructor(message?: string, statusCode?: number, requestId?: string);
1054
1648
  }
1055
1649
  /**
1056
1650
  * Thrown when request times out
@@ -1064,6 +1658,172 @@ declare class TimeoutError extends DispatchTicketsError {
1064
1658
  declare class NetworkError extends DispatchTicketsError {
1065
1659
  constructor(message?: string);
1066
1660
  }
1661
+ /**
1662
+ * Check if an error is a DispatchTicketsError
1663
+ */
1664
+ declare function isDispatchTicketsError(error: unknown): error is DispatchTicketsError;
1665
+ /**
1666
+ * Check if an error is an AuthenticationError
1667
+ */
1668
+ declare function isAuthenticationError(error: unknown): error is AuthenticationError;
1669
+ /**
1670
+ * Check if an error is a RateLimitError
1671
+ */
1672
+ declare function isRateLimitError(error: unknown): error is RateLimitError;
1673
+ /**
1674
+ * Check if an error is a ValidationError
1675
+ */
1676
+ declare function isValidationError(error: unknown): error is ValidationError;
1677
+ /**
1678
+ * Check if an error is a NotFoundError
1679
+ */
1680
+ declare function isNotFoundError(error: unknown): error is NotFoundError;
1681
+ /**
1682
+ * Check if an error is a ConflictError
1683
+ */
1684
+ declare function isConflictError(error: unknown): error is ConflictError;
1685
+ /**
1686
+ * Check if an error is a ServerError
1687
+ */
1688
+ declare function isServerError(error: unknown): error is ServerError;
1689
+ /**
1690
+ * Check if an error is a TimeoutError
1691
+ */
1692
+ declare function isTimeoutError(error: unknown): error is TimeoutError;
1693
+ /**
1694
+ * Check if an error is a NetworkError
1695
+ */
1696
+ declare function isNetworkError(error: unknown): error is NetworkError;
1697
+
1698
+ /**
1699
+ * All supported webhook event types
1700
+ */
1701
+ type WebhookEventType = 'ticket.created' | 'ticket.updated' | 'ticket.comment.created';
1702
+ /**
1703
+ * Base webhook event envelope
1704
+ */
1705
+ interface WebhookEventEnvelope<T extends WebhookEventType, D> {
1706
+ /** Unique event ID */
1707
+ id: string;
1708
+ /** Event type */
1709
+ event: T;
1710
+ /** Brand ID that triggered the event */
1711
+ brand_id: string;
1712
+ /** Event data */
1713
+ data: D;
1714
+ /** ISO timestamp when event was created */
1715
+ timestamp: string;
1716
+ }
1717
+ /**
1718
+ * Customer info included in events
1719
+ */
1720
+ interface EventCustomerInfo {
1721
+ customerId: string | null;
1722
+ customerEmail: string | null;
1723
+ customerName: string | null;
1724
+ }
1725
+ /**
1726
+ * Payload for ticket.created event
1727
+ */
1728
+ interface TicketCreatedData extends EventCustomerInfo {
1729
+ id: string;
1730
+ ticketNumber: number;
1731
+ title: string;
1732
+ status: TicketStatus;
1733
+ priority: TicketPriority;
1734
+ source: TicketSource;
1735
+ createdAt: string;
1736
+ }
1737
+ /**
1738
+ * Payload for ticket.updated event
1739
+ */
1740
+ interface TicketUpdatedData extends EventCustomerInfo {
1741
+ id: string;
1742
+ ticketNumber: number;
1743
+ title: string;
1744
+ status: TicketStatus;
1745
+ priority: TicketPriority;
1746
+ assigneeId: string | null;
1747
+ updatedAt: string;
1748
+ /** List of field names that were changed */
1749
+ changes: string[];
1750
+ }
1751
+ /**
1752
+ * Comment data in ticket.comment.created event
1753
+ */
1754
+ interface EventCommentData {
1755
+ id: string;
1756
+ body: string;
1757
+ authorId: string | null;
1758
+ authorType: AuthorType;
1759
+ createdAt: string;
1760
+ }
1761
+ /**
1762
+ * Payload for ticket.comment.created event
1763
+ */
1764
+ interface CommentCreatedData extends EventCustomerInfo {
1765
+ ticketId: string;
1766
+ /** Formatted ticket number (e.g., "ACME-123") */
1767
+ ticketNumber: string;
1768
+ comment: EventCommentData;
1769
+ }
1770
+ /**
1771
+ * Ticket created webhook event
1772
+ */
1773
+ type TicketCreatedEvent = WebhookEventEnvelope<'ticket.created', TicketCreatedData>;
1774
+ /**
1775
+ * Ticket updated webhook event
1776
+ */
1777
+ type TicketUpdatedEvent = WebhookEventEnvelope<'ticket.updated', TicketUpdatedData>;
1778
+ /**
1779
+ * Comment created webhook event
1780
+ */
1781
+ type CommentCreatedEvent = WebhookEventEnvelope<'ticket.comment.created', CommentCreatedData>;
1782
+ /**
1783
+ * Union of all webhook events
1784
+ */
1785
+ type WebhookEvent = TicketCreatedEvent | TicketUpdatedEvent | CommentCreatedEvent;
1786
+ /**
1787
+ * Map of event types to their data types
1788
+ */
1789
+ interface WebhookEventMap {
1790
+ 'ticket.created': TicketCreatedEvent;
1791
+ 'ticket.updated': TicketUpdatedEvent;
1792
+ 'ticket.comment.created': CommentCreatedEvent;
1793
+ }
1794
+ /**
1795
+ * Type guard to check if event is a ticket.created event
1796
+ */
1797
+ declare function isTicketCreatedEvent(event: WebhookEvent): event is TicketCreatedEvent;
1798
+ /**
1799
+ * Type guard to check if event is a ticket.updated event
1800
+ */
1801
+ declare function isTicketUpdatedEvent(event: WebhookEvent): event is TicketUpdatedEvent;
1802
+ /**
1803
+ * Type guard to check if event is a ticket.comment.created event
1804
+ */
1805
+ declare function isCommentCreatedEvent(event: WebhookEvent): event is CommentCreatedEvent;
1806
+ /**
1807
+ * Parse and validate a webhook payload
1808
+ *
1809
+ * @param payload - Raw JSON payload string or parsed object
1810
+ * @returns Typed webhook event
1811
+ * @throws Error if payload is invalid
1812
+ *
1813
+ * @example
1814
+ * ```typescript
1815
+ * import { parseWebhookEvent, isTicketCreatedEvent } from '@dispatchtickets/sdk';
1816
+ *
1817
+ * app.post('/webhooks', (req, res) => {
1818
+ * const event = parseWebhookEvent(req.body);
1819
+ *
1820
+ * if (isTicketCreatedEvent(event)) {
1821
+ * console.log('New ticket:', event.data.title);
1822
+ * }
1823
+ * });
1824
+ * ```
1825
+ */
1826
+ declare function parseWebhookEvent(payload: string | object): WebhookEvent;
1067
1827
 
1068
1828
  /**
1069
1829
  * Webhook signature verification utilities
@@ -1113,4 +1873,4 @@ declare const webhookUtils: {
1113
1873
  */
1114
1874
  declare function collectAll<T>(iterable: AsyncIterable<T>): Promise<T[]>;
1115
1875
 
1116
- export { type Attachment, type AttachmentStatus, type AttachmentWithUrl, AuthenticationError, type AuthorType, type Brand, type BulkAction, type BulkActionResult, type Category, type CategoryStats, type Comment, type Company, ConflictError, 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 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 TicketPriority, type TicketSource, type TicketStatus, TimeoutError, type UpdateBrandInput, type UpdateCategoryInput, type UpdateCommentInput, type UpdateCustomerInput, type UpdateFieldInput, type UpdateTagInput, type UpdateTicketInput, ValidationError, type Webhook, type WebhookDelivery, type WebhookEvent, collectAll, webhookUtils };
1876
+ 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, DispatchTickets, type DispatchTicketsConfig, DispatchTicketsError, type EntityType, type EventCommentData, type EventCustomerInfo, type FieldDefinition, type FieldDefinitions, type FieldType, type Hooks, type InitiateUploadInput, type InitiateUploadResponse, type Link, type ListCustomersFilters, type ListTicketsFilters, type MergeTagsInput, type MergeTicketsInput, NetworkError, NotFoundError, type PaginatedResponse, RateLimitError, type RateLimitInfo, type RequestContext, type ResponseContext, type RetryConfig, 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 };