@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/README.md +32 -9
- package/dist/index.d.mts +368 -44
- package/dist/index.d.ts +368 -44
- package/dist/index.js +396 -102
- package/dist/index.mjs +395 -102
- package/package.json +18 -9
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
|
-
|
|
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(
|
|
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
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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`, `
|
|
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:
|
|
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
|
-
|
|
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`, `
|
|
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:
|
|
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
|
-
/**
|
|
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,
|
|
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
|
-
/**
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
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:
|
|
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
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
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:
|
|
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
|
-
*
|
|
984
|
-
* (
|
|
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:
|
|
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:
|
|
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, ...
|
|
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,
|