@assinafy/sdk 1.4.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/README.md +105 -19
- package/dist/index.d.mts +572 -74
- package/dist/index.d.ts +572 -74
- package/dist/index.js +564 -115
- package/dist/index.mjs +562 -115
- package/package.json +24 -12
package/dist/index.js
CHANGED
|
@@ -35,8 +35,10 @@ __export(index_exports, {
|
|
|
35
35
|
AssinafyClient: () => AssinafyClient,
|
|
36
36
|
AssinafyError: () => AssinafyError,
|
|
37
37
|
AuthenticationResource: () => AuthenticationResource,
|
|
38
|
+
DEFAULT_WEBHOOK_EVENTS: () => DEFAULT_WEBHOOK_EVENTS,
|
|
38
39
|
DocumentResource: () => DocumentResource,
|
|
39
40
|
FieldsResource: () => FieldsResource,
|
|
41
|
+
MAX_UPLOAD_BYTES: () => MAX_UPLOAD_BYTES,
|
|
40
42
|
NetworkError: () => NetworkError,
|
|
41
43
|
SignerDocumentsResource: () => SignerDocumentsResource,
|
|
42
44
|
SignerResource: () => SignerResource,
|
|
@@ -55,16 +57,16 @@ var import_axios2 = __toESM(require("axios"));
|
|
|
55
57
|
|
|
56
58
|
// src/errors.ts
|
|
57
59
|
var AssinafyError = class extends Error {
|
|
60
|
+
context;
|
|
58
61
|
constructor(message, context = {}, options) {
|
|
59
|
-
super(message);
|
|
62
|
+
super(message, options);
|
|
60
63
|
this.name = "AssinafyError";
|
|
61
64
|
this.context = context;
|
|
62
|
-
if (options?.cause !== void 0) {
|
|
63
|
-
this.cause = options.cause;
|
|
64
|
-
}
|
|
65
65
|
}
|
|
66
66
|
};
|
|
67
67
|
var ApiError = class _ApiError extends AssinafyError {
|
|
68
|
+
statusCode;
|
|
69
|
+
responseData;
|
|
68
70
|
constructor(message, statusCode, responseData = null, options) {
|
|
69
71
|
super(message, { statusCode, responseData }, options);
|
|
70
72
|
this.name = "ApiError";
|
|
@@ -80,6 +82,7 @@ var ApiError = class _ApiError extends AssinafyError {
|
|
|
80
82
|
}
|
|
81
83
|
};
|
|
82
84
|
var ValidationError = class extends AssinafyError {
|
|
85
|
+
errors;
|
|
83
86
|
constructor(message = "Validation failed", errors = {}) {
|
|
84
87
|
super(message, { errors });
|
|
85
88
|
this.name = "ValidationError";
|
|
@@ -105,6 +108,23 @@ function handleAssinafyResponse(response) {
|
|
|
105
108
|
}
|
|
106
109
|
return response;
|
|
107
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
|
+
}
|
|
108
128
|
function toSdkError(error, fallbackMessage) {
|
|
109
129
|
if (error instanceof AssinafyError) {
|
|
110
130
|
return error;
|
|
@@ -112,14 +132,15 @@ function toSdkError(error, fallbackMessage) {
|
|
|
112
132
|
if (import_axios.default.isAxiosError(error)) {
|
|
113
133
|
const status = error.response?.status;
|
|
114
134
|
if (status) {
|
|
115
|
-
|
|
135
|
+
const body = decodeBinaryErrorBody(error.response?.data ?? null);
|
|
136
|
+
return ApiError.fromResponse(status, body ?? null);
|
|
116
137
|
}
|
|
117
138
|
return new NetworkError(`${fallbackMessage}: ${error.message}`, { cause: error });
|
|
118
139
|
}
|
|
119
140
|
if (error instanceof Error) {
|
|
120
141
|
return new AssinafyError(`${fallbackMessage}: ${error.message}`, {}, { cause: error });
|
|
121
142
|
}
|
|
122
|
-
return new AssinafyError(fallbackMessage, { cause: error });
|
|
143
|
+
return new AssinafyError(fallbackMessage, {}, { cause: error });
|
|
123
144
|
}
|
|
124
145
|
function createNoopLogger() {
|
|
125
146
|
return {
|
|
@@ -138,12 +159,101 @@ function cleanParams(params) {
|
|
|
138
159
|
}
|
|
139
160
|
return out;
|
|
140
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
|
+
}
|
|
141
170
|
|
|
142
|
-
// src/
|
|
171
|
+
// src/support/retry.ts
|
|
172
|
+
function header(headers, name) {
|
|
173
|
+
if (!headers) return void 0;
|
|
174
|
+
const lower = name.toLowerCase();
|
|
175
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
176
|
+
if (key.toLowerCase() === lower && value != null) {
|
|
177
|
+
return Array.isArray(value) ? String(value[0]) : String(value);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return void 0;
|
|
181
|
+
}
|
|
182
|
+
function retryDelayFromHeaders(headers) {
|
|
183
|
+
const retryAfter = header(headers, "retry-after");
|
|
184
|
+
if (retryAfter !== void 0) {
|
|
185
|
+
const seconds = Number(retryAfter);
|
|
186
|
+
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
|
|
187
|
+
const date = Date.parse(retryAfter);
|
|
188
|
+
if (!Number.isNaN(date)) return Math.max(0, date - Date.now());
|
|
189
|
+
}
|
|
190
|
+
const reset = header(headers, "x-rate-limit-reset");
|
|
191
|
+
if (reset !== void 0) {
|
|
192
|
+
const seconds = Number(reset);
|
|
193
|
+
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
|
|
194
|
+
}
|
|
195
|
+
return void 0;
|
|
196
|
+
}
|
|
197
|
+
function backoffMs(attempt) {
|
|
198
|
+
return Math.min(1e3 * 2 ** Math.max(0, attempt - 1), 8e3);
|
|
199
|
+
}
|
|
200
|
+
function nextRetryDelayMs(headers, attempt, maxDelayMs = 3e4) {
|
|
201
|
+
const hinted = retryDelayFromHeaders(headers);
|
|
202
|
+
if (hinted !== void 0) return Math.min(hinted, maxDelayMs);
|
|
203
|
+
return Math.min(backoffMs(attempt), maxDelayMs);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// src/resources/upload.ts
|
|
143
207
|
var import_node_fs = require("fs");
|
|
144
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
|
+
}
|
|
145
254
|
|
|
146
255
|
// src/resources/base.ts
|
|
256
|
+
var MULTIPART_CONTENT_TYPE = "multipart/form-data";
|
|
147
257
|
var BaseResource = class {
|
|
148
258
|
constructor(http, defaultAccountId, logger = createNoopLogger()) {
|
|
149
259
|
this.http = http;
|
|
@@ -205,6 +315,35 @@ var BaseResource = class {
|
|
|
205
315
|
throw toSdkError(err, label);
|
|
206
316
|
}
|
|
207
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
|
+
}
|
|
208
347
|
/** Execute a paginated list call and attach meta from `X-Pagination-*` headers. */
|
|
209
348
|
async callList(label, request) {
|
|
210
349
|
try {
|
|
@@ -247,7 +386,6 @@ function toInt(value) {
|
|
|
247
386
|
}
|
|
248
387
|
|
|
249
388
|
// src/resources/documents.ts
|
|
250
|
-
var MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
|
|
251
389
|
var READY_STATUSES = /* @__PURE__ */ new Set([
|
|
252
390
|
"metadata_ready",
|
|
253
391
|
"pending_signature",
|
|
@@ -261,43 +399,153 @@ var FAILED_STATUSES = /* @__PURE__ */ new Set([
|
|
|
261
399
|
]);
|
|
262
400
|
var DocumentResource = class extends BaseResource {
|
|
263
401
|
/**
|
|
264
|
-
* 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.
|
|
265
426
|
*
|
|
266
427
|
* @example
|
|
267
428
|
* ```ts
|
|
268
429
|
* await client.documents.upload({ filePath: './contract.pdf' });
|
|
269
|
-
* await client.documents.upload(
|
|
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'
|
|
270
435
|
* ```
|
|
271
436
|
*/
|
|
272
437
|
async upload(source, options = {}) {
|
|
273
|
-
const { buffer, fileName } = await loadSource(source);
|
|
274
|
-
validateUpload(buffer, fileName);
|
|
275
438
|
const accountId = this.accountId(options.accountId);
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
439
|
+
const formOptions = {};
|
|
440
|
+
if (options.name !== void 0) formOptions.name = options.name;
|
|
441
|
+
if (options.metadata !== void 0) formOptions.metadata = options.metadata;
|
|
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
|
+
}
|
|
283
450
|
);
|
|
284
|
-
if (!document?.id) {
|
|
285
|
-
throw new ValidationError("Upload succeeded but no document ID was returned", {
|
|
286
|
-
response: document
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
451
|
this.logger.info("Document uploaded", { documentId: document.id });
|
|
290
452
|
return document;
|
|
291
453
|
}
|
|
292
454
|
/**
|
|
293
455
|
* List workspace documents. Pagination info (if any) is attached in `meta`.
|
|
294
|
-
* Supports `status`, `method`, `tags`, `search`, `sort`, `page`, `
|
|
456
|
+
* Supports `status`, `method`, `tags`, `search`, `sort`, `page`, `per-page`.
|
|
295
457
|
*/
|
|
296
458
|
async list(params = {}, accountId) {
|
|
297
459
|
const id = this.accountId(accountId);
|
|
298
460
|
return this.callList(
|
|
299
461
|
"Failed to list documents",
|
|
300
|
-
() => this.http.get(`/accounts/${id}/documents`, { 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 })
|
|
301
549
|
);
|
|
302
550
|
}
|
|
303
551
|
/** Get document details. */
|
|
@@ -331,6 +579,9 @@ var DocumentResource = class extends BaseResource {
|
|
|
331
579
|
}
|
|
332
580
|
} catch (err) {
|
|
333
581
|
if (err instanceof ValidationError) throw err;
|
|
582
|
+
if (err instanceof ApiError && err.statusCode < 500 && err.statusCode !== 429) {
|
|
583
|
+
throw err;
|
|
584
|
+
}
|
|
334
585
|
this.logger.warn("Error checking document status", {
|
|
335
586
|
error: err instanceof Error ? err.message : String(err)
|
|
336
587
|
});
|
|
@@ -449,7 +700,12 @@ var DocumentResource = class extends BaseResource {
|
|
|
449
700
|
() => this.http.post(`/accounts/${accId}/templates/${tmplId}/documents`, body)
|
|
450
701
|
);
|
|
451
702
|
}
|
|
452
|
-
/**
|
|
703
|
+
/**
|
|
704
|
+
* Estimate the credit cost of creating a document from a template.
|
|
705
|
+
*
|
|
706
|
+
* @returns an {@link ICostEstimate}: `total_credits`, balances, and a
|
|
707
|
+
* per-line `breakdown` of what the operation would consume.
|
|
708
|
+
*/
|
|
453
709
|
async estimateCostFromTemplate(templateId, signers, accountId) {
|
|
454
710
|
const tmplId = this.requireId(templateId, "Template ID");
|
|
455
711
|
const accId = this.accountId(accountId);
|
|
@@ -518,43 +774,6 @@ var DocumentResource = class extends BaseResource {
|
|
|
518
774
|
return { signed, total, pending, percentage };
|
|
519
775
|
}
|
|
520
776
|
};
|
|
521
|
-
async function loadSource(source) {
|
|
522
|
-
if ("buffer" in source) {
|
|
523
|
-
if (!source.fileName) {
|
|
524
|
-
throw new ValidationError("fileName is required when uploading a Buffer");
|
|
525
|
-
}
|
|
526
|
-
return { buffer: source.buffer, fileName: source.fileName };
|
|
527
|
-
}
|
|
528
|
-
if (!source.filePath) {
|
|
529
|
-
throw new ValidationError("filePath is required");
|
|
530
|
-
}
|
|
531
|
-
const buffer = await import_node_fs.promises.readFile(source.filePath);
|
|
532
|
-
return { buffer, fileName: source.fileName ?? import_node_path.default.basename(source.filePath) };
|
|
533
|
-
}
|
|
534
|
-
function validateUpload(buffer, fileName) {
|
|
535
|
-
if (!buffer || buffer.byteLength === 0) {
|
|
536
|
-
throw new ValidationError("File buffer is empty", { fileName });
|
|
537
|
-
}
|
|
538
|
-
if (!fileName.toLowerCase().endsWith(".pdf")) {
|
|
539
|
-
throw new ValidationError("Only PDF files are supported", { fileName });
|
|
540
|
-
}
|
|
541
|
-
if (buffer.byteLength > MAX_UPLOAD_BYTES) {
|
|
542
|
-
throw new ValidationError("File size exceeds maximum allowed (25MB)", {
|
|
543
|
-
fileSize: buffer.byteLength,
|
|
544
|
-
maxSize: MAX_UPLOAD_BYTES
|
|
545
|
-
});
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
function buildUploadForm(buffer, fileName, metadata) {
|
|
549
|
-
const form = new FormData();
|
|
550
|
-
const view = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
551
|
-
form.append("file", new Blob([view], { type: "application/pdf" }), fileName);
|
|
552
|
-
form.append("name", fileName);
|
|
553
|
-
if (metadata) {
|
|
554
|
-
form.append("metadata", JSON.stringify(metadata));
|
|
555
|
-
}
|
|
556
|
-
return form;
|
|
557
|
-
}
|
|
558
777
|
function sleep(ms) {
|
|
559
778
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
560
779
|
}
|
|
@@ -593,7 +812,8 @@ var SignerResource = class extends BaseResource {
|
|
|
593
812
|
() => this.http.post(`/accounts/${id}/signers`, normaliseSignerPayload(payload))
|
|
594
813
|
);
|
|
595
814
|
} catch (err) {
|
|
596
|
-
|
|
815
|
+
const isDuplicateStatus = err instanceof ApiError && (err.statusCode === 409 || err.statusCode === 400);
|
|
816
|
+
if (isDuplicateStatus && payload.email) {
|
|
597
817
|
const duplicate = await this.findByEmail(payload.email, id);
|
|
598
818
|
if (duplicate) {
|
|
599
819
|
this.logger.info("Signer already exists, using existing signer", {
|
|
@@ -614,12 +834,12 @@ var SignerResource = class extends BaseResource {
|
|
|
614
834
|
() => this.http.get(`/accounts/${id}/signers/${sid}`)
|
|
615
835
|
);
|
|
616
836
|
}
|
|
617
|
-
/** List signers for the workspace (supports `page`, `
|
|
837
|
+
/** List signers for the workspace (supports `page`, `per-page`, `search`, `sort`). */
|
|
618
838
|
async list(params = {}, accountId) {
|
|
619
839
|
const id = this.accountId(accountId);
|
|
620
840
|
return this.callList(
|
|
621
841
|
"Failed to list signers",
|
|
622
|
-
() => this.http.get(`/accounts/${id}/signers`, { params:
|
|
842
|
+
() => this.http.get(`/accounts/${id}/signers`, { params: cleanListParams(params) })
|
|
623
843
|
);
|
|
624
844
|
}
|
|
625
845
|
/** Update a signer. Fails if the signer has active assignments. */
|
|
@@ -640,11 +860,28 @@ var SignerResource = class extends BaseResource {
|
|
|
640
860
|
() => this.http.delete(`/accounts/${id}/signers/${sid}`)
|
|
641
861
|
);
|
|
642
862
|
}
|
|
643
|
-
/**
|
|
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
|
+
*/
|
|
644
881
|
async findByEmail(email, accountId) {
|
|
645
882
|
this.assertEmail(email);
|
|
646
883
|
try {
|
|
647
|
-
const { data } = await this.list({ search: email,
|
|
884
|
+
const { data } = await this.list({ search: email, "per-page": 50 }, accountId);
|
|
648
885
|
const lower = email.toLowerCase();
|
|
649
886
|
return data.find((s) => (s.email ?? "").toLowerCase() === lower) ?? null;
|
|
650
887
|
} catch (err) {
|
|
@@ -756,6 +993,58 @@ function normaliseSignerRef(ref, options) {
|
|
|
756
993
|
throw new ValidationError("Invalid signer reference", { ref });
|
|
757
994
|
}
|
|
758
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
|
+
}
|
|
759
1048
|
/** Create a signing assignment for a document. */
|
|
760
1049
|
async create(documentId, payload) {
|
|
761
1050
|
const docId = this.requireId(documentId, "Document ID");
|
|
@@ -770,7 +1059,15 @@ var AssignmentResource = class extends BaseResource {
|
|
|
770
1059
|
() => this.http.post(`/documents/${docId}/assignments`, body)
|
|
771
1060
|
);
|
|
772
1061
|
}
|
|
773
|
-
/**
|
|
1062
|
+
/**
|
|
1063
|
+
* Estimate the cost (in credits/documents) of creating the assignment.
|
|
1064
|
+
*
|
|
1065
|
+
* Signer entries may omit `id` and supply only `verification_method` /
|
|
1066
|
+
* `notification_methods` when only the channel mix matters for the estimate.
|
|
1067
|
+
*
|
|
1068
|
+
* @returns an {@link ICostEstimate} with `total_credits`, balances, and a
|
|
1069
|
+
* line-item `breakdown`.
|
|
1070
|
+
*/
|
|
774
1071
|
async estimateCost(documentId, payload) {
|
|
775
1072
|
const docId = this.requireId(documentId, "Document ID");
|
|
776
1073
|
return this.call(
|
|
@@ -805,7 +1102,11 @@ var AssignmentResource = class extends BaseResource {
|
|
|
805
1102
|
() => this.http.put(`/documents/${docId}/assignments/${asgId}/signers/${sid}/resend`)
|
|
806
1103
|
);
|
|
807
1104
|
}
|
|
808
|
-
/**
|
|
1105
|
+
/**
|
|
1106
|
+
* Estimate the cost of resending a signer notification.
|
|
1107
|
+
*
|
|
1108
|
+
* @returns an {@link IResendCostEstimate} (`total`, `breakdown`, balances).
|
|
1109
|
+
*/
|
|
809
1110
|
async estimateResendCost(documentId, assignmentId, signerId) {
|
|
810
1111
|
const docId = this.requireId(documentId, "Document ID");
|
|
811
1112
|
const asgId = this.requireId(assignmentId, "Assignment ID");
|
|
@@ -829,26 +1130,10 @@ var AssignmentResource = class extends BaseResource {
|
|
|
829
1130
|
() => this.http.get(`/documents/${docId}/assignments/${asgId}/whatsapp-notifications`)
|
|
830
1131
|
);
|
|
831
1132
|
}
|
|
832
|
-
/**
|
|
833
|
-
* Cancel a signature request. This endpoint is not listed in the public
|
|
834
|
-
* Swagger but is exposed by the platform.
|
|
835
|
-
*/
|
|
836
|
-
async cancel(documentId, reason, accountId) {
|
|
837
|
-
const docId = this.requireId(documentId, "Document ID");
|
|
838
|
-
const accId = this.accountId(accountId);
|
|
839
|
-
this.logger.info("Cancelling signature request", { documentId: docId, reason });
|
|
840
|
-
return this.call(
|
|
841
|
-
"Failed to cancel signature request",
|
|
842
|
-
() => this.http.post(
|
|
843
|
-
`/accounts/${accId}/signature-requests/${docId}/cancel`,
|
|
844
|
-
{ document_id: docId, reason }
|
|
845
|
-
)
|
|
846
|
-
);
|
|
847
|
-
}
|
|
848
1133
|
};
|
|
849
1134
|
|
|
850
1135
|
// src/resources/webhooks.ts
|
|
851
|
-
var
|
|
1136
|
+
var DEFAULT_WEBHOOK_EVENTS = [
|
|
852
1137
|
"document_ready",
|
|
853
1138
|
"document_prepared",
|
|
854
1139
|
"signer_signed_document",
|
|
@@ -856,7 +1141,21 @@ var DEFAULT_EVENTS = [
|
|
|
856
1141
|
"document_processing_failed"
|
|
857
1142
|
];
|
|
858
1143
|
var WebhookResource = class extends BaseResource {
|
|
859
|
-
/**
|
|
1144
|
+
/**
|
|
1145
|
+
* Register (or replace) the workspace's single webhook subscription
|
|
1146
|
+
* (`PUT /accounts/{id}/webhooks/subscriptions`). There is exactly one
|
|
1147
|
+
* subscription per workspace, keyed by URL.
|
|
1148
|
+
*
|
|
1149
|
+
* When `events` is omitted or empty, {@link DEFAULT_WEBHOOK_EVENTS} is used
|
|
1150
|
+
* (`document_ready`, `document_prepared`, `signer_signed_document`,
|
|
1151
|
+
* `signer_rejected_document`, `document_processing_failed`).
|
|
1152
|
+
*
|
|
1153
|
+
* @example
|
|
1154
|
+
* ```ts
|
|
1155
|
+
* await client.webhooks.register({ url: 'https://example.com/hook', email: 'ops@example.com' });
|
|
1156
|
+
* // → { url, email, events: [...], is_active: true, updated_at: '2026-…' }
|
|
1157
|
+
* ```
|
|
1158
|
+
*/
|
|
860
1159
|
async register(payload, accountId) {
|
|
861
1160
|
if (!payload.url) throw new ValidationError("Webhook URL is required");
|
|
862
1161
|
if (!payload.email) throw new ValidationError("Webhook email is required");
|
|
@@ -864,7 +1163,7 @@ var WebhookResource = class extends BaseResource {
|
|
|
864
1163
|
const body = {
|
|
865
1164
|
url: payload.url,
|
|
866
1165
|
email: payload.email,
|
|
867
|
-
events: payload.events && payload.events.length > 0 ? payload.events :
|
|
1166
|
+
events: payload.events && payload.events.length > 0 ? payload.events : DEFAULT_WEBHOOK_EVENTS,
|
|
868
1167
|
is_active: payload.is_active ?? true
|
|
869
1168
|
};
|
|
870
1169
|
this.logger.info("Registering webhook", { url: payload.url });
|
|
@@ -881,16 +1180,14 @@ var WebhookResource = class extends BaseResource {
|
|
|
881
1180
|
() => this.http.get(`/accounts/${id}/webhooks/subscriptions`)
|
|
882
1181
|
);
|
|
883
1182
|
}
|
|
884
|
-
/**
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
}
|
|
893
|
-
/** 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
|
+
*/
|
|
894
1191
|
async inactivate(accountId) {
|
|
895
1192
|
const id = this.accountId(accountId);
|
|
896
1193
|
this.logger.info("Inactivating webhook subscription");
|
|
@@ -912,7 +1209,7 @@ var WebhookResource = class extends BaseResource {
|
|
|
912
1209
|
return this.callList(
|
|
913
1210
|
"Failed to list webhook dispatches",
|
|
914
1211
|
() => this.http.get(`/accounts/${id}/webhooks`, {
|
|
915
|
-
params:
|
|
1212
|
+
params: cleanListParams(params)
|
|
916
1213
|
})
|
|
917
1214
|
);
|
|
918
1215
|
}
|
|
@@ -929,20 +1226,56 @@ var WebhookResource = class extends BaseResource {
|
|
|
929
1226
|
|
|
930
1227
|
// src/resources/templates.ts
|
|
931
1228
|
var TemplateResource = class extends BaseResource {
|
|
1229
|
+
/**
|
|
1230
|
+
* Create a template by uploading a PDF (`POST /accounts/{id}/templates`).
|
|
1231
|
+
*
|
|
1232
|
+
* The template is created in `Uploaded` status and transitions to `Ready`
|
|
1233
|
+
* once the platform finishes processing its pages. Configure roles/fields
|
|
1234
|
+
* afterwards in the Assinafy editor.
|
|
1235
|
+
*
|
|
1236
|
+
* @example
|
|
1237
|
+
* ```ts
|
|
1238
|
+
* const tmpl = await client.templates.create(
|
|
1239
|
+
* { filePath: './nda.pdf' },
|
|
1240
|
+
* { name: 'NDA template' },
|
|
1241
|
+
* );
|
|
1242
|
+
* // → { resource: 'template', id, name, status: 'Uploaded',
|
|
1243
|
+
* // roles: [{ id, name: 'TemplateEditor', assignment_type: 'Editor' }],
|
|
1244
|
+
* // pages: [], tags: [], created_at, updated_at }
|
|
1245
|
+
* ```
|
|
1246
|
+
*/
|
|
1247
|
+
async create(source, options = {}) {
|
|
1248
|
+
const id = this.accountId(options.accountId);
|
|
1249
|
+
const formOptions = {};
|
|
1250
|
+
if (options.name !== void 0) formOptions.name = options.name;
|
|
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
|
+
}
|
|
1259
|
+
);
|
|
1260
|
+
this.logger.info("Template created", { templateId: template.id });
|
|
1261
|
+
return template;
|
|
1262
|
+
}
|
|
932
1263
|
/** List templates for the workspace. */
|
|
933
1264
|
async list(params = {}, accountId) {
|
|
934
1265
|
const id = this.accountId(accountId);
|
|
935
1266
|
return this.callList(
|
|
936
1267
|
"Failed to list templates",
|
|
937
|
-
() => this.http.get(`/accounts/${id}/templates`, { params:
|
|
1268
|
+
() => this.http.get(`/accounts/${id}/templates`, { params: cleanListParams(params) })
|
|
938
1269
|
);
|
|
939
1270
|
}
|
|
940
1271
|
/**
|
|
941
|
-
* Get a template by ID.
|
|
1272
|
+
* Get a template by ID (`GET /accounts/{id}/templates/{template_id}`).
|
|
942
1273
|
*
|
|
943
|
-
*
|
|
944
|
-
* `
|
|
945
|
-
*
|
|
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.
|
|
946
1279
|
*/
|
|
947
1280
|
async get(templateId, accountId) {
|
|
948
1281
|
const id = this.accountId(accountId);
|
|
@@ -953,9 +1286,40 @@ var TemplateResource = class extends BaseResource {
|
|
|
953
1286
|
);
|
|
954
1287
|
}
|
|
955
1288
|
/**
|
|
956
|
-
* `
|
|
957
|
-
*
|
|
958
|
-
*
|
|
1289
|
+
* Update a template's `name` and/or default `message`
|
|
1290
|
+
* (`PUT /accounts/{id}/templates/{template_id}`). Returns the updated template.
|
|
1291
|
+
*
|
|
1292
|
+
* @example
|
|
1293
|
+
* ```ts
|
|
1294
|
+
* await client.templates.update(templateId, { name: 'NDA v2', message: 'Please sign' });
|
|
1295
|
+
* ```
|
|
1296
|
+
*/
|
|
1297
|
+
async update(templateId, payload, accountId) {
|
|
1298
|
+
const id = this.accountId(accountId);
|
|
1299
|
+
const tmplId = this.requireId(templateId, "Template ID");
|
|
1300
|
+
return this.call(
|
|
1301
|
+
"Failed to update template",
|
|
1302
|
+
() => this.http.put(
|
|
1303
|
+
`/accounts/${id}/templates/${tmplId}`,
|
|
1304
|
+
cleanParams(payload)
|
|
1305
|
+
)
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
/** Delete a template (`DELETE /accounts/{id}/templates/{template_id}`). */
|
|
1309
|
+
async delete(templateId, accountId) {
|
|
1310
|
+
const id = this.accountId(accountId);
|
|
1311
|
+
const tmplId = this.requireId(templateId, "Template ID");
|
|
1312
|
+
return this.callVoid(
|
|
1313
|
+
"Failed to delete template",
|
|
1314
|
+
() => this.http.delete(`/accounts/${id}/templates/${tmplId}`)
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* Download a template page as a JPEG
|
|
1319
|
+
* (`GET /accounts/{id}/templates/{template_id}/pages/{page_id}/download`).
|
|
1320
|
+
*
|
|
1321
|
+
* Used by template editors to render page thumbnails on the client. The
|
|
1322
|
+
* matching `download_url` is also returned on each `template.pages[]` entry.
|
|
959
1323
|
*/
|
|
960
1324
|
async downloadPage(templateId, pageId, accountId) {
|
|
961
1325
|
const id = this.accountId(accountId);
|
|
@@ -979,7 +1343,7 @@ var TagResource = class extends BaseResource {
|
|
|
979
1343
|
return this.call(
|
|
980
1344
|
"Failed to list tags",
|
|
981
1345
|
() => this.http.get(`/accounts/${id}/tags`, {
|
|
982
|
-
params:
|
|
1346
|
+
params: cleanListParams(params)
|
|
983
1347
|
})
|
|
984
1348
|
);
|
|
985
1349
|
}
|
|
@@ -1121,7 +1485,7 @@ var FieldsResource = class extends BaseResource {
|
|
|
1121
1485
|
return this.call(
|
|
1122
1486
|
"Failed to list field definitions",
|
|
1123
1487
|
() => this.http.get(`/accounts/${id}/fields`, {
|
|
1124
|
-
params:
|
|
1488
|
+
params: cleanListParams(params)
|
|
1125
1489
|
})
|
|
1126
1490
|
);
|
|
1127
1491
|
}
|
|
@@ -1205,7 +1569,44 @@ var SignerDocumentsResource = class extends BaseResource {
|
|
|
1205
1569
|
return this.callList(
|
|
1206
1570
|
"Failed to list signer documents",
|
|
1207
1571
|
() => this.http.get(`/signers/${sid}/documents`, {
|
|
1208
|
-
params: { "signer-access-code": code, ...
|
|
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 })
|
|
1209
1610
|
})
|
|
1210
1611
|
);
|
|
1211
1612
|
}
|
|
@@ -1323,7 +1724,12 @@ var SignerDocumentsResource = class extends BaseResource {
|
|
|
1323
1724
|
})
|
|
1324
1725
|
);
|
|
1325
1726
|
}
|
|
1326
|
-
/**
|
|
1727
|
+
/**
|
|
1728
|
+
* `GET /sign?signer-access-code=…` — fetch the assignment as the signer sees it.
|
|
1729
|
+
*
|
|
1730
|
+
* @param hasAcceptedTerms maps to the `has_accepted_terms` query param
|
|
1731
|
+
* (server default `false`); pass `true` once the signer has accepted terms.
|
|
1732
|
+
*/
|
|
1327
1733
|
async getAssignment(signerAccessCode, hasAcceptedTerms) {
|
|
1328
1734
|
const code = this.requireId(signerAccessCode, "signer-access-code");
|
|
1329
1735
|
return this.call(
|
|
@@ -1353,8 +1759,8 @@ var SignerDocumentsResource = class extends BaseResource {
|
|
|
1353
1759
|
}
|
|
1354
1760
|
/**
|
|
1355
1761
|
* `PUT /documents/{documentId}/assignments/{assignmentId}/reject?signer-access-code=…`
|
|
1356
|
-
* — signer-side decline. (
|
|
1357
|
-
* workspace
|
|
1762
|
+
* — signer-side decline. (The workspace-side equivalent is to delete the
|
|
1763
|
+
* document via `documents.delete`; there is no workspace "cancel" endpoint.)
|
|
1358
1764
|
*/
|
|
1359
1765
|
async decline(documentId, assignmentId, signerAccessCode, declineReason) {
|
|
1360
1766
|
const did = this.requireId(documentId, "Document ID");
|
|
@@ -1420,6 +1826,21 @@ var WebhookVerifier = class {
|
|
|
1420
1826
|
// src/client.ts
|
|
1421
1827
|
var DEFAULT_BASE_URL = "https://api.assinafy.com.br/v1";
|
|
1422
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;
|
|
1423
1844
|
constructor(options) {
|
|
1424
1845
|
if (!options.apiKey && !options.token) {
|
|
1425
1846
|
throw new ValidationError(
|
|
@@ -1445,6 +1866,10 @@ var AssinafyClient = class _AssinafyClient {
|
|
|
1445
1866
|
timeout: options.timeout ?? 3e4,
|
|
1446
1867
|
headers
|
|
1447
1868
|
});
|
|
1869
|
+
const maxRetries = options.maxRetries ?? 2;
|
|
1870
|
+
if (maxRetries > 0) {
|
|
1871
|
+
installRateLimitRetry(this.axiosInstance, maxRetries, this.logger);
|
|
1872
|
+
}
|
|
1448
1873
|
this.documents = new DocumentResource(this.axiosInstance, this.defaultAccountId, this.logger);
|
|
1449
1874
|
this.signers = new SignerResource(this.axiosInstance, this.defaultAccountId, this.logger);
|
|
1450
1875
|
this.workspaces = new WorkspaceResource(this.axiosInstance, void 0, this.logger);
|
|
@@ -1483,6 +1908,7 @@ var AssinafyClient = class _AssinafyClient {
|
|
|
1483
1908
|
if (baseUrl !== void 0) opts.baseUrl = baseUrl;
|
|
1484
1909
|
if (webhookSecret !== void 0) opts.webhookSecret = webhookSecret;
|
|
1485
1910
|
if (config.timeout !== void 0) opts.timeout = config.timeout;
|
|
1911
|
+
if (config.maxRetries !== void 0) opts.maxRetries = config.maxRetries;
|
|
1486
1912
|
if (config.logger !== void 0) opts.logger = config.logger;
|
|
1487
1913
|
return new _AssinafyClient(opts);
|
|
1488
1914
|
}
|
|
@@ -1542,6 +1968,27 @@ var AssinafyClient = class _AssinafyClient {
|
|
|
1542
1968
|
function normaliseBaseUrl(raw) {
|
|
1543
1969
|
return raw.endsWith("/") ? raw.slice(0, -1) : raw;
|
|
1544
1970
|
}
|
|
1971
|
+
function installRateLimitRetry(http, maxRetries, logger) {
|
|
1972
|
+
http.interceptors.response.use(
|
|
1973
|
+
(response) => response,
|
|
1974
|
+
async (error) => {
|
|
1975
|
+
if (!import_axios2.default.isAxiosError(error) || error.response?.status !== 429 || !error.config) {
|
|
1976
|
+
throw error;
|
|
1977
|
+
}
|
|
1978
|
+
const config = error.config;
|
|
1979
|
+
const attempt = (config._retryCount ?? 0) + 1;
|
|
1980
|
+
if (attempt > maxRetries) throw error;
|
|
1981
|
+
config._retryCount = attempt;
|
|
1982
|
+
const delayMs = nextRetryDelayMs(
|
|
1983
|
+
error.response.headers,
|
|
1984
|
+
attempt
|
|
1985
|
+
);
|
|
1986
|
+
logger.warn("Rate limited (429); retrying after delay", { attempt, maxRetries, delayMs });
|
|
1987
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1988
|
+
return http(config);
|
|
1989
|
+
}
|
|
1990
|
+
);
|
|
1991
|
+
}
|
|
1545
1992
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1546
1993
|
0 && (module.exports = {
|
|
1547
1994
|
ApiError,
|
|
@@ -1549,8 +1996,10 @@ function normaliseBaseUrl(raw) {
|
|
|
1549
1996
|
AssinafyClient,
|
|
1550
1997
|
AssinafyError,
|
|
1551
1998
|
AuthenticationResource,
|
|
1999
|
+
DEFAULT_WEBHOOK_EVENTS,
|
|
1552
2000
|
DocumentResource,
|
|
1553
2001
|
FieldsResource,
|
|
2002
|
+
MAX_UPLOAD_BYTES,
|
|
1554
2003
|
NetworkError,
|
|
1555
2004
|
SignerDocumentsResource,
|
|
1556
2005
|
SignerResource,
|