@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.mjs CHANGED
@@ -3,16 +3,16 @@ import axios2 from "axios";
3
3
 
4
4
  // src/errors.ts
5
5
  var AssinafyError = class extends Error {
6
+ context;
6
7
  constructor(message, context = {}, options) {
7
- super(message);
8
+ super(message, options);
8
9
  this.name = "AssinafyError";
9
10
  this.context = context;
10
- if (options?.cause !== void 0) {
11
- this.cause = options.cause;
12
- }
13
11
  }
14
12
  };
15
13
  var ApiError = class _ApiError extends AssinafyError {
14
+ statusCode;
15
+ responseData;
16
16
  constructor(message, statusCode, responseData = null, options) {
17
17
  super(message, { statusCode, responseData }, options);
18
18
  this.name = "ApiError";
@@ -28,6 +28,7 @@ var ApiError = class _ApiError extends AssinafyError {
28
28
  }
29
29
  };
30
30
  var ValidationError = class extends AssinafyError {
31
+ errors;
31
32
  constructor(message = "Validation failed", errors = {}) {
32
33
  super(message, { errors });
33
34
  this.name = "ValidationError";
@@ -53,6 +54,23 @@ function handleAssinafyResponse(response) {
53
54
  }
54
55
  return response;
55
56
  }
57
+ function decodeBinaryErrorBody(data) {
58
+ let text;
59
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(data)) {
60
+ text = data.toString("utf8");
61
+ } else if (data instanceof ArrayBuffer) {
62
+ text = Buffer.from(data).toString("utf8");
63
+ } else if (ArrayBuffer.isView(data)) {
64
+ text = Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString("utf8");
65
+ } else {
66
+ return data;
67
+ }
68
+ try {
69
+ return JSON.parse(text);
70
+ } catch {
71
+ return text.length > 0 ? { message: text } : null;
72
+ }
73
+ }
56
74
  function toSdkError(error, fallbackMessage) {
57
75
  if (error instanceof AssinafyError) {
58
76
  return error;
@@ -60,14 +78,15 @@ function toSdkError(error, fallbackMessage) {
60
78
  if (axios.isAxiosError(error)) {
61
79
  const status = error.response?.status;
62
80
  if (status) {
63
- return ApiError.fromResponse(status, error.response?.data ?? null);
81
+ const body = decodeBinaryErrorBody(error.response?.data ?? null);
82
+ return ApiError.fromResponse(status, body ?? null);
64
83
  }
65
84
  return new NetworkError(`${fallbackMessage}: ${error.message}`, { cause: error });
66
85
  }
67
86
  if (error instanceof Error) {
68
87
  return new AssinafyError(`${fallbackMessage}: ${error.message}`, {}, { cause: error });
69
88
  }
70
- return new AssinafyError(fallbackMessage, { cause: error });
89
+ return new AssinafyError(fallbackMessage, {}, { cause: error });
71
90
  }
72
91
  function createNoopLogger() {
73
92
  return {
@@ -86,6 +105,14 @@ function cleanParams(params) {
86
105
  }
87
106
  return out;
88
107
  }
108
+ function cleanListParams(params) {
109
+ const out = cleanParams(params);
110
+ if (out["per_page"] !== void 0) {
111
+ out["per-page"] ??= out["per_page"];
112
+ delete out["per_page"];
113
+ }
114
+ return out;
115
+ }
89
116
 
90
117
  // src/support/retry.ts
91
118
  function header(headers, name) {
@@ -122,7 +149,57 @@ function nextRetryDelayMs(headers, attempt, maxDelayMs = 3e4) {
122
149
  return Math.min(backoffMs(attempt), maxDelayMs);
123
150
  }
124
151
 
152
+ // src/resources/upload.ts
153
+ import { promises as fs } from "fs";
154
+ import path from "path";
155
+ var MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
156
+ async function loadSource(source) {
157
+ if ("buffer" in source) {
158
+ if (!source.fileName) {
159
+ throw new ValidationError("fileName is required when uploading a Buffer");
160
+ }
161
+ return { buffer: source.buffer, fileName: source.fileName };
162
+ }
163
+ if (!source.filePath) {
164
+ throw new ValidationError("filePath is required");
165
+ }
166
+ const buffer = await fs.readFile(source.filePath);
167
+ return { buffer, fileName: source.fileName ?? path.basename(source.filePath) };
168
+ }
169
+ function validateUpload(buffer, fileName) {
170
+ if (!buffer || buffer.byteLength === 0) {
171
+ throw new ValidationError("File buffer is empty", { fileName });
172
+ }
173
+ if (!fileName.toLowerCase().endsWith(".pdf")) {
174
+ throw new ValidationError("Only PDF files are supported", { fileName });
175
+ }
176
+ if (buffer.byteLength > MAX_UPLOAD_BYTES) {
177
+ throw new ValidationError("File size exceeds maximum allowed (25MB)", {
178
+ fileSize: buffer.byteLength,
179
+ maxSize: MAX_UPLOAD_BYTES
180
+ });
181
+ }
182
+ }
183
+ function toUploadFileName(name) {
184
+ return name.toLowerCase().endsWith(".pdf") ? name : `${name}.pdf`;
185
+ }
186
+ function buildUploadForm(buffer, fileName, options = {}) {
187
+ const form = new FormData();
188
+ const view = new Uint8Array(
189
+ buffer.buffer,
190
+ buffer.byteOffset,
191
+ buffer.byteLength
192
+ );
193
+ const partName = options.name === void 0 ? fileName : toUploadFileName(options.name);
194
+ form.append("file", new Blob([view], { type: "application/pdf" }), partName);
195
+ if (options.metadata) {
196
+ form.append("metadata", JSON.stringify(options.metadata));
197
+ }
198
+ return form;
199
+ }
200
+
125
201
  // src/resources/base.ts
202
+ var MULTIPART_CONTENT_TYPE = "multipart/form-data";
126
203
  var BaseResource = class {
127
204
  constructor(http, defaultAccountId, logger = createNoopLogger()) {
128
205
  this.http = http;
@@ -184,6 +261,35 @@ var BaseResource = class {
184
261
  throw toSdkError(err, label);
185
262
  }
186
263
  }
264
+ /**
265
+ * Upload a PDF as `multipart/form-data` and assert the API echoed an id.
266
+ *
267
+ * Shared by `documents.upload` and `templates.create`, which are the same
268
+ * sequence over different paths: load → validate → build form → POST →
269
+ * assert an id came back. Callers keep their own success logging.
270
+ *
271
+ * @param path - Account-scoped endpoint to POST to.
272
+ * @param source - The PDF, as a file path or in-memory buffer.
273
+ * @param formOptions - `name` (display name) and optional `metadata`.
274
+ * @param labels - `errorLabel` for the request failure, `missingId` for a
275
+ * `2xx` that returned no id.
276
+ */
277
+ async uploadPdf(path2, source, formOptions, labels) {
278
+ const { buffer, fileName } = await loadSource(source);
279
+ validateUpload(buffer, fileName);
280
+ this.logger.info("Uploading PDF", { path: path2, fileName, size: buffer.byteLength });
281
+ const form = buildUploadForm(buffer, fileName, formOptions);
282
+ const result = await this.call(
283
+ labels.errorLabel,
284
+ () => this.http.post(path2, form, { headers: { "Content-Type": MULTIPART_CONTENT_TYPE } })
285
+ );
286
+ if (!result?.id) {
287
+ throw new ValidationError(labels.missingId, {
288
+ response: result
289
+ });
290
+ }
291
+ return result;
292
+ }
187
293
  /** Execute a paginated list call and attach meta from `X-Pagination-*` headers. */
188
294
  async callList(label, request) {
189
295
  try {
@@ -225,48 +331,6 @@ function toInt(value) {
225
331
  return Number.isFinite(n) ? n : void 0;
226
332
  }
227
333
 
228
- // src/resources/upload.ts
229
- import { promises as fs } from "fs";
230
- import path from "path";
231
- var MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
232
- async function loadSource(source) {
233
- if ("buffer" in source) {
234
- if (!source.fileName) {
235
- throw new ValidationError("fileName is required when uploading a Buffer");
236
- }
237
- return { buffer: source.buffer, fileName: source.fileName };
238
- }
239
- if (!source.filePath) {
240
- throw new ValidationError("filePath is required");
241
- }
242
- const buffer = await fs.readFile(source.filePath);
243
- return { buffer, fileName: source.fileName ?? path.basename(source.filePath) };
244
- }
245
- function validateUpload(buffer, fileName) {
246
- if (!buffer || buffer.byteLength === 0) {
247
- throw new ValidationError("File buffer is empty", { fileName });
248
- }
249
- if (!fileName.toLowerCase().endsWith(".pdf")) {
250
- throw new ValidationError("Only PDF files are supported", { fileName });
251
- }
252
- if (buffer.byteLength > MAX_UPLOAD_BYTES) {
253
- throw new ValidationError("File size exceeds maximum allowed (25MB)", {
254
- fileSize: buffer.byteLength,
255
- maxSize: MAX_UPLOAD_BYTES
256
- });
257
- }
258
- }
259
- function buildUploadForm(buffer, fileName, options = {}) {
260
- const form = new FormData();
261
- const view = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
262
- form.append("file", new Blob([view], { type: "application/pdf" }), fileName);
263
- form.append("name", options.name ?? fileName);
264
- if (options.metadata) {
265
- form.append("metadata", JSON.stringify(options.metadata));
266
- }
267
- return form;
268
- }
269
-
270
334
  // src/resources/documents.ts
271
335
  var READY_STATUSES = /* @__PURE__ */ new Set([
272
336
  "metadata_ready",
@@ -281,45 +345,153 @@ var FAILED_STATUSES = /* @__PURE__ */ new Set([
281
345
  ]);
282
346
  var DocumentResource = class extends BaseResource {
283
347
  /**
284
- * Upload a PDF to the workspace.
348
+ * Upload a PDF to the workspace (`POST /accounts/{accountId}/documents`).
349
+ *
350
+ * The document is created in `metadata_processing` status and becomes
351
+ * usable once it reaches `metadata_ready`; use
352
+ * {@link DocumentResource.waitUntilReady} to await that transition. Note
353
+ * that {@link DocumentResource.rename} and {@link DocumentResource.delete}
354
+ * return `400` while the document is still processing.
355
+ *
356
+ * @param source - The PDF to upload, as a file path or an in-memory buffer.
357
+ * @param options - Display name, metadata, and account override.
358
+ * @returns The created document. Response shape:
359
+ * ```jsonc
360
+ * {
361
+ * "resource": "document",
362
+ * "id": "c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
363
+ * "name": "Service agreement.pdf",
364
+ * "status": "metadata_processing",
365
+ * "created_at": "2026-07-15T16:15:33Z",
366
+ * "updated_at": "2026-07-15T16:15:33Z"
367
+ * }
368
+ * ```
369
+ * @throws {ValidationError} If the file is empty, not a `.pdf`, exceeds
370
+ * 25 MB, or the API returns no document ID.
371
+ * @throws {ApiError} If the API rejects the upload.
285
372
  *
286
373
  * @example
287
374
  * ```ts
288
375
  * await client.documents.upload({ filePath: './contract.pdf' });
289
- * await client.documents.upload({ buffer, fileName: 'contract.pdf' }, { metadata });
376
+ * await client.documents.upload(
377
+ * { buffer, fileName: 'contract.pdf' },
378
+ * { name: 'Service agreement', metadata: { orderId: 'A-1' } },
379
+ * );
380
+ * // → name is stored as 'Service agreement.pdf'
290
381
  * ```
291
382
  */
292
383
  async upload(source, options = {}) {
293
- const { buffer, fileName } = await loadSource(source);
294
- validateUpload(buffer, fileName);
295
384
  const accountId = this.accountId(options.accountId);
296
385
  const formOptions = {};
386
+ if (options.name !== void 0) formOptions.name = options.name;
297
387
  if (options.metadata !== void 0) formOptions.metadata = options.metadata;
298
- const form = buildUploadForm(buffer, fileName, formOptions);
299
- this.logger.info("Uploading document", { fileName, size: buffer.byteLength });
300
- const document = await this.call(
301
- "Document upload failed",
302
- () => this.http.post(`/accounts/${accountId}/documents`, form, {
303
- headers: { "Content-Type": "multipart/form-data" }
304
- })
388
+ const document = await this.uploadPdf(
389
+ `/accounts/${accountId}/documents`,
390
+ source,
391
+ formOptions,
392
+ {
393
+ errorLabel: "Document upload failed",
394
+ missingId: "Upload succeeded but no document ID was returned"
395
+ }
305
396
  );
306
- if (!document?.id) {
307
- throw new ValidationError("Upload succeeded but no document ID was returned", {
308
- response: document
309
- });
310
- }
311
397
  this.logger.info("Document uploaded", { documentId: document.id });
312
398
  return document;
313
399
  }
314
400
  /**
315
401
  * List workspace documents. Pagination info (if any) is attached in `meta`.
316
- * Supports `status`, `method`, `tags`, `search`, `sort`, `page`, `per_page`.
402
+ * Supports `status`, `method`, `tags`, `search`, `sort`, `page`, `per-page`.
317
403
  */
318
404
  async list(params = {}, accountId) {
319
405
  const id = this.accountId(accountId);
320
406
  return this.callList(
321
407
  "Failed to list documents",
322
- () => this.http.get(`/accounts/${id}/documents`, { params: cleanParams(params) })
408
+ () => this.http.get(`/accounts/${id}/documents`, { params: cleanListParams(params) })
409
+ );
410
+ }
411
+ /**
412
+ * Search workspace documents
413
+ * (`GET /accounts/{accountId}/documents/search`).
414
+ *
415
+ * A lighter-weight alternative to {@link DocumentResource.list}: it returns
416
+ * a compact representation with no expanded `assignment` or `pages`, so
417
+ * prefer it for name lookups and pickers.
418
+ *
419
+ * @param params - `search`, `status`, `page`, `per-page`.
420
+ * @param accountId - Override the client's default account ID.
421
+ * @returns Matching documents, with pagination in `meta`. Each item:
422
+ * ```jsonc
423
+ * {
424
+ * "id": "c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
425
+ * "account_id": "d4e5f6a7b8c9d0e1f2a3b4c5d6e7",
426
+ * "template_id": null,
427
+ * "name": "Service agreement.pdf",
428
+ * "status": "pending_signature",
429
+ * "artifacts": { "original": "https://…" },
430
+ * "is_closed": false,
431
+ * "signing_url": "https://…",
432
+ * "decline_reason": null,
433
+ * "declined_by": null,
434
+ * "tags": [],
435
+ * "created_at": "2026-07-15T16:15:33Z",
436
+ * "updated_at": "2026-07-15T16:15:40Z"
437
+ * }
438
+ * ```
439
+ * @throws {ValidationError} If no account ID is available.
440
+ * @throws {ApiError} If the API rejects the request.
441
+ *
442
+ * @example
443
+ * ```ts
444
+ * const { data, meta } = await client.documents.search({
445
+ * search: 'agreement',
446
+ * status: 'pending_signature',
447
+ * 'per-page': 20,
448
+ * });
449
+ * ```
450
+ */
451
+ async search(params = {}, accountId) {
452
+ const id = this.accountId(accountId);
453
+ return this.callList(
454
+ "Failed to search documents",
455
+ () => this.http.get(`/accounts/${id}/documents/search`, { params: cleanListParams(params) })
456
+ );
457
+ }
458
+ /**
459
+ * Rename a document (`PATCH /documents/{documentId}`).
460
+ *
461
+ * Only valid while the document is still renameable: the API returns `400`
462
+ * ("Document cannot be renamed after the signature process has started")
463
+ * both once signing has begun **and** while the document is still in
464
+ * `metadata_processing` immediately after upload. Await
465
+ * {@link DocumentResource.waitUntilReady} before renaming a fresh upload.
466
+ *
467
+ * To set a name at upload time instead, pass `name` to
468
+ * {@link DocumentResource.upload} — that avoids the extra round-trip and
469
+ * the processing race entirely.
470
+ *
471
+ * @param documentId - The document to rename.
472
+ * @param name - The new display name (max 255 chars), e.g.
473
+ * `'Service agreement.pdf'`.
474
+ * @returns The updated document — **without** `pages` or `assignment`,
475
+ * which this endpoint does not return (unlike
476
+ * {@link DocumentResource.details}). Call `details()` if you need them.
477
+ * @throws {ValidationError} If `documentId` or `name` is missing.
478
+ * @throws {ApiError} `400` if the document is processing or already in
479
+ * signing; `404` if it does not exist.
480
+ *
481
+ * @example
482
+ * ```ts
483
+ * const doc = await client.documents.upload({ filePath: './c.pdf' });
484
+ * await client.documents.waitUntilReady(doc.id); // else 400
485
+ * await client.documents.rename(doc.id, 'Service agreement.pdf');
486
+ * ```
487
+ */
488
+ async rename(documentId, name) {
489
+ const id = this.requireId(documentId, "Document ID");
490
+ const newName = this.requireId(name, "Name");
491
+ this.logger.info("Renaming document", { documentId: id });
492
+ return this.call(
493
+ "Failed to rename document",
494
+ () => this.http.patch(`/documents/${id}`, { name: newName })
323
495
  );
324
496
  }
325
497
  /** Get document details. */
@@ -353,6 +525,9 @@ var DocumentResource = class extends BaseResource {
353
525
  }
354
526
  } catch (err) {
355
527
  if (err instanceof ValidationError) throw err;
528
+ if (err instanceof ApiError && err.statusCode < 500 && err.statusCode !== 429) {
529
+ throw err;
530
+ }
356
531
  this.logger.warn("Error checking document status", {
357
532
  error: err instanceof Error ? err.message : String(err)
358
533
  });
@@ -583,7 +758,8 @@ var SignerResource = class extends BaseResource {
583
758
  () => this.http.post(`/accounts/${id}/signers`, normaliseSignerPayload(payload))
584
759
  );
585
760
  } catch (err) {
586
- if (err instanceof ApiError && err.statusCode === 409 && payload.email) {
761
+ const isDuplicateStatus = err instanceof ApiError && (err.statusCode === 409 || err.statusCode === 400);
762
+ if (isDuplicateStatus && payload.email) {
587
763
  const duplicate = await this.findByEmail(payload.email, id);
588
764
  if (duplicate) {
589
765
  this.logger.info("Signer already exists, using existing signer", {
@@ -604,12 +780,12 @@ var SignerResource = class extends BaseResource {
604
780
  () => this.http.get(`/accounts/${id}/signers/${sid}`)
605
781
  );
606
782
  }
607
- /** List signers for the workspace (supports `page`, `per_page`, `search`, `sort`). */
783
+ /** List signers for the workspace (supports `page`, `per-page`, `search`, `sort`). */
608
784
  async list(params = {}, accountId) {
609
785
  const id = this.accountId(accountId);
610
786
  return this.callList(
611
787
  "Failed to list signers",
612
- () => this.http.get(`/accounts/${id}/signers`, { params: cleanParams(params) })
788
+ () => this.http.get(`/accounts/${id}/signers`, { params: cleanListParams(params) })
613
789
  );
614
790
  }
615
791
  /** Update a signer. Fails if the signer has active assignments. */
@@ -630,11 +806,28 @@ var SignerResource = class extends BaseResource {
630
806
  () => this.http.delete(`/accounts/${id}/signers/${sid}`)
631
807
  );
632
808
  }
633
- /** Find a signer by email via the API's `search` parameter. Returns `null` if none match. */
809
+ /**
810
+ * Find a signer by exact email, using the API's `search` filter to narrow
811
+ * the page first. Returns `null` if none match.
812
+ *
813
+ * `search` is a substring match across signer fields, so the result is
814
+ * re-filtered here for an exact, case-insensitive email match.
815
+ *
816
+ * Page size is pinned to the API's maximum of 50: larger values are
817
+ * silently clamped to 50 by the server, so asking for more is misleading.
818
+ * An exact address realistically matches one signer, but a search term that
819
+ * matched more than 50 could in principle miss one — the API exposes no
820
+ * exact-email filter to rule that out.
821
+ *
822
+ * @param email - Exact email address to look for.
823
+ * @param accountId - Override the client's default account ID.
824
+ * @returns The matching {@link ISigner}, or `null`.
825
+ * @throws {ValidationError} If `email` is not a valid address.
826
+ */
634
827
  async findByEmail(email, accountId) {
635
828
  this.assertEmail(email);
636
829
  try {
637
- const { data } = await this.list({ search: email, per_page: 100 }, accountId);
830
+ const { data } = await this.list({ search: email, "per-page": 50 }, accountId);
638
831
  const lower = email.toLowerCase();
639
832
  return data.find((s) => (s.email ?? "").toLowerCase() === lower) ?? null;
640
833
  } catch (err) {
@@ -746,6 +939,58 @@ function normaliseSignerRef(ref, options) {
746
939
  throw new ValidationError("Invalid signer reference", { ref });
747
940
  }
748
941
  var AssignmentResource = class extends BaseResource {
942
+ /**
943
+ * List assignments across the workspace (`GET /assignments`).
944
+ *
945
+ * The account is passed as an `accountId` **query parameter** — the API
946
+ * responds `400` ("Um contexto de conta é necessário e não foi fornecido")
947
+ * without it. Note the camelCase spelling: `account_id` and an
948
+ * `X-Account-Id` header are both rejected.
949
+ *
950
+ * @param params - `page`, `per-page`.
951
+ * @param accountId - Override the client's default account ID.
952
+ * @returns Assignments, with pagination in `meta`. Each item:
953
+ * ```jsonc
954
+ * {
955
+ * "id": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4",
956
+ * "sender_email": "sender@example.com",
957
+ * "method": "virtual",
958
+ * "expires_at": null,
959
+ * "message": "Please sign this contract",
960
+ * "signers": [
961
+ * {
962
+ * "id": "b2c3d4e5f6a7b8c9d0e1f2a3b4c5",
963
+ * "full_name": "Ana Souza",
964
+ * "email": "signer@example.com",
965
+ * "whatsapp_phone_number": null,
966
+ * "has_accepted_terms": false,
967
+ * "completed": false,
968
+ * "notification_history": [],
969
+ * "verification_method": "Email",
970
+ * "notification_methods": ["Email"],
971
+ * "step": 1,
972
+ * "notified": true
973
+ * }
974
+ * ]
975
+ * }
976
+ * ```
977
+ * @throws {ValidationError} If no account ID is available.
978
+ * @throws {ApiError} If the API rejects the request.
979
+ *
980
+ * @example
981
+ * ```ts
982
+ * const { data, meta } = await client.assignments.list({ 'per-page': 20 });
983
+ * ```
984
+ */
985
+ async list(params = {}, accountId) {
986
+ const id = this.accountId(accountId);
987
+ return this.callList(
988
+ "Failed to list assignments",
989
+ () => this.http.get("/assignments", {
990
+ params: { accountId: id, ...cleanListParams(params) }
991
+ })
992
+ );
993
+ }
749
994
  /** Create a signing assignment for a document. */
750
995
  async create(documentId, payload) {
751
996
  const docId = this.requireId(documentId, "Document ID");
@@ -881,16 +1126,14 @@ var WebhookResource = class extends BaseResource {
881
1126
  () => this.http.get(`/accounts/${id}/webhooks/subscriptions`)
882
1127
  );
883
1128
  }
884
- /** Delete the current webhook subscription. */
885
- async delete(accountId) {
886
- const id = this.accountId(accountId);
887
- this.logger.info("Deleting webhook subscription");
888
- return this.callVoid(
889
- "Failed to delete webhook subscription",
890
- () => this.http.delete(`/accounts/${id}/webhooks/subscriptions`)
891
- );
892
- }
893
- /** Inactivate the current webhook subscription without deleting it. */
1129
+ /**
1130
+ * Inactivate the current webhook subscription.
1131
+ *
1132
+ * This is the only supported way to stop deliveries — the API has no
1133
+ * subscription-delete route. The subscription is retained (with its `url`
1134
+ * and `events`) and simply stops firing; re-enable it by calling
1135
+ * {@link WebhookResource.register} again with `is_active: true`.
1136
+ */
894
1137
  async inactivate(accountId) {
895
1138
  const id = this.accountId(accountId);
896
1139
  this.logger.info("Inactivating webhook subscription");
@@ -912,7 +1155,7 @@ var WebhookResource = class extends BaseResource {
912
1155
  return this.callList(
913
1156
  "Failed to list webhook dispatches",
914
1157
  () => this.http.get(`/accounts/${id}/webhooks`, {
915
- params: cleanParams(params)
1158
+ params: cleanListParams(params)
916
1159
  })
917
1160
  );
918
1161
  }
@@ -948,24 +1191,18 @@ var TemplateResource = class extends BaseResource {
948
1191
  * ```
949
1192
  */
950
1193
  async create(source, options = {}) {
951
- const { buffer, fileName } = await loadSource(source);
952
- validateUpload(buffer, fileName);
953
1194
  const id = this.accountId(options.accountId);
954
1195
  const formOptions = {};
955
1196
  if (options.name !== void 0) formOptions.name = options.name;
956
- const form = buildUploadForm(buffer, fileName, formOptions);
957
- this.logger.info("Creating template", { fileName, size: buffer.byteLength });
958
- const template = await this.call(
959
- "Failed to create template",
960
- () => this.http.post(`/accounts/${id}/templates`, form, {
961
- headers: { "Content-Type": "multipart/form-data" }
962
- })
1197
+ const template = await this.uploadPdf(
1198
+ `/accounts/${id}/templates`,
1199
+ source,
1200
+ formOptions,
1201
+ {
1202
+ errorLabel: "Failed to create template",
1203
+ missingId: "Template upload succeeded but no template ID was returned"
1204
+ }
963
1205
  );
964
- if (!template?.id) {
965
- throw new ValidationError("Template upload succeeded but no template ID was returned", {
966
- response: template
967
- });
968
- }
969
1206
  this.logger.info("Template created", { templateId: template.id });
970
1207
  return template;
971
1208
  }
@@ -974,14 +1211,17 @@ var TemplateResource = class extends BaseResource {
974
1211
  const id = this.accountId(accountId);
975
1212
  return this.callList(
976
1213
  "Failed to list templates",
977
- () => this.http.get(`/accounts/${id}/templates`, { params: cleanParams(params) })
1214
+ () => this.http.get(`/accounts/${id}/templates`, { params: cleanListParams(params) })
978
1215
  );
979
1216
  }
980
1217
  /**
981
1218
  * Get a template by ID (`GET /accounts/{id}/templates/{template_id}`).
982
1219
  *
983
- * Unlike the list endpoint, the single-template response includes `pages`
984
- * (with per-page `download_url`) and `default_document_tags`.
1220
+ * Returns the same shape as {@link TemplateResource.list} plus
1221
+ * `default_document_tags` (the tags auto-applied to every document created
1222
+ * from this template) and `resource`. Both endpoints return `pages` with
1223
+ * per-page `download_url`, so fetching a template again purely to read its
1224
+ * pages is unnecessary.
985
1225
  */
986
1226
  async get(templateId, accountId) {
987
1227
  const id = this.accountId(accountId);
@@ -1049,7 +1289,7 @@ var TagResource = class extends BaseResource {
1049
1289
  return this.call(
1050
1290
  "Failed to list tags",
1051
1291
  () => this.http.get(`/accounts/${id}/tags`, {
1052
- params: cleanParams(params)
1292
+ params: cleanListParams(params)
1053
1293
  })
1054
1294
  );
1055
1295
  }
@@ -1191,7 +1431,7 @@ var FieldsResource = class extends BaseResource {
1191
1431
  return this.call(
1192
1432
  "Failed to list field definitions",
1193
1433
  () => this.http.get(`/accounts/${id}/fields`, {
1194
- params: cleanParams(params)
1434
+ params: cleanListParams(params)
1195
1435
  })
1196
1436
  );
1197
1437
  }
@@ -1275,7 +1515,44 @@ var SignerDocumentsResource = class extends BaseResource {
1275
1515
  return this.callList(
1276
1516
  "Failed to list signer documents",
1277
1517
  () => this.http.get(`/signers/${sid}/documents`, {
1278
- params: { "signer-access-code": code, ...cleanParams(params) }
1518
+ params: { "signer-access-code": code, ...cleanListParams(params) }
1519
+ })
1520
+ );
1521
+ }
1522
+ /**
1523
+ * Search the documents awaiting a given signer
1524
+ * (`GET /signers/{signer_id}/documents/search?signer-access-code=…`).
1525
+ *
1526
+ * The signer-side counterpart of {@link DocumentResource.search}, scoped to
1527
+ * one signer and authorised by their access code rather than the API key.
1528
+ * Like {@link SignerDocumentsResource.list}, it requires
1529
+ * `signer-access-code`; the published spec omits that parameter, but the
1530
+ * endpoint is not usable without it.
1531
+ *
1532
+ * @param signerId - The signer whose documents are searched.
1533
+ * @param signerAccessCode - The signer's access code, from their signing link.
1534
+ * @param search - Free-text term matched against the document name.
1535
+ * @returns Matching documents for that signer, in the compact
1536
+ * {@link IDocumentListItem} shape, with pagination in `meta`.
1537
+ * @throws {ValidationError} If `signerId` or `signerAccessCode` is missing.
1538
+ * @throws {ApiError} If the access code is invalid or expired.
1539
+ *
1540
+ * @example
1541
+ * ```ts
1542
+ * const { data } = await client.signerDocuments.search(
1543
+ * signerId,
1544
+ * accessCode,
1545
+ * 'agreement',
1546
+ * );
1547
+ * ```
1548
+ */
1549
+ async search(signerId, signerAccessCode, search) {
1550
+ const sid = this.requireId(signerId, "Signer ID");
1551
+ const code = this.requireId(signerAccessCode, "signer-access-code");
1552
+ return this.callList(
1553
+ "Failed to search signer documents",
1554
+ () => this.http.get(`/signers/${sid}/documents/search`, {
1555
+ params: cleanParams({ "signer-access-code": code, search })
1279
1556
  })
1280
1557
  );
1281
1558
  }
@@ -1495,6 +1772,21 @@ var WebhookVerifier = class {
1495
1772
  // src/client.ts
1496
1773
  var DEFAULT_BASE_URL = "https://api.assinafy.com.br/v1";
1497
1774
  var AssinafyClient = class _AssinafyClient {
1775
+ axiosInstance;
1776
+ defaultAccountId;
1777
+ logger;
1778
+ webhookSecret;
1779
+ documents;
1780
+ signers;
1781
+ workspaces;
1782
+ assignments;
1783
+ webhooks;
1784
+ templates;
1785
+ tags;
1786
+ auth;
1787
+ fields;
1788
+ signerDocuments;
1789
+ webhookVerifier;
1498
1790
  constructor(options) {
1499
1791
  if (!options.apiKey && !options.token) {
1500
1792
  throw new ValidationError(
@@ -1652,6 +1944,7 @@ export {
1652
1944
  DEFAULT_WEBHOOK_EVENTS,
1653
1945
  DocumentResource,
1654
1946
  FieldsResource,
1947
+ MAX_UPLOAD_BYTES,
1655
1948
  NetworkError,
1656
1949
  SignerDocumentsResource,
1657
1950
  SignerResource,