@assinafy/sdk 1.5.0 → 2.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.
package/dist/index.js CHANGED
@@ -38,6 +38,7 @@ __export(index_exports, {
38
38
  DEFAULT_WEBHOOK_EVENTS: () => DEFAULT_WEBHOOK_EVENTS,
39
39
  DocumentResource: () => DocumentResource,
40
40
  FieldsResource: () => FieldsResource,
41
+ MAX_UPLOAD_BYTES: () => MAX_UPLOAD_BYTES,
41
42
  NetworkError: () => NetworkError,
42
43
  SignerDocumentsResource: () => SignerDocumentsResource,
43
44
  SignerResource: () => SignerResource,
@@ -56,16 +57,16 @@ var import_axios2 = __toESM(require("axios"));
56
57
 
57
58
  // src/errors.ts
58
59
  var AssinafyError = class extends Error {
60
+ context;
59
61
  constructor(message, context = {}, options) {
60
- super(message);
62
+ super(message, options);
61
63
  this.name = "AssinafyError";
62
64
  this.context = context;
63
- if (options?.cause !== void 0) {
64
- this.cause = options.cause;
65
- }
66
65
  }
67
66
  };
68
67
  var ApiError = class _ApiError extends AssinafyError {
68
+ statusCode;
69
+ responseData;
69
70
  constructor(message, statusCode, responseData = null, options) {
70
71
  super(message, { statusCode, responseData }, options);
71
72
  this.name = "ApiError";
@@ -81,6 +82,7 @@ var ApiError = class _ApiError extends AssinafyError {
81
82
  }
82
83
  };
83
84
  var ValidationError = class extends AssinafyError {
85
+ errors;
84
86
  constructor(message = "Validation failed", errors = {}) {
85
87
  super(message, { errors });
86
88
  this.name = "ValidationError";
@@ -106,6 +108,23 @@ function handleAssinafyResponse(response) {
106
108
  }
107
109
  return response;
108
110
  }
111
+ function decodeBinaryErrorBody(data) {
112
+ let text;
113
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(data)) {
114
+ text = data.toString("utf8");
115
+ } else if (data instanceof ArrayBuffer) {
116
+ text = Buffer.from(data).toString("utf8");
117
+ } else if (ArrayBuffer.isView(data)) {
118
+ text = Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString("utf8");
119
+ } else {
120
+ return data;
121
+ }
122
+ try {
123
+ return JSON.parse(text);
124
+ } catch {
125
+ return text.length > 0 ? { message: text } : null;
126
+ }
127
+ }
109
128
  function toSdkError(error, fallbackMessage) {
110
129
  if (error instanceof AssinafyError) {
111
130
  return error;
@@ -113,14 +132,15 @@ function toSdkError(error, fallbackMessage) {
113
132
  if (import_axios.default.isAxiosError(error)) {
114
133
  const status = error.response?.status;
115
134
  if (status) {
116
- return ApiError.fromResponse(status, error.response?.data ?? null);
135
+ const body = decodeBinaryErrorBody(error.response?.data ?? null);
136
+ return ApiError.fromResponse(status, body ?? null);
117
137
  }
118
138
  return new NetworkError(`${fallbackMessage}: ${error.message}`, { cause: error });
119
139
  }
120
140
  if (error instanceof Error) {
121
141
  return new AssinafyError(`${fallbackMessage}: ${error.message}`, {}, { cause: error });
122
142
  }
123
- return new AssinafyError(fallbackMessage, { cause: error });
143
+ return new AssinafyError(fallbackMessage, {}, { cause: error });
124
144
  }
125
145
  function createNoopLogger() {
126
146
  return {
@@ -139,6 +159,14 @@ function cleanParams(params) {
139
159
  }
140
160
  return out;
141
161
  }
162
+ function cleanListParams(params) {
163
+ const out = cleanParams(params);
164
+ if (out["per_page"] !== void 0) {
165
+ out["per-page"] ??= out["per_page"];
166
+ delete out["per_page"];
167
+ }
168
+ return out;
169
+ }
142
170
 
143
171
  // src/support/retry.ts
144
172
  function header(headers, name) {
@@ -175,7 +203,57 @@ function nextRetryDelayMs(headers, attempt, maxDelayMs = 3e4) {
175
203
  return Math.min(backoffMs(attempt), maxDelayMs);
176
204
  }
177
205
 
206
+ // src/resources/upload.ts
207
+ var import_node_fs = require("fs");
208
+ var import_node_path = __toESM(require("path"));
209
+ var MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
210
+ async function loadSource(source) {
211
+ if ("buffer" in source) {
212
+ if (!source.fileName) {
213
+ throw new ValidationError("fileName is required when uploading a Buffer");
214
+ }
215
+ return { buffer: source.buffer, fileName: source.fileName };
216
+ }
217
+ if (!source.filePath) {
218
+ throw new ValidationError("filePath is required");
219
+ }
220
+ const buffer = await import_node_fs.promises.readFile(source.filePath);
221
+ return { buffer, fileName: source.fileName ?? import_node_path.default.basename(source.filePath) };
222
+ }
223
+ function validateUpload(buffer, fileName) {
224
+ if (!buffer || buffer.byteLength === 0) {
225
+ throw new ValidationError("File buffer is empty", { fileName });
226
+ }
227
+ if (!fileName.toLowerCase().endsWith(".pdf")) {
228
+ throw new ValidationError("Only PDF files are supported", { fileName });
229
+ }
230
+ if (buffer.byteLength > MAX_UPLOAD_BYTES) {
231
+ throw new ValidationError("File size exceeds maximum allowed (25MB)", {
232
+ fileSize: buffer.byteLength,
233
+ maxSize: MAX_UPLOAD_BYTES
234
+ });
235
+ }
236
+ }
237
+ function toUploadFileName(name) {
238
+ return name.toLowerCase().endsWith(".pdf") ? name : `${name}.pdf`;
239
+ }
240
+ function buildUploadForm(buffer, fileName, options = {}) {
241
+ const form = new FormData();
242
+ const view = new Uint8Array(
243
+ buffer.buffer,
244
+ buffer.byteOffset,
245
+ buffer.byteLength
246
+ );
247
+ const partName = options.name === void 0 ? fileName : toUploadFileName(options.name);
248
+ form.append("file", new Blob([view], { type: "application/pdf" }), partName);
249
+ if (options.metadata) {
250
+ form.append("metadata", JSON.stringify(options.metadata));
251
+ }
252
+ return form;
253
+ }
254
+
178
255
  // src/resources/base.ts
256
+ var MULTIPART_CONTENT_TYPE = "multipart/form-data";
179
257
  var BaseResource = class {
180
258
  constructor(http, defaultAccountId, logger = createNoopLogger()) {
181
259
  this.http = http;
@@ -237,6 +315,35 @@ var BaseResource = class {
237
315
  throw toSdkError(err, label);
238
316
  }
239
317
  }
318
+ /**
319
+ * Upload a PDF as `multipart/form-data` and assert the API echoed an id.
320
+ *
321
+ * Shared by `documents.upload` and `templates.create`, which are the same
322
+ * sequence over different paths: load → validate → build form → POST →
323
+ * assert an id came back. Callers keep their own success logging.
324
+ *
325
+ * @param path - Account-scoped endpoint to POST to.
326
+ * @param source - The PDF, as a file path or in-memory buffer.
327
+ * @param formOptions - `name` (display name) and optional `metadata`.
328
+ * @param labels - `errorLabel` for the request failure, `missingId` for a
329
+ * `2xx` that returned no id.
330
+ */
331
+ async uploadPdf(path2, source, formOptions, labels) {
332
+ const { buffer, fileName } = await loadSource(source);
333
+ validateUpload(buffer, fileName);
334
+ this.logger.info("Uploading PDF", { path: path2, fileName, size: buffer.byteLength });
335
+ const form = buildUploadForm(buffer, fileName, formOptions);
336
+ const result = await this.call(
337
+ labels.errorLabel,
338
+ () => this.http.post(path2, form, { headers: { "Content-Type": MULTIPART_CONTENT_TYPE } })
339
+ );
340
+ if (!result?.id) {
341
+ throw new ValidationError(labels.missingId, {
342
+ response: result
343
+ });
344
+ }
345
+ return result;
346
+ }
240
347
  /** Execute a paginated list call and attach meta from `X-Pagination-*` headers. */
241
348
  async callList(label, request) {
242
349
  try {
@@ -278,48 +385,6 @@ function toInt(value) {
278
385
  return Number.isFinite(n) ? n : void 0;
279
386
  }
280
387
 
281
- // src/resources/upload.ts
282
- var import_node_fs = require("fs");
283
- var import_node_path = __toESM(require("path"));
284
- var MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
285
- async function loadSource(source) {
286
- if ("buffer" in source) {
287
- if (!source.fileName) {
288
- throw new ValidationError("fileName is required when uploading a Buffer");
289
- }
290
- return { buffer: source.buffer, fileName: source.fileName };
291
- }
292
- if (!source.filePath) {
293
- throw new ValidationError("filePath is required");
294
- }
295
- const buffer = await import_node_fs.promises.readFile(source.filePath);
296
- return { buffer, fileName: source.fileName ?? import_node_path.default.basename(source.filePath) };
297
- }
298
- function validateUpload(buffer, fileName) {
299
- if (!buffer || buffer.byteLength === 0) {
300
- throw new ValidationError("File buffer is empty", { fileName });
301
- }
302
- if (!fileName.toLowerCase().endsWith(".pdf")) {
303
- throw new ValidationError("Only PDF files are supported", { fileName });
304
- }
305
- if (buffer.byteLength > MAX_UPLOAD_BYTES) {
306
- throw new ValidationError("File size exceeds maximum allowed (25MB)", {
307
- fileSize: buffer.byteLength,
308
- maxSize: MAX_UPLOAD_BYTES
309
- });
310
- }
311
- }
312
- function buildUploadForm(buffer, fileName, options = {}) {
313
- const form = new FormData();
314
- const view = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
315
- form.append("file", new Blob([view], { type: "application/pdf" }), fileName);
316
- form.append("name", options.name ?? fileName);
317
- if (options.metadata) {
318
- form.append("metadata", JSON.stringify(options.metadata));
319
- }
320
- return form;
321
- }
322
-
323
388
  // src/resources/documents.ts
324
389
  var READY_STATUSES = /* @__PURE__ */ new Set([
325
390
  "metadata_ready",
@@ -334,45 +399,153 @@ var FAILED_STATUSES = /* @__PURE__ */ new Set([
334
399
  ]);
335
400
  var DocumentResource = class extends BaseResource {
336
401
  /**
337
- * Upload a PDF to the workspace.
402
+ * Upload a PDF to the workspace (`POST /accounts/{accountId}/documents`).
403
+ *
404
+ * The document is created in `metadata_processing` status and becomes
405
+ * usable once it reaches `metadata_ready`; use
406
+ * {@link DocumentResource.waitUntilReady} to await that transition. Note
407
+ * that {@link DocumentResource.rename} and {@link DocumentResource.delete}
408
+ * return `400` while the document is still processing.
409
+ *
410
+ * @param source - The PDF to upload, as a file path or an in-memory buffer.
411
+ * @param options - Display name, metadata, and account override.
412
+ * @returns The created document. Response shape:
413
+ * ```jsonc
414
+ * {
415
+ * "resource": "document",
416
+ * "id": "c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
417
+ * "name": "Service agreement.pdf",
418
+ * "status": "metadata_processing",
419
+ * "created_at": "2026-07-15T16:15:33Z",
420
+ * "updated_at": "2026-07-15T16:15:33Z"
421
+ * }
422
+ * ```
423
+ * @throws {ValidationError} If the file is empty, not a `.pdf`, exceeds
424
+ * 25 MB, or the API returns no document ID.
425
+ * @throws {ApiError} If the API rejects the upload.
338
426
  *
339
427
  * @example
340
428
  * ```ts
341
429
  * await client.documents.upload({ filePath: './contract.pdf' });
342
- * await client.documents.upload({ buffer, fileName: 'contract.pdf' }, { metadata });
430
+ * await client.documents.upload(
431
+ * { buffer, fileName: 'contract.pdf' },
432
+ * { name: 'Service agreement', metadata: { orderId: 'A-1' } },
433
+ * );
434
+ * // → name is stored as 'Service agreement.pdf'
343
435
  * ```
344
436
  */
345
437
  async upload(source, options = {}) {
346
- const { buffer, fileName } = await loadSource(source);
347
- validateUpload(buffer, fileName);
348
438
  const accountId = this.accountId(options.accountId);
349
439
  const formOptions = {};
440
+ if (options.name !== void 0) formOptions.name = options.name;
350
441
  if (options.metadata !== void 0) formOptions.metadata = options.metadata;
351
- const form = buildUploadForm(buffer, fileName, formOptions);
352
- this.logger.info("Uploading document", { fileName, size: buffer.byteLength });
353
- const document = await this.call(
354
- "Document upload failed",
355
- () => this.http.post(`/accounts/${accountId}/documents`, form, {
356
- headers: { "Content-Type": "multipart/form-data" }
357
- })
442
+ const document = await this.uploadPdf(
443
+ `/accounts/${accountId}/documents`,
444
+ source,
445
+ formOptions,
446
+ {
447
+ errorLabel: "Document upload failed",
448
+ missingId: "Upload succeeded but no document ID was returned"
449
+ }
358
450
  );
359
- if (!document?.id) {
360
- throw new ValidationError("Upload succeeded but no document ID was returned", {
361
- response: document
362
- });
363
- }
364
451
  this.logger.info("Document uploaded", { documentId: document.id });
365
452
  return document;
366
453
  }
367
454
  /**
368
455
  * List workspace documents. Pagination info (if any) is attached in `meta`.
369
- * Supports `status`, `method`, `tags`, `search`, `sort`, `page`, `per_page`.
456
+ * Supports `status`, `method`, `tags`, `search`, `sort`, `page`, `per-page`.
370
457
  */
371
458
  async list(params = {}, accountId) {
372
459
  const id = this.accountId(accountId);
373
460
  return this.callList(
374
461
  "Failed to list documents",
375
- () => this.http.get(`/accounts/${id}/documents`, { params: cleanParams(params) })
462
+ () => this.http.get(`/accounts/${id}/documents`, { params: cleanListParams(params) })
463
+ );
464
+ }
465
+ /**
466
+ * Search workspace documents
467
+ * (`GET /accounts/{accountId}/documents/search`).
468
+ *
469
+ * A lighter-weight alternative to {@link DocumentResource.list}: it returns
470
+ * a compact representation with no expanded `assignment` or `pages`, so
471
+ * prefer it for name lookups and pickers.
472
+ *
473
+ * @param params - `search`, `status`, `page`, `per-page`.
474
+ * @param accountId - Override the client's default account ID.
475
+ * @returns Matching documents, with pagination in `meta`. Each item:
476
+ * ```jsonc
477
+ * {
478
+ * "id": "c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
479
+ * "account_id": "d4e5f6a7b8c9d0e1f2a3b4c5d6e7",
480
+ * "template_id": null,
481
+ * "name": "Service agreement.pdf",
482
+ * "status": "pending_signature",
483
+ * "artifacts": { "original": "https://…" },
484
+ * "is_closed": false,
485
+ * "signing_url": "https://…",
486
+ * "decline_reason": null,
487
+ * "declined_by": null,
488
+ * "tags": [],
489
+ * "created_at": "2026-07-15T16:15:33Z",
490
+ * "updated_at": "2026-07-15T16:15:40Z"
491
+ * }
492
+ * ```
493
+ * @throws {ValidationError} If no account ID is available.
494
+ * @throws {ApiError} If the API rejects the request.
495
+ *
496
+ * @example
497
+ * ```ts
498
+ * const { data, meta } = await client.documents.search({
499
+ * search: 'agreement',
500
+ * status: 'pending_signature',
501
+ * 'per-page': 20,
502
+ * });
503
+ * ```
504
+ */
505
+ async search(params = {}, accountId) {
506
+ const id = this.accountId(accountId);
507
+ return this.callList(
508
+ "Failed to search documents",
509
+ () => this.http.get(`/accounts/${id}/documents/search`, { params: cleanListParams(params) })
510
+ );
511
+ }
512
+ /**
513
+ * Rename a document (`PATCH /documents/{documentId}`).
514
+ *
515
+ * Only valid while the document is still renameable: the API returns `400`
516
+ * ("Document cannot be renamed after the signature process has started")
517
+ * both once signing has begun **and** while the document is still in
518
+ * `metadata_processing` immediately after upload. Await
519
+ * {@link DocumentResource.waitUntilReady} before renaming a fresh upload.
520
+ *
521
+ * To set a name at upload time instead, pass `name` to
522
+ * {@link DocumentResource.upload} — that avoids the extra round-trip and
523
+ * the processing race entirely.
524
+ *
525
+ * @param documentId - The document to rename.
526
+ * @param name - The new display name (max 255 chars), e.g.
527
+ * `'Service agreement.pdf'`.
528
+ * @returns The updated document — **without** `pages` or `assignment`,
529
+ * which this endpoint does not return (unlike
530
+ * {@link DocumentResource.details}). Call `details()` if you need them.
531
+ * @throws {ValidationError} If `documentId` or `name` is missing.
532
+ * @throws {ApiError} `400` if the document is processing or already in
533
+ * signing; `404` if it does not exist.
534
+ *
535
+ * @example
536
+ * ```ts
537
+ * const doc = await client.documents.upload({ filePath: './c.pdf' });
538
+ * await client.documents.waitUntilReady(doc.id); // else 400
539
+ * await client.documents.rename(doc.id, 'Service agreement.pdf');
540
+ * ```
541
+ */
542
+ async rename(documentId, name) {
543
+ const id = this.requireId(documentId, "Document ID");
544
+ const newName = this.requireId(name, "Name");
545
+ this.logger.info("Renaming document", { documentId: id });
546
+ return this.call(
547
+ "Failed to rename document",
548
+ () => this.http.patch(`/documents/${id}`, { name: newName })
376
549
  );
377
550
  }
378
551
  /** Get document details. */
@@ -406,6 +579,9 @@ var DocumentResource = class extends BaseResource {
406
579
  }
407
580
  } catch (err) {
408
581
  if (err instanceof ValidationError) throw err;
582
+ if (err instanceof ApiError && err.statusCode < 500 && err.statusCode !== 429) {
583
+ throw err;
584
+ }
409
585
  this.logger.warn("Error checking document status", {
410
586
  error: err instanceof Error ? err.message : String(err)
411
587
  });
@@ -636,7 +812,8 @@ var SignerResource = class extends BaseResource {
636
812
  () => this.http.post(`/accounts/${id}/signers`, normaliseSignerPayload(payload))
637
813
  );
638
814
  } catch (err) {
639
- if (err instanceof ApiError && err.statusCode === 409 && payload.email) {
815
+ const isDuplicateStatus = err instanceof ApiError && (err.statusCode === 409 || err.statusCode === 400);
816
+ if (isDuplicateStatus && payload.email) {
640
817
  const duplicate = await this.findByEmail(payload.email, id);
641
818
  if (duplicate) {
642
819
  this.logger.info("Signer already exists, using existing signer", {
@@ -657,12 +834,12 @@ var SignerResource = class extends BaseResource {
657
834
  () => this.http.get(`/accounts/${id}/signers/${sid}`)
658
835
  );
659
836
  }
660
- /** List signers for the workspace (supports `page`, `per_page`, `search`, `sort`). */
837
+ /** List signers for the workspace (supports `page`, `per-page`, `search`, `sort`). */
661
838
  async list(params = {}, accountId) {
662
839
  const id = this.accountId(accountId);
663
840
  return this.callList(
664
841
  "Failed to list signers",
665
- () => this.http.get(`/accounts/${id}/signers`, { params: cleanParams(params) })
842
+ () => this.http.get(`/accounts/${id}/signers`, { params: cleanListParams(params) })
666
843
  );
667
844
  }
668
845
  /** Update a signer. Fails if the signer has active assignments. */
@@ -683,11 +860,28 @@ var SignerResource = class extends BaseResource {
683
860
  () => this.http.delete(`/accounts/${id}/signers/${sid}`)
684
861
  );
685
862
  }
686
- /** Find a signer by email via the API's `search` parameter. Returns `null` if none match. */
863
+ /**
864
+ * Find a signer by exact email, using the API's `search` filter to narrow
865
+ * the page first. Returns `null` if none match.
866
+ *
867
+ * `search` is a substring match across signer fields, so the result is
868
+ * re-filtered here for an exact, case-insensitive email match.
869
+ *
870
+ * Page size is pinned to the API's maximum of 50: larger values are
871
+ * silently clamped to 50 by the server, so asking for more is misleading.
872
+ * An exact address realistically matches one signer, but a search term that
873
+ * matched more than 50 could in principle miss one — the API exposes no
874
+ * exact-email filter to rule that out.
875
+ *
876
+ * @param email - Exact email address to look for.
877
+ * @param accountId - Override the client's default account ID.
878
+ * @returns The matching {@link ISigner}, or `null`.
879
+ * @throws {ValidationError} If `email` is not a valid address.
880
+ */
687
881
  async findByEmail(email, accountId) {
688
882
  this.assertEmail(email);
689
883
  try {
690
- const { data } = await this.list({ search: email, per_page: 100 }, accountId);
884
+ const { data } = await this.list({ search: email, "per-page": 50 }, accountId);
691
885
  const lower = email.toLowerCase();
692
886
  return data.find((s) => (s.email ?? "").toLowerCase() === lower) ?? null;
693
887
  } catch (err) {
@@ -799,6 +993,58 @@ function normaliseSignerRef(ref, options) {
799
993
  throw new ValidationError("Invalid signer reference", { ref });
800
994
  }
801
995
  var AssignmentResource = class extends BaseResource {
996
+ /**
997
+ * List assignments across the workspace (`GET /assignments`).
998
+ *
999
+ * The account is passed as an `accountId` **query parameter** — the API
1000
+ * responds `400` ("Um contexto de conta é necessário e não foi fornecido")
1001
+ * without it. Note the camelCase spelling: `account_id` and an
1002
+ * `X-Account-Id` header are both rejected.
1003
+ *
1004
+ * @param params - `page`, `per-page`.
1005
+ * @param accountId - Override the client's default account ID.
1006
+ * @returns Assignments, with pagination in `meta`. Each item:
1007
+ * ```jsonc
1008
+ * {
1009
+ * "id": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4",
1010
+ * "sender_email": "sender@example.com",
1011
+ * "method": "virtual",
1012
+ * "expires_at": null,
1013
+ * "message": "Please sign this contract",
1014
+ * "signers": [
1015
+ * {
1016
+ * "id": "b2c3d4e5f6a7b8c9d0e1f2a3b4c5",
1017
+ * "full_name": "Ana Souza",
1018
+ * "email": "signer@example.com",
1019
+ * "whatsapp_phone_number": null,
1020
+ * "has_accepted_terms": false,
1021
+ * "completed": false,
1022
+ * "notification_history": [],
1023
+ * "verification_method": "Email",
1024
+ * "notification_methods": ["Email"],
1025
+ * "step": 1,
1026
+ * "notified": true
1027
+ * }
1028
+ * ]
1029
+ * }
1030
+ * ```
1031
+ * @throws {ValidationError} If no account ID is available.
1032
+ * @throws {ApiError} If the API rejects the request.
1033
+ *
1034
+ * @example
1035
+ * ```ts
1036
+ * const { data, meta } = await client.assignments.list({ 'per-page': 20 });
1037
+ * ```
1038
+ */
1039
+ async list(params = {}, accountId) {
1040
+ const id = this.accountId(accountId);
1041
+ return this.callList(
1042
+ "Failed to list assignments",
1043
+ () => this.http.get("/assignments", {
1044
+ params: { accountId: id, ...cleanListParams(params) }
1045
+ })
1046
+ );
1047
+ }
802
1048
  /** Create a signing assignment for a document. */
803
1049
  async create(documentId, payload) {
804
1050
  const docId = this.requireId(documentId, "Document ID");
@@ -934,16 +1180,14 @@ var WebhookResource = class extends BaseResource {
934
1180
  () => this.http.get(`/accounts/${id}/webhooks/subscriptions`)
935
1181
  );
936
1182
  }
937
- /** Delete the current webhook subscription. */
938
- async delete(accountId) {
939
- const id = this.accountId(accountId);
940
- this.logger.info("Deleting webhook subscription");
941
- return this.callVoid(
942
- "Failed to delete webhook subscription",
943
- () => this.http.delete(`/accounts/${id}/webhooks/subscriptions`)
944
- );
945
- }
946
- /** Inactivate the current webhook subscription without deleting it. */
1183
+ /**
1184
+ * Inactivate the current webhook subscription.
1185
+ *
1186
+ * This is the only supported way to stop deliveries — the API has no
1187
+ * subscription-delete route. The subscription is retained (with its `url`
1188
+ * and `events`) and simply stops firing; re-enable it by calling
1189
+ * {@link WebhookResource.register} again with `is_active: true`.
1190
+ */
947
1191
  async inactivate(accountId) {
948
1192
  const id = this.accountId(accountId);
949
1193
  this.logger.info("Inactivating webhook subscription");
@@ -965,7 +1209,7 @@ var WebhookResource = class extends BaseResource {
965
1209
  return this.callList(
966
1210
  "Failed to list webhook dispatches",
967
1211
  () => this.http.get(`/accounts/${id}/webhooks`, {
968
- params: cleanParams(params)
1212
+ params: cleanListParams(params)
969
1213
  })
970
1214
  );
971
1215
  }
@@ -1001,24 +1245,18 @@ var TemplateResource = class extends BaseResource {
1001
1245
  * ```
1002
1246
  */
1003
1247
  async create(source, options = {}) {
1004
- const { buffer, fileName } = await loadSource(source);
1005
- validateUpload(buffer, fileName);
1006
1248
  const id = this.accountId(options.accountId);
1007
1249
  const formOptions = {};
1008
1250
  if (options.name !== void 0) formOptions.name = options.name;
1009
- const form = buildUploadForm(buffer, fileName, formOptions);
1010
- this.logger.info("Creating template", { fileName, size: buffer.byteLength });
1011
- const template = await this.call(
1012
- "Failed to create template",
1013
- () => this.http.post(`/accounts/${id}/templates`, form, {
1014
- headers: { "Content-Type": "multipart/form-data" }
1015
- })
1251
+ const template = await this.uploadPdf(
1252
+ `/accounts/${id}/templates`,
1253
+ source,
1254
+ formOptions,
1255
+ {
1256
+ errorLabel: "Failed to create template",
1257
+ missingId: "Template upload succeeded but no template ID was returned"
1258
+ }
1016
1259
  );
1017
- if (!template?.id) {
1018
- throw new ValidationError("Template upload succeeded but no template ID was returned", {
1019
- response: template
1020
- });
1021
- }
1022
1260
  this.logger.info("Template created", { templateId: template.id });
1023
1261
  return template;
1024
1262
  }
@@ -1027,14 +1265,17 @@ var TemplateResource = class extends BaseResource {
1027
1265
  const id = this.accountId(accountId);
1028
1266
  return this.callList(
1029
1267
  "Failed to list templates",
1030
- () => this.http.get(`/accounts/${id}/templates`, { params: cleanParams(params) })
1268
+ () => this.http.get(`/accounts/${id}/templates`, { params: cleanListParams(params) })
1031
1269
  );
1032
1270
  }
1033
1271
  /**
1034
1272
  * Get a template by ID (`GET /accounts/{id}/templates/{template_id}`).
1035
1273
  *
1036
- * Unlike the list endpoint, the single-template response includes `pages`
1037
- * (with per-page `download_url`) and `default_document_tags`.
1274
+ * Returns the same shape as {@link TemplateResource.list} plus
1275
+ * `default_document_tags` (the tags auto-applied to every document created
1276
+ * from this template) and `resource`. Both endpoints return `pages` with
1277
+ * per-page `download_url`, so fetching a template again purely to read its
1278
+ * pages is unnecessary.
1038
1279
  */
1039
1280
  async get(templateId, accountId) {
1040
1281
  const id = this.accountId(accountId);
@@ -1102,7 +1343,7 @@ var TagResource = class extends BaseResource {
1102
1343
  return this.call(
1103
1344
  "Failed to list tags",
1104
1345
  () => this.http.get(`/accounts/${id}/tags`, {
1105
- params: cleanParams(params)
1346
+ params: cleanListParams(params)
1106
1347
  })
1107
1348
  );
1108
1349
  }
@@ -1244,7 +1485,7 @@ var FieldsResource = class extends BaseResource {
1244
1485
  return this.call(
1245
1486
  "Failed to list field definitions",
1246
1487
  () => this.http.get(`/accounts/${id}/fields`, {
1247
- params: cleanParams(params)
1488
+ params: cleanListParams(params)
1248
1489
  })
1249
1490
  );
1250
1491
  }
@@ -1328,7 +1569,44 @@ var SignerDocumentsResource = class extends BaseResource {
1328
1569
  return this.callList(
1329
1570
  "Failed to list signer documents",
1330
1571
  () => this.http.get(`/signers/${sid}/documents`, {
1331
- params: { "signer-access-code": code, ...cleanParams(params) }
1572
+ params: { "signer-access-code": code, ...cleanListParams(params) }
1573
+ })
1574
+ );
1575
+ }
1576
+ /**
1577
+ * Search the documents awaiting a given signer
1578
+ * (`GET /signers/{signer_id}/documents/search?signer-access-code=…`).
1579
+ *
1580
+ * The signer-side counterpart of {@link DocumentResource.search}, scoped to
1581
+ * one signer and authorised by their access code rather than the API key.
1582
+ * Like {@link SignerDocumentsResource.list}, it requires
1583
+ * `signer-access-code`; the published spec omits that parameter, but the
1584
+ * endpoint is not usable without it.
1585
+ *
1586
+ * @param signerId - The signer whose documents are searched.
1587
+ * @param signerAccessCode - The signer's access code, from their signing link.
1588
+ * @param search - Free-text term matched against the document name.
1589
+ * @returns Matching documents for that signer, in the compact
1590
+ * {@link IDocumentListItem} shape, with pagination in `meta`.
1591
+ * @throws {ValidationError} If `signerId` or `signerAccessCode` is missing.
1592
+ * @throws {ApiError} If the access code is invalid or expired.
1593
+ *
1594
+ * @example
1595
+ * ```ts
1596
+ * const { data } = await client.signerDocuments.search(
1597
+ * signerId,
1598
+ * accessCode,
1599
+ * 'agreement',
1600
+ * );
1601
+ * ```
1602
+ */
1603
+ async search(signerId, signerAccessCode, search) {
1604
+ const sid = this.requireId(signerId, "Signer ID");
1605
+ const code = this.requireId(signerAccessCode, "signer-access-code");
1606
+ return this.callList(
1607
+ "Failed to search signer documents",
1608
+ () => this.http.get(`/signers/${sid}/documents/search`, {
1609
+ params: cleanParams({ "signer-access-code": code, search })
1332
1610
  })
1333
1611
  );
1334
1612
  }
@@ -1548,6 +1826,21 @@ var WebhookVerifier = class {
1548
1826
  // src/client.ts
1549
1827
  var DEFAULT_BASE_URL = "https://api.assinafy.com.br/v1";
1550
1828
  var AssinafyClient = class _AssinafyClient {
1829
+ axiosInstance;
1830
+ defaultAccountId;
1831
+ logger;
1832
+ webhookSecret;
1833
+ documents;
1834
+ signers;
1835
+ workspaces;
1836
+ assignments;
1837
+ webhooks;
1838
+ templates;
1839
+ tags;
1840
+ auth;
1841
+ fields;
1842
+ signerDocuments;
1843
+ webhookVerifier;
1551
1844
  constructor(options) {
1552
1845
  if (!options.apiKey && !options.token) {
1553
1846
  throw new ValidationError(
@@ -1706,6 +1999,7 @@ function installRateLimitRetry(http, maxRetries, logger) {
1706
1999
  DEFAULT_WEBHOOK_EVENTS,
1707
2000
  DocumentResource,
1708
2001
  FieldsResource,
2002
+ MAX_UPLOAD_BYTES,
1709
2003
  NetworkError,
1710
2004
  SignerDocumentsResource,
1711
2005
  SignerResource,