@signingstudio/sdk 1.0.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.
@@ -0,0 +1,702 @@
1
+ import { request, FormData } from 'undici';
2
+ import { Buffer } from 'node:buffer';
3
+
4
+ interface HttpClientOptions {
5
+ apiKey: string;
6
+ baseUrl: string;
7
+ maxRetries: number;
8
+ requestTimeoutMs: number;
9
+ userAgent: string;
10
+ /** Test seam — overridable fetch-like transport for the test suite. */
11
+ fetchImpl?: FetchFn;
12
+ }
13
+ /**
14
+ * Minimal fetch surface the SDK consumes. Undici's `request` fits this
15
+ * shape; tests use a mocked implementation.
16
+ */
17
+ type FetchFn = typeof request;
18
+ /**
19
+ * Successful response with the envelope unwrapped and rate-limit metadata
20
+ * hoisted to typed fields for easy access.
21
+ */
22
+ interface ApiResponse<T> {
23
+ data: T;
24
+ meta: Record<string, unknown>;
25
+ requestId: string | null;
26
+ rateLimit: {
27
+ remainingMinute: number | null;
28
+ remainingDay: number | null;
29
+ limitMinute: number | null;
30
+ limitDay: number | null;
31
+ };
32
+ }
33
+ interface RequestOptions {
34
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
35
+ path: string;
36
+ query?: Record<string, string | number | boolean | undefined | null>;
37
+ json?: unknown;
38
+ /**
39
+ * Multipart body — passed straight to undici. Provide a FormData with
40
+ * one entry per field; PDF upload fields must be named `file`.
41
+ */
42
+ form?: FormData;
43
+ /**
44
+ * If true, the raw response body is returned as a Uint8Array instead of
45
+ * being decoded as JSON. Used for the `?redirect=1` PDF download endpoints.
46
+ */
47
+ binary?: boolean;
48
+ }
49
+ /**
50
+ * HTTP transport for the SDK. Wraps undici with:
51
+ * - Authorization header injection
52
+ * - JSON / multipart body handling
53
+ * - Envelope unwrap ({ success, data, meta })
54
+ * - Status → typed error mapping
55
+ * - Bounded retry on 429 (with Retry-After ≤ 60s) + 5xx + network errors
56
+ */
57
+ declare class HttpClient {
58
+ private readonly opts;
59
+ constructor(opts: HttpClientOptions);
60
+ /**
61
+ * JSON-returning request. Callers get the parsed data + envelope meta +
62
+ * rate-limit + request-id, or a typed error.
63
+ */
64
+ requestJson<T>(req: RequestOptions): Promise<ApiResponse<T>>;
65
+ /**
66
+ * Binary-returning request (PDF download). Skips JSON parsing; returns
67
+ * the raw body as a Uint8Array.
68
+ */
69
+ requestBinary(req: RequestOptions): Promise<Uint8Array>;
70
+ private execute;
71
+ private buildUrl;
72
+ private parseSuccess;
73
+ private readRateLimit;
74
+ private buildApiError;
75
+ private extractRetryAfter;
76
+ private headerString;
77
+ private parseIntHeader;
78
+ private readText;
79
+ private readBinary;
80
+ /**
81
+ * Exponential backoff (500 ms · 1 s · 2 s · 4 s) plus a small jitter to
82
+ * avoid herd effects when many callers retry at once.
83
+ */
84
+ private backoffMs;
85
+ }
86
+
87
+ /**
88
+ * Response types for the Signing Studio v1 REST API.
89
+ *
90
+ * These interfaces mirror the exact JSON shapes the server returns. The
91
+ * runtime SDK does no coercion beyond parsing the outer `{ success, data,
92
+ * meta }` envelope — callers that receive an object shaped by these types
93
+ * are looking at what the server actually sent.
94
+ *
95
+ * Field-level nullability follows the server: `string | null` means the
96
+ * server may emit `null`; a missing `| null` means the server always emits
97
+ * the field. Optionality (`?`) is only used when the SDK builds a request
98
+ * object; response types are exhaustive.
99
+ */
100
+ type DocumentStatus = "draft" | "sent" | "viewed" | "completed" | "declined" | "cancelled" | "expired";
101
+ type RecipientStatus = "pending" | "sent" | "viewed" | "signed" | "declined";
102
+ type ListView = "active" | "archived" | "deleted" | "all";
103
+ /**
104
+ * Fields on the list endpoint — a lighter subset than the detail view.
105
+ * See {@link DocumentDetail} for the full shape.
106
+ */
107
+ interface DocumentListItem {
108
+ id: string;
109
+ title: string;
110
+ status: DocumentStatus;
111
+ file_name: string;
112
+ page_count: number;
113
+ created_at: string;
114
+ sent_at: string | null;
115
+ completed_at: string | null;
116
+ expires_at: string | null;
117
+ is_archived: 0 | 1;
118
+ is_deleted: 0 | 1;
119
+ template_id: string | null;
120
+ }
121
+ interface DocumentListParams {
122
+ status?: DocumentStatus;
123
+ view?: ListView;
124
+ limit?: number;
125
+ offset?: number;
126
+ }
127
+ interface DocumentListMeta {
128
+ total: number;
129
+ limit: number;
130
+ offset: number;
131
+ view: string;
132
+ }
133
+ interface DocumentListResponse {
134
+ data: DocumentListItem[];
135
+ meta: DocumentListMeta;
136
+ }
137
+ type FieldType = "signature" | "initials" | "text" | "date" | "checkbox" | "radio" | "dropdown" | "email" | "phone" | "label";
138
+ interface DocumentField {
139
+ id: string;
140
+ document_id: string;
141
+ field_type: FieldType;
142
+ page: number;
143
+ x: number;
144
+ y: number;
145
+ width: number;
146
+ height: number;
147
+ label: string | null;
148
+ name: string | null;
149
+ placeholder: string | null;
150
+ required: 0 | 1;
151
+ readonly: 0 | 1;
152
+ signer_index: number;
153
+ options: string | null;
154
+ value: string | null;
155
+ }
156
+ interface Recipient {
157
+ id: string;
158
+ name: string;
159
+ email: string;
160
+ phone: string | null;
161
+ signing_order: number;
162
+ status: RecipientStatus;
163
+ viewed_at: string | null;
164
+ signed_at: string | null;
165
+ declined_at: string | null;
166
+ decline_reason: string | null;
167
+ role: string | null;
168
+ delivery: Array<"email" | "sms">;
169
+ /** Always null on the detail view. Use `remind()` to mint a fresh URL. */
170
+ signing_url: null;
171
+ signing_url_expires_at: string | null;
172
+ signing_url_used: boolean;
173
+ }
174
+ interface DocumentTemplateSummary {
175
+ id: string;
176
+ name: string;
177
+ signer_count: number;
178
+ signers: TemplateSigner[];
179
+ delivery_methods: Array<"email" | "sms" | "webhook">;
180
+ webhook_id: number | null;
181
+ default_subject: string | null;
182
+ default_message: string | null;
183
+ }
184
+ interface DocumentDetail {
185
+ id: string;
186
+ title: string;
187
+ status: DocumentStatus;
188
+ message: string | null;
189
+ email_subject: string | null;
190
+ instructions: string | null;
191
+ email_from_name: string | null;
192
+ email_from_address: string | null;
193
+ email_body_template: string | null;
194
+ sms_body_template: string | null;
195
+ file_name: string;
196
+ file_size: number;
197
+ page_count: number;
198
+ expires_at: string | null;
199
+ sent_at: string | null;
200
+ completed_at: string | null;
201
+ created_at: string;
202
+ updated_at: string;
203
+ is_archived: 0 | 1;
204
+ is_deleted: 0 | 1;
205
+ reminder_enabled: 0 | 1;
206
+ reminder_interval_days: number | null;
207
+ last_reminder_sent_at: string | null;
208
+ created_by_name: string | null;
209
+ fields: DocumentField[];
210
+ recipients: Recipient[];
211
+ fileUrl: string;
212
+ template: DocumentTemplateSummary | null;
213
+ }
214
+ interface DocumentProgress {
215
+ document_id: string;
216
+ status: DocumentStatus;
217
+ sent_at: string | null;
218
+ completed_at: string | null;
219
+ expires_at: string | null;
220
+ progress: {
221
+ total: number;
222
+ signed: number;
223
+ declined: number;
224
+ viewed: number;
225
+ pending: number;
226
+ percent_complete: number;
227
+ };
228
+ recipients: Array<Pick<Recipient, "id" | "name" | "email" | "signing_order" | "status" | "viewed_at" | "signed_at" | "declined_at" | "decline_reason">>;
229
+ }
230
+ /** One row from the audit log. */
231
+ interface ActivityEntry {
232
+ id: number;
233
+ account_id: number;
234
+ user_id: number | null;
235
+ entity_type: "document" | "signing";
236
+ entity_id: number;
237
+ action: string;
238
+ metadata: Record<string, unknown> | null;
239
+ ip_address: string | null;
240
+ user_agent: string | null;
241
+ created_at: string;
242
+ user_name: string | null;
243
+ user_email: string | null;
244
+ }
245
+ interface SendPrefillValue {
246
+ /** Either `field_id` (uuid) OR `field_name` must be set. */
247
+ field_id?: string;
248
+ field_name?: string;
249
+ value: string;
250
+ }
251
+ interface SendRecipient {
252
+ name: string;
253
+ email: string;
254
+ phone?: string;
255
+ /** 0-indexed. First to sign in a sequential chain is `0`. */
256
+ signing_order?: number;
257
+ role?: string;
258
+ email_subject?: string;
259
+ email_body_template?: string;
260
+ sms_body_template?: string;
261
+ email_from_name?: string;
262
+ }
263
+ interface SendDocumentPayload {
264
+ template_id: string;
265
+ title: string;
266
+ message?: string;
267
+ subject?: string;
268
+ expires_at?: string;
269
+ recipients: SendRecipient[];
270
+ prefill_values?: SendPrefillValue[];
271
+ instructions?: string;
272
+ email_from_name?: string;
273
+ email_from_address?: string;
274
+ email_body_template?: string;
275
+ sms_body_template?: string;
276
+ }
277
+ interface RemindResponse {
278
+ success: true;
279
+ signing_url: string;
280
+ }
281
+ interface SuccessAck {
282
+ success: true;
283
+ }
284
+ interface DeleteResponse extends SuccessAck {
285
+ mode: "soft" | "hard";
286
+ }
287
+ interface PresignedUrl {
288
+ url: string;
289
+ }
290
+ interface TemplateSigner {
291
+ role?: string;
292
+ default_name?: string;
293
+ default_email?: string;
294
+ delivery?: Array<"email" | "sms">;
295
+ email_subject?: string;
296
+ email_body_template?: string;
297
+ sms_body_template?: string;
298
+ email_from_name?: string;
299
+ }
300
+ interface TemplateListItem {
301
+ id: string;
302
+ name: string;
303
+ description: string | null;
304
+ file_name: string;
305
+ page_count: number;
306
+ created_at: string;
307
+ signer_count: number;
308
+ delivery_methods: Array<"email" | "sms" | "webhook">;
309
+ is_archived: 0 | 1;
310
+ created_by_name: string | null;
311
+ }
312
+ interface TemplateField {
313
+ id: string;
314
+ field_type: FieldType;
315
+ page: number;
316
+ x: number;
317
+ y: number;
318
+ width: number;
319
+ height: number;
320
+ label: string | null;
321
+ name: string | null;
322
+ placeholder: string | null;
323
+ required: 0 | 1;
324
+ readonly: 0 | 1;
325
+ signer_index: number;
326
+ options: string | null;
327
+ }
328
+ interface TemplateDetail {
329
+ id: string;
330
+ name: string;
331
+ description: string | null;
332
+ file_key: string;
333
+ file_name: string;
334
+ file_size: number;
335
+ page_count: number;
336
+ signer_count: number;
337
+ signers: TemplateSigner[];
338
+ delivery_methods: Array<"email" | "sms" | "webhook">;
339
+ webhook_id: number | null;
340
+ default_subject: string | null;
341
+ default_message: string | null;
342
+ email_from_name: string | null;
343
+ email_from_address: string | null;
344
+ email_body_template: string | null;
345
+ sms_body_template: string | null;
346
+ default_instructions: string | null;
347
+ is_archived: 0 | 1;
348
+ is_deleted: 0 | 1;
349
+ created_at: string;
350
+ updated_at: string;
351
+ created_by_name: string | null;
352
+ fields: TemplateField[];
353
+ fileUrl: string;
354
+ }
355
+ /** Metadata payload accepted by create + update. */
356
+ interface TemplateMetadata {
357
+ name?: string;
358
+ description?: string;
359
+ signer_count?: number;
360
+ signers?: TemplateSigner[];
361
+ delivery_methods?: Array<"email" | "sms" | "webhook">;
362
+ webhook_id?: number | null;
363
+ default_subject?: string;
364
+ default_message?: string;
365
+ email_from_name?: string;
366
+ email_from_address?: string;
367
+ email_body_template?: string;
368
+ sms_body_template?: string;
369
+ default_instructions?: string;
370
+ }
371
+ /**
372
+ * Field-editing shape. Wider than {@link TemplateField} — the input allows
373
+ * `options` as a real array (server-side it's persisted as a JSON string).
374
+ */
375
+ interface TemplateFieldInput {
376
+ field_type: FieldType;
377
+ page: number;
378
+ x: number;
379
+ y: number;
380
+ width: number;
381
+ height: number;
382
+ label?: string;
383
+ name?: string;
384
+ placeholder?: string;
385
+ required?: boolean;
386
+ readonly?: boolean;
387
+ signer_index?: number;
388
+ options?: string[];
389
+ }
390
+ interface TemplateHistoryEntry {
391
+ id: number;
392
+ file_name: string;
393
+ file_size: number;
394
+ page_count: number;
395
+ replaced_at: string;
396
+ replaced_by_name: string | null;
397
+ }
398
+ type WebhookEvent = "document.sent" | "document.viewed" | "document.signed" | "document.declined" | "document.completed";
399
+ interface WebhookPayload {
400
+ event: WebhookEvent;
401
+ data: {
402
+ document: {
403
+ id: string;
404
+ title: string;
405
+ status: DocumentStatus;
406
+ template_id: string | null;
407
+ sent_at: string | null;
408
+ completed_at: string | null;
409
+ };
410
+ download_url: string | null;
411
+ download_expires_in_seconds: number;
412
+ recipients: Array<Pick<Recipient, "id" | "name" | "email" | "signing_order" | "status" | "viewed_at" | "signed_at" | "declined_at" | "decline_reason">>;
413
+ [k: string]: unknown;
414
+ };
415
+ timestamp: string;
416
+ }
417
+
418
+ /**
419
+ * Documents resource — send documents from templates, poll status,
420
+ * download completed PDFs, remind signers.
421
+ *
422
+ * Every method returns the parsed data payload. If you need the full
423
+ * envelope (rate-limit headers, request id) drop down to
424
+ * `client.http.requestJson`.
425
+ */
426
+ declare class Documents {
427
+ private readonly http;
428
+ constructor(http: HttpClient);
429
+ /**
430
+ * List documents. All parameters are optional.
431
+ *
432
+ * The `view` toggle controls which lifecycle bucket to read from:
433
+ * - `active` (default) — non-archived, non-deleted
434
+ * - `archived` — archived only
435
+ * - `deleted` — soft-deleted only
436
+ * - `all` — active + archived, still excludes trash
437
+ */
438
+ list(params?: DocumentListParams): Promise<DocumentListResponse>;
439
+ /**
440
+ * Send a new document built from a template.
441
+ *
442
+ * Server-side rules to be aware of (surfaced as {@link import("../errors/index.js").ValidationError}):
443
+ * - `template_id` is required.
444
+ * - At least one recipient must be supplied.
445
+ * - `prefill_values[]` needs either `field_id` or `field_name` + `value`.
446
+ * - Signature / initials fields can't be prefilled.
447
+ * - Any template field that is both required AND readonly must be prefilled.
448
+ *
449
+ * Successful sends count against the account's monthly document quota.
450
+ */
451
+ send(payload: SendDocumentPayload): Promise<DocumentDetail>;
452
+ get(id: string): Promise<DocumentDetail>;
453
+ /**
454
+ * Delete a document. `hard: true` also purges the file from storage.
455
+ * Only valid on cancelled / completed / declined / expired documents;
456
+ * cancel first if the document is still in flight.
457
+ */
458
+ delete(id: string, options?: {
459
+ hard?: boolean;
460
+ }): Promise<DeleteResponse>;
461
+ restore(id: string): Promise<SuccessAck>;
462
+ archive(id: string): Promise<SuccessAck>;
463
+ unarchive(id: string): Promise<SuccessAck>;
464
+ cancel(id: string): Promise<SuccessAck>;
465
+ /**
466
+ * Read the current signing progress — cheaper than {@link get} when
467
+ * polling. Returns overall counts plus per-recipient state, no field
468
+ * or template data.
469
+ */
470
+ progress(id: string): Promise<DocumentProgress>;
471
+ /**
472
+ * Get a presigned download URL for the document PDF. For completed
473
+ * documents this is the flattened final PDF plus the audit-trail page;
474
+ * while in flight it's the in-progress snapshot (or source PDF if no
475
+ * signatures yet).
476
+ */
477
+ downloadUrl(id: string): Promise<PresignedUrl>;
478
+ /**
479
+ * Fetch the document PDF as raw bytes in one call (follows the presigned
480
+ * URL redirect server-side). Handy for saving straight to disk.
481
+ */
482
+ downloadPdf(id: string): Promise<Uint8Array>;
483
+ activity(id: string): Promise<ActivityEntry[]>;
484
+ /**
485
+ * Re-send the signing invite (or first invite, if it hasn't gone out
486
+ * yet) to a specific recipient. Response includes a freshly-minted
487
+ * signing URL — this is the only endpoint where a URL is returned in
488
+ * plaintext, so capture it if you need to relay it out-of-band.
489
+ */
490
+ remind(documentId: string, recipientId: string): Promise<RemindResponse>;
491
+ }
492
+
493
+ /** Anything the SDK accepts as a PDF payload. */
494
+ type PdfInput = string | Uint8Array | Buffer | Blob | {
495
+ path: string;
496
+ } | {
497
+ data: Uint8Array | Buffer;
498
+ filename?: string;
499
+ contentType?: string;
500
+ };
501
+ /**
502
+ * Templates resource — create / update / version PDF templates.
503
+ *
504
+ * PDF upload constraints (server-enforced, 400 on violation):
505
+ * - Max 50 MB per file
506
+ * - `application/pdf` only
507
+ * - Multipart form field must be `file` — the SDK sets this for you
508
+ */
509
+ declare class Templates {
510
+ private readonly http;
511
+ constructor(http: HttpClient);
512
+ list(): Promise<TemplateListItem[]>;
513
+ /**
514
+ * Create a new template from a PDF plus metadata.
515
+ *
516
+ * The `pdf` argument can be a filesystem path, a Uint8Array/Buffer of
517
+ * raw bytes, a Blob, or an object with an explicit filename. See
518
+ * {@link PdfInput}.
519
+ */
520
+ create(pdf: PdfInput, metadata: TemplateMetadata, options?: {
521
+ filename?: string;
522
+ }): Promise<TemplateDetail>;
523
+ get(id: string): Promise<TemplateDetail>;
524
+ update(id: string, metadata: TemplateMetadata): Promise<TemplateDetail>;
525
+ /**
526
+ * Soft-delete. There is no hard-delete on this route; contact support
527
+ * if you need permanent removal.
528
+ */
529
+ delete(id: string): Promise<SuccessAck>;
530
+ pdfUrl(id: string): Promise<PresignedUrl>;
531
+ downloadPdf(id: string): Promise<Uint8Array>;
532
+ /**
533
+ * Replace the template's PDF. Prior versions are kept in history —
534
+ * download via {@link historyPdfUrl}.
535
+ */
536
+ replacePdf(id: string, pdf: PdfInput, options?: {
537
+ filename?: string;
538
+ }): Promise<TemplateDetail>;
539
+ /**
540
+ * Replace ALL fields on the template. The server deletes existing rows
541
+ * and inserts the supplied set — the SDK does not support partial
542
+ * updates because the server doesn't.
543
+ */
544
+ setFields(id: string, fields: TemplateFieldInput[]): Promise<TemplateField[]>;
545
+ duplicate(id: string): Promise<TemplateDetail>;
546
+ archive(id: string): Promise<SuccessAck>;
547
+ history(id: string): Promise<TemplateHistoryEntry[]>;
548
+ historyPdfUrl(templateId: string, historyId: number): Promise<PresignedUrl>;
549
+ downloadHistoryPdf(templateId: string, historyId: number): Promise<Uint8Array>;
550
+ /**
551
+ * Build the multipart form: PDF blob under field name `file` (required
552
+ * by the server) + JSON-encoded scalar/array metadata fields.
553
+ *
554
+ * Nested arrays/objects (like `signers`, `delivery_methods`) are
555
+ * serialized with JSON.stringify so they survive the form-data trip —
556
+ * the server route unwraps them.
557
+ */
558
+ private buildPdfForm;
559
+ private resolvePdf;
560
+ }
561
+
562
+ interface ClientOptions {
563
+ /** Your API key from Signing Studio → Settings → API Keys. Required. */
564
+ apiKey: string;
565
+ /** Override for staging / self-hosted deployments. Default: production. */
566
+ baseUrl?: string;
567
+ /** How many times to retry transient failures (429 short Retry-After, 5xx, network). Default 3. */
568
+ maxRetries?: number;
569
+ /** Full request-timeout in milliseconds — generous by default so a 50 MB PDF upload has time to finish. */
570
+ requestTimeoutMs?: number;
571
+ /** Custom User-Agent — replace or extend with your app's identifier. */
572
+ userAgent?: string;
573
+ /**
574
+ * Test-only seam. Replace the transport with a mocked implementation
575
+ * in the test suite. Undici's `request` fits by default.
576
+ */
577
+ fetchImpl?: FetchFn;
578
+ }
579
+ /**
580
+ * Top-level entry point for the Signing Studio Node.js SDK.
581
+ *
582
+ * import { SigningStudio } from "@signingstudio/sdk";
583
+ *
584
+ * const client = new SigningStudio({ apiKey: process.env.SIGNING_STUDIO_API_KEY! });
585
+ * const doc = await client.documents.send({
586
+ * template_id: "…",
587
+ * title: "MSA — Acme",
588
+ * recipients: [{ name: "Alex", email: "alex@acme.com" }],
589
+ * });
590
+ *
591
+ * The Client is safe to reuse across concurrent requests. Undici keeps an
592
+ * internal connection pool per host, which is the recommended shape for
593
+ * long-running processes (workers, cron, servers).
594
+ */
595
+ declare class SigningStudio {
596
+ readonly documents: Documents;
597
+ readonly templates: Templates;
598
+ /** Escape hatch — the raw HTTP layer for callers that need to consume
599
+ * headers (rate-limit remaining, request id) alongside the parsed data. */
600
+ readonly http: HttpClient;
601
+ constructor(options: ClientOptions);
602
+ }
603
+
604
+ /**
605
+ * Verify an incoming Signing Studio webhook delivery.
606
+ *
607
+ * The server signs the raw JSON body with HMAC-SHA256 keyed on the
608
+ * webhook's secret and sends it as `X-DDS-Signature: sha256=<hex>`.
609
+ *
610
+ * Usage (Express-style example):
611
+ *
612
+ * import express from "express";
613
+ * import { verifyWebhook } from "@signingstudio/sdk";
614
+ *
615
+ * const app = express();
616
+ * app.post("/webhooks/signing-studio",
617
+ * express.raw({ type: "application/json" }), // do NOT pre-parse
618
+ * (req, res) => {
619
+ * const ok = verifyWebhook(req.body, req.get("X-DDS-Signature"), SECRET);
620
+ * if (!ok) return res.status(401).end();
621
+ * const payload = JSON.parse(req.body.toString("utf8"));
622
+ * // ...act on payload.event
623
+ * res.status(200).end();
624
+ * }
625
+ * );
626
+ *
627
+ * You MUST pass the RAW body — a parsed & re-serialized body will fail
628
+ * verification, even if it looks identical.
629
+ */
630
+ declare function verifyWebhook(rawBody: string | Uint8Array | Buffer, header: string | null | undefined, secret: string): boolean;
631
+
632
+ /**
633
+ * Every exception the SDK throws extends {@link SigningStudioError}, so
634
+ * integrators can catch a single type at the outer boundary of their code
635
+ * and inspect the subclass for specifics.
636
+ *
637
+ * try {
638
+ * await client.documents.send(payload);
639
+ * } catch (err) {
640
+ * if (err instanceof ValidationError) { /* field-map available *​/ }
641
+ * if (err instanceof RateLimitError) { /* retryAfter, window *​/ }
642
+ * if (err instanceof SigningStudioError) { /* fallback *​/ }
643
+ * throw err;
644
+ * }
645
+ */
646
+ declare class SigningStudioError extends Error {
647
+ constructor(message: string, options?: {
648
+ cause?: unknown;
649
+ });
650
+ }
651
+ interface ApiErrorInit {
652
+ statusCode: number;
653
+ requestId?: string | null;
654
+ body?: Record<string, unknown> | null;
655
+ }
656
+ /**
657
+ * Non-2xx HTTP response from the API. Carries the parsed error envelope,
658
+ * the request id (for support tickets), and the status code (for
659
+ * programmatic branching).
660
+ */
661
+ declare class ApiError extends SigningStudioError {
662
+ readonly statusCode: number;
663
+ readonly requestId: string | null;
664
+ readonly body: Record<string, unknown> | null;
665
+ constructor(message: string, init: ApiErrorInit);
666
+ }
667
+ /** HTTP 401 — API key missing, unknown, or the account behind it is inactive. */
668
+ declare class AuthenticationError extends ApiError {
669
+ constructor(message: string, init: ApiErrorInit);
670
+ }
671
+ /** HTTP 404 — resource does not exist under the caller's account. */
672
+ declare class NotFoundError extends ApiError {
673
+ constructor(message: string, init: ApiErrorInit);
674
+ }
675
+ interface ValidationErrorInit extends ApiErrorInit {
676
+ errors: Record<string, string[]>;
677
+ }
678
+ /**
679
+ * HTTP 422 — Zod-level or business-rule validation failure. `errors` is
680
+ * the field-name → messages map from the server; empty when the failure
681
+ * was purely business-rule (e.g. prefill of a signature field).
682
+ */
683
+ declare class ValidationError extends ApiError {
684
+ readonly errors: Record<string, string[]>;
685
+ constructor(message: string, init: ValidationErrorInit);
686
+ }
687
+ interface RateLimitErrorInit extends ApiErrorInit {
688
+ retryAfter: number | null;
689
+ window: string | null;
690
+ }
691
+ /**
692
+ * HTTP 429 — rate limit or monthly quota. `retryAfter` is present for
693
+ * sliding-window rate limits (seconds until safe to retry) and null for
694
+ * monthly quota exhaustion.
695
+ */
696
+ declare class RateLimitError extends ApiError {
697
+ readonly retryAfter: number | null;
698
+ readonly window: string | null;
699
+ constructor(message: string, init: RateLimitErrorInit);
700
+ }
701
+
702
+ export { type ActivityEntry, ApiError, AuthenticationError, type ClientOptions, type DeleteResponse, type DocumentDetail, type DocumentField, type DocumentListItem, type DocumentListMeta, type DocumentListParams, type DocumentListResponse, type DocumentProgress, type DocumentStatus, type DocumentTemplateSummary, Documents, type FieldType, type ListView, NotFoundError, type PdfInput, type PresignedUrl, RateLimitError, type Recipient, type RecipientStatus, type RemindResponse, type SendDocumentPayload, type SendPrefillValue, type SendRecipient, SigningStudio, SigningStudioError, type SuccessAck, type TemplateDetail, type TemplateField, type TemplateFieldInput, type TemplateHistoryEntry, type TemplateListItem, type TemplateMetadata, type TemplateSigner, Templates, ValidationError, type WebhookEvent, type WebhookPayload, verifyWebhook };