@assinafy/sdk 2.1.1 → 2.2.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/CHANGELOG.md +99 -1
- package/README.md +601 -117
- package/SECURITY.md +4 -2
- package/dist/index.d.mts +697 -316
- package/dist/index.d.ts +697 -316
- package/dist/index.js +3102 -1938
- package/dist/index.mjs +3100 -1938
- package/docs/API_COVERAGE.md +28 -30
- package/docs/COMPATIBILITY.md +279 -179
- package/docs/RELEASING.md +42 -11
- package/package.json +10 -9
package/README.md
CHANGED
|
@@ -6,18 +6,50 @@ Covers all 89 operations in the current official OpenAPI document: accounts,
|
|
|
6
6
|
authentication, users, documents, assignments, signers, signer-side flows,
|
|
7
7
|
templates, tags, fields, webhooks, branding, statistics, and the high-level
|
|
8
8
|
`uploadAndRequestSignatures` workflow. Five additional template-management
|
|
9
|
-
routes
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
[
|
|
13
|
-
|
|
9
|
+
routes used by existing integrations and two legacy browser URL helpers are
|
|
10
|
+
retained for compatibility.
|
|
11
|
+
|
|
12
|
+
See [API coverage](docs/API_COVERAGE.md) for the operation map and
|
|
13
|
+
[compatibility notes](docs/COMPATIBILITY.md) for deployment-specific request
|
|
14
|
+
and response variants.
|
|
15
|
+
|
|
16
|
+
## Contents
|
|
17
|
+
|
|
18
|
+
This document runs from setup to a complete signature workflow, then to
|
|
19
|
+
per-resource detail. Read it in order the first time; use it as a reference
|
|
20
|
+
afterwards.
|
|
21
|
+
|
|
22
|
+
**Getting set up** — [Requirements](#requirements) ·
|
|
23
|
+
[Installation](#installation) · [Quick start](#quick-start) ·
|
|
24
|
+
[Authentication](#authentication) · [Configuration](#configuration)
|
|
25
|
+
([rate limiting](#rate-limiting), [factories](#factories)) ·
|
|
26
|
+
[Endpoint coverage](#endpoint-coverage)
|
|
27
|
+
|
|
28
|
+
**The end-to-end flow** — [Document lifecycle](#document-lifecycle):
|
|
29
|
+
[upload](#1-upload-the-pdf) → [signers](#2-create-or-reuse-the-email-signers) →
|
|
30
|
+
[price and request signatures](#3-price-then-request-signatures) →
|
|
31
|
+
[the signer's side](#4-complete-the-email-signer-flow) →
|
|
32
|
+
[completion and artifacts](#5-observe-completion-and-download-artifacts)
|
|
33
|
+
|
|
34
|
+
**Per-resource detail** — [Resource reference](#resource-reference):
|
|
35
|
+
[documents](#documents) · [signers](#signers) · [assignments](#assignments) ·
|
|
36
|
+
[paid signing branches](#paid-signing-branches) · [templates](#templates) ·
|
|
37
|
+
[tags](#tags) · [workspaces](#workspaces) ·
|
|
38
|
+
[field definitions](#field-definitions) ·
|
|
39
|
+
[auth and API keys](#authentication--api-key-management) ·
|
|
40
|
+
[the current user](#authenticated-user) · [webhooks](#webhooks)
|
|
41
|
+
([verification](#webhook-verification)) ·
|
|
42
|
+
[signer-side endpoints](#signer-side-endpoints)
|
|
43
|
+
|
|
44
|
+
**Everything else** — [High-level helper](#high-level-helper) ·
|
|
45
|
+
[Errors](#errors) · [Development](#development) · [License](#license)
|
|
14
46
|
|
|
15
47
|
## Requirements
|
|
16
48
|
|
|
17
49
|
- Node.js 22+ for the built-in `FormData` / `Blob` APIs used by uploads. Packed
|
|
18
50
|
CJS and ESM imports are tested on 22 (maintenance LTS), 24 (active LTS), and
|
|
19
51
|
26 (Current); Node 20 reached end-of-life in April 2026 and is unsupported.
|
|
20
|
-
- or Bun 1.
|
|
52
|
+
- or Bun 1.4.0 (the version pinned for development and CI)
|
|
21
53
|
|
|
22
54
|
## Installation
|
|
23
55
|
|
|
@@ -39,23 +71,30 @@ The package is published to both [npmjs.com](https://www.npmjs.com/package/@assi
|
|
|
39
71
|
```ts
|
|
40
72
|
import { AssinafyClient } from '@assinafy/sdk';
|
|
41
73
|
|
|
74
|
+
const baseUrl = process.env.ASSINAFY_BASE_URL ?? 'https://api.assinafy.com.br/v1';
|
|
42
75
|
const client = new AssinafyClient({
|
|
43
76
|
apiKey: process.env.ASSINAFY_API_KEY!,
|
|
44
77
|
accountId: process.env.ASSINAFY_ACCOUNT_ID!,
|
|
78
|
+
baseUrl,
|
|
45
79
|
});
|
|
46
80
|
|
|
47
81
|
const result = await client.uploadAndRequestSignatures({
|
|
48
82
|
source: { filePath: './contract.pdf' },
|
|
49
83
|
signers: [
|
|
50
|
-
{ name: 'John Doe',
|
|
51
|
-
{ name: 'Jane Smith',
|
|
84
|
+
{ name: 'John Doe', email: 'john@example.com' },
|
|
85
|
+
{ name: 'Jane Smith', email: 'jane@example.com' },
|
|
52
86
|
],
|
|
53
87
|
message: 'Please sign this contract',
|
|
54
88
|
});
|
|
55
89
|
|
|
56
90
|
console.log('Document ID:', result.document.id);
|
|
91
|
+
console.log('Assignment ID:', result.assignment.id);
|
|
57
92
|
```
|
|
58
93
|
|
|
94
|
+
This path uses email verification and notification for every signer. WhatsApp
|
|
95
|
+
and ICP-Brasil certificate signing have separate prerequisites and costs; see
|
|
96
|
+
[Paid signing branches](#paid-signing-branches) before enabling either one.
|
|
97
|
+
|
|
59
98
|
## Authentication
|
|
60
99
|
|
|
61
100
|
The API supports two authentication methods. Prefer `apiKey` — it maps to the `X-Api-Key` header recommended by Assinafy for backend services.
|
|
@@ -85,6 +124,11 @@ await publicClient.signerDocuments.self(signerAccessCode);
|
|
|
85
124
|
Protected methods still require `apiKey` or `token`; the API returns its normal
|
|
86
125
|
`401` response if one is called without credentials.
|
|
87
126
|
|
|
127
|
+
Every SDK transport, including public and signer-access-code requests, sends
|
|
128
|
+
`User-Agent: Assinafy-Typescript-SDK/v<VERSION>`, where `<VERSION>` is the
|
|
129
|
+
installed package version. The exact value is also exported as
|
|
130
|
+
`SDK_USER_AGENT` for custom transport checks and observability rules.
|
|
131
|
+
|
|
88
132
|
## Configuration
|
|
89
133
|
|
|
90
134
|
| Option | Type | Default | Description |
|
|
@@ -92,7 +136,7 @@ Protected methods still require `apiKey` or `token`; the API returns its normal
|
|
|
92
136
|
| `apiKey` | string | — | Preferred credential (sent as `X-Api-Key`). |
|
|
93
137
|
| `token` | string | — | Access token (sent as `Authorization: Bearer`). |
|
|
94
138
|
| `accountId` | string | — | Default workspace/account ID. |
|
|
95
|
-
| `baseUrl` | string | `https://api.assinafy.com.br/v1` |
|
|
139
|
+
| `baseUrl` | string | `https://api.assinafy.com.br/v1` | Absolute API base without credentials, query, or fragment. Must be `https` unless the host is loopback. |
|
|
96
140
|
| `webhookSecret` | string | — | Opt-in HMAC secret used by `WebhookVerifier`; see its [contract caveat](docs/COMPATIBILITY.md#webhook-signature-verification-is-not-in-the-openapi-contract). |
|
|
97
141
|
| `timeout` | number | `30000` | Request timeout in milliseconds. |
|
|
98
142
|
| `maxRetries` | number | `2` | Auto-retries eligible HTTP 429 responses, honoring `Retry-After`. `0` disables. |
|
|
@@ -102,9 +146,12 @@ Protected methods still require `apiKey` or `token`; the API returns its normal
|
|
|
102
146
|
|
|
103
147
|
On an HTTP `429`, the client automatically retries up to `maxRetries` times,
|
|
104
148
|
waiting for the server-provided `Retry-After` (or `X-Rate-Limit-Reset`) delay
|
|
105
|
-
before each attempt. Automatic replay is limited to `GET`, `HEAD`,
|
|
106
|
-
`
|
|
107
|
-
|
|
149
|
+
before each attempt. Automatic replay is limited to read-safe `GET`, `HEAD`,
|
|
150
|
+
`OPTIONS`, and `DELETE` requests. `GET /sign` is excluded because it records
|
|
151
|
+
that the signer viewed the assignment. Writes are not replayed by default.
|
|
152
|
+
A non-empty `Idempotency-Key` opts a custom request into SDK replay, but it is
|
|
153
|
+
not part of the current Assinafy OpenAPI contract: confirm that the target
|
|
154
|
+
route deduplicates that key server-side first. No other HTTP status is retried.
|
|
108
155
|
|
|
109
156
|
### Factories
|
|
110
157
|
|
|
@@ -122,8 +169,8 @@ const client = AssinafyClient.fromConfig({
|
|
|
122
169
|
## Endpoint coverage
|
|
123
170
|
|
|
124
171
|
All 89 operations documented at https://api.assinafy.com.br/v1/docs are
|
|
125
|
-
covered. The table below is the resource-level summary; the
|
|
126
|
-
|
|
172
|
+
covered. The table below is the resource-level summary; the detailed operation
|
|
173
|
+
ledger is in [docs/API_COVERAGE.md](docs/API_COVERAGE.md).
|
|
127
174
|
|
|
128
175
|
| Resource | Endpoints |
|
|
129
176
|
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -136,7 +183,7 @@ operation-by-operation ledger is in [docs/API_COVERAGE.md](docs/API_COVERAGE.md)
|
|
|
136
183
|
| `client.webhooks` | register, get, inactivate, listEventTypes, listDispatches, retryDispatch |
|
|
137
184
|
| `client.fields` | create, list, get, update, delete, validate, validateMultiple, listTypes |
|
|
138
185
|
| `client.auth` | getSocialLoginUrl, getSocialLoginCallbackUrl, login, socialLogin, linkSocialLogin, createApiKey, getApiKey, deleteApiKey, changePassword, requestPasswordReset, resetPassword |
|
|
139
|
-
| `client.users` | getCurrent, getStats
|
|
186
|
+
| `client.users` | getCurrent, getStats, getNotificationPreferences, updateNotificationPreferences |
|
|
140
187
|
| `client.signerDocuments` | getCurrent, list, **search**, download, signMultiple, declineMultiple, self, acceptTerms, verifyEmail, confirmData, uploadSignature, downloadSignature, getAssignment, sign, decline |
|
|
141
188
|
| `client.webhookVerifier` | verify, extractEvent, getEventType, getEventData |
|
|
142
189
|
|
|
@@ -148,7 +195,251 @@ inline in the generated declarations. Editors expose the reference on hover,
|
|
|
148
195
|
and declaration files ship with the package. The coverage ledger links those
|
|
149
196
|
typed methods back to each upstream operation without duplicating the schema.
|
|
150
197
|
|
|
151
|
-
##
|
|
198
|
+
## Document lifecycle
|
|
199
|
+
|
|
200
|
+
The normal integration has an account-owner phase, a signer phase, and a final
|
|
201
|
+
artifact phase. The example below keeps every signer on email and uses a
|
|
202
|
+
`virtual` assignment, so no page coordinates or paid notification channel are
|
|
203
|
+
required.
|
|
204
|
+
|
|
205
|
+
### 1. Upload the PDF
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
const uploaded = await client.documents.upload({ filePath: './contract.pdf' });
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
The official multipart body contains the `file` part. The SDK also supports a
|
|
212
|
+
display-name override (used as that file part's filename) and an optional JSON
|
|
213
|
+
`metadata` part for deployments that accept it. A successful response is
|
|
214
|
+
`IDocumentUploadResponse`:
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
{
|
|
218
|
+
resource?: string;
|
|
219
|
+
id: string;
|
|
220
|
+
account_id: string;
|
|
221
|
+
template_id: string | null;
|
|
222
|
+
name: string;
|
|
223
|
+
status: DocumentStatus;
|
|
224
|
+
assignment?: IAssignment | null;
|
|
225
|
+
artifacts: {
|
|
226
|
+
original: string;
|
|
227
|
+
certificated?: string;
|
|
228
|
+
'certificate-page'?: string;
|
|
229
|
+
pades?: string;
|
|
230
|
+
bundle?: string;
|
|
231
|
+
thumbnail?: string;
|
|
232
|
+
};
|
|
233
|
+
signing_url?: string;
|
|
234
|
+
pages: Array<{ id: string; number: number; height: number; width: number; download_url: string }>;
|
|
235
|
+
tags?: Array<{ id: string; name: string; color?: string | null }>;
|
|
236
|
+
created_at: string;
|
|
237
|
+
updated_at: string;
|
|
238
|
+
is_closed: boolean;
|
|
239
|
+
decline_reason: string | null;
|
|
240
|
+
declined_by: ISigner | null;
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
`DocumentStatus` covers `uploading`, `uploaded`, `metadata_processing`,
|
|
245
|
+
`metadata_ready`, `pending_signature`, `expired`, `certificating`,
|
|
246
|
+
`certificated`, `rejected_by_signer`, `rejected_by_user`, and `failed`.
|
|
247
|
+
|
|
248
|
+
Uploads must be PDFs, at most 25 MB and at most 2,000 pages. The SDK checks the
|
|
249
|
+
extension, size, and `%PDF-` header before sending. A new upload can have an
|
|
250
|
+
empty `pages` array until metadata processing finishes. Wait before creating a
|
|
251
|
+
`collect` assignment because its fields refer to rendered page IDs; a
|
|
252
|
+
`virtual` assignment may be created immediately.
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
const prepared = await client.documents.waitUntilReady(uploaded.id, {
|
|
256
|
+
maxWaitMs: 30_000,
|
|
257
|
+
pollIntervalMs: 2_000,
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### 2. Create or reuse the email signers
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
const signerA = await client.signers.create({
|
|
265
|
+
full_name: 'John Doe',
|
|
266
|
+
email: 'john@example.com',
|
|
267
|
+
});
|
|
268
|
+
const signerB = await client.signers.create({
|
|
269
|
+
full_name: 'Jane Smith',
|
|
270
|
+
email: 'jane@example.com',
|
|
271
|
+
});
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
The wire body is `{ full_name, email }`. Each response is an `ISigner`:
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
{
|
|
278
|
+
resource?: string;
|
|
279
|
+
id: string;
|
|
280
|
+
full_name: string;
|
|
281
|
+
email: string | null;
|
|
282
|
+
whatsapp_phone_number?: string | null;
|
|
283
|
+
cpf?: string | null; // compatibility type; not echoed by the API
|
|
284
|
+
has_accepted_terms?: boolean;
|
|
285
|
+
has_signature?: boolean; // signer-self response only
|
|
286
|
+
has_initial?: boolean; // signer-self response only
|
|
287
|
+
is_signature_reusable?: boolean; // signer-self response only
|
|
288
|
+
metadata?: Record<string, unknown>;
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
When an email is present, `signers.create()` first looks up that email in the
|
|
293
|
+
workspace and reuses the matching signer; a name-only or phone-only request
|
|
294
|
+
always creates a new signer.
|
|
295
|
+
|
|
296
|
+
### 3. Price, then request signatures
|
|
297
|
+
|
|
298
|
+
Cost estimation takes channel descriptors, not signer IDs:
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
const estimate = await client.assignments.estimateCost(uploaded.id, {
|
|
302
|
+
method: 'virtual',
|
|
303
|
+
signers: [{}, {}], // `{}` selects Email for each signer
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
if (!estimate.has_sufficient_resources) {
|
|
307
|
+
throw new Error(estimate.blocking_reason ?? estimate.message ?? 'Insufficient resources');
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
The response is `ICostEstimate`:
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
{
|
|
315
|
+
documents: number;
|
|
316
|
+
credits: number;
|
|
317
|
+
needs_extra_document: boolean;
|
|
318
|
+
extra_document_cost: number;
|
|
319
|
+
total_credits: number;
|
|
320
|
+
breakdown: Array<{ code: string; name: string; cost: number; quantity?: number; unit_cost?: number }>;
|
|
321
|
+
document_balance: number;
|
|
322
|
+
credit_balance: number;
|
|
323
|
+
has_sufficient_resources: boolean;
|
|
324
|
+
blocking_reason: 'PendingPayment' | 'InsufficientDocuments' | 'InsufficientCredits' | null;
|
|
325
|
+
message: string | null;
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Create the email assignment only after accepting that estimate:
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
const assignment = await client.assignments.create(uploaded.id, {
|
|
333
|
+
method: 'virtual',
|
|
334
|
+
signers: [
|
|
335
|
+
{ id: signerA.id, verification_method: 'Email', notification_methods: ['Email'] },
|
|
336
|
+
{ id: signerB.id, verification_method: 'Email', notification_methods: ['Email'] },
|
|
337
|
+
],
|
|
338
|
+
message: 'Please review and sign',
|
|
339
|
+
expires_at: '2027-12-31T23:59:00Z',
|
|
340
|
+
});
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
The request returns an `IAssignment`:
|
|
344
|
+
|
|
345
|
+
```ts
|
|
346
|
+
{
|
|
347
|
+
resource?: string;
|
|
348
|
+
id: string;
|
|
349
|
+
sender_email?: string;
|
|
350
|
+
method: 'virtual' | 'collect';
|
|
351
|
+
expires_at?: string | null;
|
|
352
|
+
expiration?: string;
|
|
353
|
+
message?: string | null;
|
|
354
|
+
signers: IAssignmentSigner[];
|
|
355
|
+
copy_receivers?: Array<Record<string, unknown>>;
|
|
356
|
+
items?: IAssignmentItem[];
|
|
357
|
+
summary?: {
|
|
358
|
+
signer_count: number;
|
|
359
|
+
completed_count: number;
|
|
360
|
+
signers: Array<ISigner & { completed?: boolean }>;
|
|
361
|
+
};
|
|
362
|
+
signing_urls?: Array<{ signer_id: string; url: string }>;
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
The URLs and delivered messages contain signer credentials; treat them as
|
|
367
|
+
secrets.
|
|
368
|
+
|
|
369
|
+
### 4. Complete the email signer flow
|
|
370
|
+
|
|
371
|
+
Assinafy sends each signer a link containing their access code and sends the
|
|
372
|
+
one-time verification code through the selected channel. Neither value is
|
|
373
|
+
returned as a standalone owner-side API field. A custom signer portal must
|
|
374
|
+
obtain both values from the signer-delivery flow; do not manufacture them or
|
|
375
|
+
log them.
|
|
376
|
+
|
|
377
|
+
```ts
|
|
378
|
+
// Signer-side client: no account API credential is needed or sent.
|
|
379
|
+
const signerClient = new AssinafyClient({
|
|
380
|
+
baseUrl,
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
const self = await signerClient.signerDocuments.self(accessCode); // ISignerSelf
|
|
384
|
+
|
|
385
|
+
// Query: signer-access-code=<accessCode>
|
|
386
|
+
// Body: { 'verification-code': '<six-digit code>' }
|
|
387
|
+
await signerClient.signerDocuments.verifyEmail({
|
|
388
|
+
signerAccessCode: accessCode,
|
|
389
|
+
verificationCode,
|
|
390
|
+
}); // Promise<void>
|
|
391
|
+
|
|
392
|
+
const confirmed = await signerClient.signerDocuments.confirmData(
|
|
393
|
+
uploaded.id,
|
|
394
|
+
accessCode,
|
|
395
|
+
{ full_name: self.full_name, email: self.email ?? undefined },
|
|
396
|
+
); // ISigner
|
|
397
|
+
|
|
398
|
+
const signable = await signerClient.signerDocuments.getAssignment(accessCode, true);
|
|
399
|
+
// `getAssignment` returns IDocumentDetailsResponse and records that the signer
|
|
400
|
+
// viewed the assignment. Do not issue it merely as a health check.
|
|
401
|
+
|
|
402
|
+
await signerClient.signerDocuments.signMultiple([signable.id], accessCode);
|
|
403
|
+
// Wire body: { document_ids: [signable.id] }; acknowledgement has no data.
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
Repeat this phase separately for each signer with that signer's own access code
|
|
407
|
+
and one-time code. The two signers in this example share the default step and
|
|
408
|
+
can sign in parallel.
|
|
409
|
+
|
|
410
|
+
`signMultiple` is only for `virtual` assignments. For `collect`, read
|
|
411
|
+
`signable.assignment.items`, then call `sign(documentId, assignmentId,
|
|
412
|
+
accessCode, entries)` with a non-empty array of
|
|
413
|
+
`{ itemId, fieldId, pageId, value }`. A virtual signer must confirm their data
|
|
414
|
+
before signing. A `DigitalCertificate` signer cannot call `sign`; that branch
|
|
415
|
+
uses Assinafy's certificate-start and certificate-complete flow, which is not
|
|
416
|
+
part of this SDK's current 89-operation surface.
|
|
417
|
+
|
|
418
|
+
### 5. Observe completion and download artifacts
|
|
419
|
+
|
|
420
|
+
Subscribe to `document_ready` for event-driven completion, or fetch
|
|
421
|
+
`documents.details(documentId)` until `status === 'certificated'`. Webhook
|
|
422
|
+
deliveries can repeat, so use their numeric `id` as an
|
|
423
|
+
idempotency key. Once complete:
|
|
424
|
+
|
|
425
|
+
```ts
|
|
426
|
+
const finalDocument = await client.documents.details(uploaded.id);
|
|
427
|
+
const signedPdf = await client.documents.download(uploaded.id, 'certificated');
|
|
428
|
+
const certificatePage = await client.documents.download(uploaded.id, 'certificate-page');
|
|
429
|
+
const bundleZip = await client.documents.download(uploaded.id, 'bundle');
|
|
430
|
+
|
|
431
|
+
// Validate an Assinafy signature hash when your workflow has extracted it.
|
|
432
|
+
const validation = await client.documents.verify(documentSignatureHash);
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
`original`, `certificated`, and `certificate-page` are PDFs. `bundle` is a ZIP
|
|
436
|
+
containing those three artifacts and also `pades` when the document had an
|
|
437
|
+
ICP-Brasil certificate signer. The `pades` PDF exists only for documents that
|
|
438
|
+
had certificate signers. An artifact can return `404` before generation has
|
|
439
|
+
finished. `decline_reason` is included in document details only when the access
|
|
440
|
+
token belongs to the document creator.
|
|
441
|
+
|
|
442
|
+
## Resource reference
|
|
152
443
|
|
|
153
444
|
Most account-scoped methods accept an optional `accountId` that overrides the
|
|
154
445
|
client default. Workspace `get`, `update`, `delete`, branding, and statistics
|
|
@@ -162,13 +453,15 @@ const doc = await client.documents.upload(
|
|
|
162
453
|
{ filePath: './contract.pdf' },
|
|
163
454
|
{ name: 'Service agreement', metadata: { type: 'service' } },
|
|
164
455
|
);
|
|
456
|
+
// `name` and `metadata` are compatibility multipart parts outside the published
|
|
457
|
+
// file-only request schema.
|
|
165
458
|
// `name` is optional and defaults to the file's own name. The API derives the
|
|
166
459
|
// display name from the uploaded filename and appends `.pdf` when absent, so
|
|
167
460
|
// the document above is stored as 'Service agreement.pdf'. Accents are
|
|
168
461
|
// transliterated by the API ('Contrato de Serviço' → 'Contrato de Servico.pdf').
|
|
169
462
|
// → {
|
|
170
463
|
// resource: 'document', id: '1031…', account_id: '102d…', template_id: null,
|
|
171
|
-
// name: '
|
|
464
|
+
// name: 'Service agreement.pdf', status: 'uploaded',
|
|
172
465
|
// artifacts: { original: 'https://…/download/original' },
|
|
173
466
|
// signing_url: 'https://app…/sign/1031…',
|
|
174
467
|
// pages: [], // populated once status reaches `metadata_ready`
|
|
@@ -179,7 +472,7 @@ const doc = await client.documents.upload(
|
|
|
179
472
|
await client.documents.upload({ buffer, fileName: 'contract.pdf' });
|
|
180
473
|
|
|
181
474
|
// List → { data: IDocumentListItem[], meta?: { current_page, per_page, total, last_page } }
|
|
182
|
-
const { data, meta } = await client.documents.list({ page: 1,
|
|
475
|
+
const { data, meta } = await client.documents.list({ page: 1, 'per-page': 20, sort: 'updated_at' });
|
|
183
476
|
|
|
184
477
|
// Search is the lightweight alternative to list: same item shape, but the API
|
|
185
478
|
// skips the expanded `assignment`/`pages`. Prefer it for name lookups.
|
|
@@ -194,7 +487,11 @@ await client.documents.waitUntilReady(doc.id, { maxWaitMs: 30_000 });
|
|
|
194
487
|
// (Passing `name` to upload() avoids both the round-trip and the race.)
|
|
195
488
|
await client.documents.rename(doc.id, 'Signed service agreement.pdf');
|
|
196
489
|
|
|
197
|
-
await client.documents.download(doc.id, 'certificated'); //
|
|
490
|
+
await client.documents.download(doc.id, 'certificated'); // signed PDF
|
|
491
|
+
await client.documents.download(doc.id, 'certificate-page');
|
|
492
|
+
await client.documents.download(doc.id, 'bundle'); // ZIP
|
|
493
|
+
// `pades` exists only when at least one signer used DigitalCertificate.
|
|
494
|
+
await client.documents.download(doc.id, 'pades');
|
|
198
495
|
await client.documents.thumbnail(doc.id);
|
|
199
496
|
await client.documents.downloadPage(doc.id, pageId);
|
|
200
497
|
|
|
@@ -222,13 +519,30 @@ const urgentTag = await client.tags.create({ name: 'Urgent' });
|
|
|
222
519
|
await client.documents.listTags(doc.id);
|
|
223
520
|
await client.documents.replaceTags(doc.id, [contractsTag.id, quarterTag.id]); // [] detaches all
|
|
224
521
|
await client.documents.addTags(doc.id, [urgentTag.id]); // append
|
|
225
|
-
await client.documents.detachTag(doc.id, urgentTag.id);
|
|
522
|
+
await client.documents.detachTag(doc.id, urgentTag.id); // → { detached: true }
|
|
226
523
|
```
|
|
227
524
|
|
|
228
525
|
Uploads are validated locally: only `.pdf` files up to 25 MB whose bytes begin
|
|
229
|
-
with the PDF magic header (`%PDF-`) are accepted
|
|
230
|
-
|
|
231
|
-
|
|
526
|
+
with the PDF magic header (`%PDF-`) are accepted. The API also limits documents
|
|
527
|
+
to 2,000 pages.
|
|
528
|
+
|
|
529
|
+
Page and artifact URLs embedded in JSON responses still require the same
|
|
530
|
+
account authentication as their download operations. Prefer
|
|
531
|
+
`documents.downloadPage()` and `documents.download()` so the SDK applies the
|
|
532
|
+
credential and returns a `Buffer`. `bundle` contains `original`, `certificated`,
|
|
533
|
+
and `certificate-page`, plus `pades` when available.
|
|
534
|
+
|
|
535
|
+
List endpoints return `{ data, meta }`, where `meta` is populated from the
|
|
536
|
+
`X-Pagination-*` response headers. Two API behaviours are worth knowing because
|
|
537
|
+
both are silent rather than errors:
|
|
538
|
+
|
|
539
|
+
- Only the hyphenated `per-page` is read. `per_page` is accepted and ignored,
|
|
540
|
+
falling back to 20 rows — so the SDK rewrites `per_page` to `per-page` on
|
|
541
|
+
every list method, and an explicit `per-page` wins when both are given.
|
|
542
|
+
- `per-page` is clamped to **50**. Asking for 100 returns 50 rows with a `200`.
|
|
543
|
+
The exported `MAX_LIST_PAGE_SIZE` constant is that ceiling; page through with
|
|
544
|
+
`page` rather than requesting a larger page, and trust `meta.per_page` over
|
|
545
|
+
the value you asked for.
|
|
232
546
|
|
|
233
547
|
### Signers
|
|
234
548
|
|
|
@@ -236,38 +550,36 @@ List endpoints return `{ data, meta }` where `meta` is populated from the `X-Pag
|
|
|
236
550
|
await client.signers.create({
|
|
237
551
|
full_name: 'John Doe',
|
|
238
552
|
email: 'john@example.com',
|
|
239
|
-
|
|
240
|
-
cpf: '123.456.789-00', // optional Brazilian tax ID — non-digits are stripped automatically
|
|
553
|
+
cpf: '123.456.789-00', // legacy compatibility input; non-digits are stripped
|
|
241
554
|
});
|
|
242
555
|
// → { id: '19e6…', full_name: 'John Doe', email: 'john@example.com',
|
|
243
|
-
// whatsapp_phone_number:
|
|
556
|
+
// whatsapp_phone_number: null, has_accepted_terms: false }
|
|
244
557
|
// (note: `cpf` is accepted on input but never echoed back by the API)
|
|
245
558
|
|
|
246
|
-
// Both contacts are optional
|
|
247
|
-
|
|
248
|
-
full_name: 'WhatsApp Only',
|
|
249
|
-
whatsapp_phone_number: '+5548999990000',
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
// Name-only is valid too, but cannot be notified until a contact is added.
|
|
559
|
+
// Both contacts are optional. A name-only signer cannot be notified until a
|
|
560
|
+
// contact is added.
|
|
253
561
|
await client.signers.create({ full_name: 'Contact Pending' });
|
|
254
562
|
|
|
255
|
-
// PHP SDK compatibility aliases are also accepted
|
|
256
563
|
await client.signers.create({
|
|
257
564
|
full_name: 'Jane Doe',
|
|
258
565
|
email: 'jane@example.com',
|
|
259
|
-
phone: '+5548999991111', // alias for whatsapp_phone_number
|
|
260
566
|
});
|
|
261
567
|
|
|
262
568
|
await client.signers.get(signerId);
|
|
263
|
-
await client.signers.list({ page: 1,
|
|
264
|
-
await client.signers.update(signerId, {
|
|
569
|
+
await client.signers.list({ page: 1, 'per-page': 50, search: 'john' });
|
|
570
|
+
await client.signers.update(signerId, {
|
|
571
|
+
full_name: 'Johnny Doe',
|
|
572
|
+
government_id: '390.533.447-05', // official update field; sent as digits
|
|
573
|
+
});
|
|
265
574
|
await client.signers.delete(signerId);
|
|
266
575
|
|
|
267
576
|
const existing = await client.signers.findByEmail('john@example.com');
|
|
268
577
|
```
|
|
269
578
|
|
|
270
|
-
When an `email` is supplied, `signers.create()` is idempotent by email
|
|
579
|
+
When an `email` is supplied, `signers.create()` is idempotent by email: it
|
|
580
|
+
reuses an existing signer when the same email is already present in the
|
|
581
|
+
workspace. Signers without email are always created fresh. See
|
|
582
|
+
[Paid signing branches](#paid-signing-branches) for phone-only signers.
|
|
271
583
|
|
|
272
584
|
### Assignments
|
|
273
585
|
|
|
@@ -281,8 +593,8 @@ await client.assignments.create(documentId, {
|
|
|
281
593
|
method: 'virtual',
|
|
282
594
|
signers: ['signer-1', 'signer-2'],
|
|
283
595
|
message: 'Please review and sign',
|
|
284
|
-
expires_at: '
|
|
285
|
-
copy_receivers: ['
|
|
596
|
+
expires_at: '2027-12-31T23:59:00Z',
|
|
597
|
+
copy_receivers: ['copy-recipient-signer-id'],
|
|
286
598
|
});
|
|
287
599
|
|
|
288
600
|
// Sequential signing: `step` controls signing order (parallel within a step).
|
|
@@ -294,31 +606,47 @@ await client.assignments.create(documentId, {
|
|
|
294
606
|
],
|
|
295
607
|
});
|
|
296
608
|
|
|
609
|
+
// Collect fields use 150-DPI page-image pixels measured from the upper-left.
|
|
610
|
+
await client.assignments.create(documentId, {
|
|
611
|
+
method: 'collect',
|
|
612
|
+
signers: [{ id: signerId }],
|
|
613
|
+
entries: [{
|
|
614
|
+
page_id: pageId,
|
|
615
|
+
fields: [{
|
|
616
|
+
signer_id: signerId,
|
|
617
|
+
field_id: fieldId,
|
|
618
|
+
display_settings: {
|
|
619
|
+
left: 69, top: 282, width: 421, height: 45.86, fontSize: 22,
|
|
620
|
+
fontFamily: 'Arial', backgroundColor: '#D5EBFF',
|
|
621
|
+
},
|
|
622
|
+
}],
|
|
623
|
+
}],
|
|
624
|
+
});
|
|
625
|
+
|
|
297
626
|
// Estimate cost (the endpoint prices channel descriptors, not signer IDs) → ICostEstimate
|
|
298
627
|
await client.assignments.estimateCost(documentId, { signers: [{}] }); // default Email
|
|
299
|
-
await client.assignments.estimateCost(documentId, {
|
|
300
|
-
signers: [{ verification_method: 'Whatsapp' }],
|
|
301
|
-
});
|
|
302
628
|
// → {
|
|
303
629
|
// documents: 1, credits: 0, needs_extra_document: false, extra_document_cost: 0,
|
|
304
630
|
// total_credits: 0, breakdown: [], document_balance: 67, credit_balance: 0,
|
|
305
631
|
// has_sufficient_resources: true, blocking_reason: null, message: null
|
|
306
632
|
// }
|
|
307
633
|
|
|
308
|
-
await client.assignments.resetExpiration(documentId, assignmentId, '
|
|
309
|
-
|
|
634
|
+
await client.assignments.resetExpiration(documentId, assignmentId, '2027-06-30T00:00:00Z');
|
|
635
|
+
// Compatibility only: the published request requires a date-time string.
|
|
636
|
+
// Confirm target support before using `null` to clear an expiration.
|
|
637
|
+
await client.assignments.resetExpiration(documentId, assignmentId, null);
|
|
310
638
|
|
|
311
639
|
await client.assignments.resendNotification(documentId, assignmentId, signerId);
|
|
312
640
|
// → { is_sent: true, document_id: '…', signer_id: '…' }
|
|
313
641
|
|
|
314
|
-
await client.assignments.estimateResendCost(documentId, assignmentId, signerId);
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
|
|
318
|
-
await client.assignments.listWhatsAppNotifications(documentId, assignmentId); // → IWhatsAppNotification[]
|
|
642
|
+
const resendCost = await client.assignments.estimateResendCost(documentId, assignmentId, signerId);
|
|
643
|
+
// Official response: ICostEstimate. Older deployments can return the compact
|
|
644
|
+
// IResendCostEstimate branch with `total` and `has_sufficient_credits`; narrow
|
|
645
|
+
// with `'total_credits' in resendCost` before reading branch-specific fields.
|
|
319
646
|
```
|
|
320
647
|
|
|
321
|
-
The `create` response is an `IAssignment`: `{ id, method, signers: [...],
|
|
648
|
+
The `create` response is an `IAssignment`: `{ id, method, signers: [...],
|
|
649
|
+
items: [{ display_settings, ... }], signing_urls: [{ signer_id, url }], … }`.
|
|
322
650
|
|
|
323
651
|
For backwards compatibility, the SDK also accepts legacy `signer_ids` and `signerIds` payloads and rewrites them to the current `signers: [{ id }]` format expected by the API.
|
|
324
652
|
|
|
@@ -329,15 +657,92 @@ await client.documents.delete(documentId); // w
|
|
|
329
657
|
await client.signerDocuments.decline(documentId, assignmentId, accessCode, 'No longer needed'); // signer-side
|
|
330
658
|
```
|
|
331
659
|
|
|
660
|
+
### Paid signing branches
|
|
661
|
+
|
|
662
|
+
Keep the email flow as the default. Enable either branch below only after the
|
|
663
|
+
workspace has the required plan or feature and the returned cost estimate is
|
|
664
|
+
acceptable.
|
|
665
|
+
|
|
666
|
+
#### WhatsApp verification and notification
|
|
667
|
+
|
|
668
|
+
WhatsApp is available only on paid subscriptions and costs 0.45 credit per
|
|
669
|
+
notification. Create a phone-only signer or add a phone to an existing signer,
|
|
670
|
+
then request the `Whatsapp` channel explicitly:
|
|
671
|
+
|
|
672
|
+
```ts
|
|
673
|
+
const phoneSigner = await client.signers.create({
|
|
674
|
+
full_name: 'Mobile Signer',
|
|
675
|
+
whatsapp_phone_number: '+5511999990000',
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
const whatsappCost = await client.assignments.estimateCost(documentId, {
|
|
679
|
+
method: 'virtual',
|
|
680
|
+
signers: [{ verification_method: 'Whatsapp', notification_methods: ['Whatsapp'] }],
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
const whatsappAssignment = await client.assignments.create(documentId, {
|
|
684
|
+
method: 'virtual',
|
|
685
|
+
signers: [{
|
|
686
|
+
id: phoneSigner.id,
|
|
687
|
+
verification_method: 'Whatsapp',
|
|
688
|
+
notification_methods: ['Whatsapp'],
|
|
689
|
+
}],
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
const notices = await client.assignments.listWhatsAppNotifications(
|
|
693
|
+
documentId,
|
|
694
|
+
whatsappAssignment.id,
|
|
695
|
+
);
|
|
696
|
+
// IWhatsAppNotification[]:
|
|
697
|
+
// [{ sent_at, header, body, buttons: [{ text, url? }], phone_number, signer_id }]
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
The high-level helper selects this paid branch for a signer that has a phone
|
|
701
|
+
number but no email. Button URLs can contain signer credentials; do not log or
|
|
702
|
+
forward them outside the signing flow.
|
|
703
|
+
|
|
704
|
+
#### ICP-Brasil digital certificate
|
|
705
|
+
|
|
706
|
+
`DigitalCertificate` requires the account feature, a CPF or CNPJ in the
|
|
707
|
+
signer's `government_id`, and exactly one certificate signer in that signing
|
|
708
|
+
step. It costs two credits per certificate signer in addition to the selected
|
|
709
|
+
notification cost.
|
|
710
|
+
|
|
711
|
+
```ts
|
|
712
|
+
const certificateSigner = await client.signers.update(signerId, {
|
|
713
|
+
government_id: '390.533.447-05',
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
const certificateCost = await client.assignments.estimateCost(documentId, {
|
|
717
|
+
method: 'virtual',
|
|
718
|
+
signers: [{ verification_method: 'DigitalCertificate', notification_methods: ['Email'] }],
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
await client.assignments.create(documentId, {
|
|
722
|
+
method: 'virtual',
|
|
723
|
+
signers: [{
|
|
724
|
+
id: certificateSigner.id,
|
|
725
|
+
step: 1,
|
|
726
|
+
verification_method: 'DigitalCertificate',
|
|
727
|
+
notification_methods: ['Email'],
|
|
728
|
+
}],
|
|
729
|
+
});
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
Before opening the assignment, the signer must confirm identity data and accept
|
|
733
|
+
terms with `confirmData(..., { has_accepted_terms: true })` or `acceptTerms()`.
|
|
734
|
+
The regular `sign()` endpoint rejects certificate signers; they complete the
|
|
735
|
+
ICP-Brasil flow through Assinafy's browser integration. After completion,
|
|
736
|
+
`documents.download(documentId, 'pades')` returns the qualified PAdES artifact.
|
|
737
|
+
|
|
332
738
|
### Templates
|
|
333
739
|
|
|
334
|
-
`templates.list()` is part of the current OpenAPI document.
|
|
335
|
-
|
|
336
|
-
and `downloadPage`—that are
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
responses; compare `template.status.toLowerCase()` when branching on it.
|
|
740
|
+
`templates.list()` is part of the current OpenAPI document. Existing
|
|
741
|
+
integrations can also use five template-management routes—`create`, `get`,
|
|
742
|
+
`update`, `delete`, and `downloadPage`—that are absent from that document. See
|
|
743
|
+
[compatibility notes](docs/COMPATIBILITY.md#template-management-extensions).
|
|
744
|
+
Template status casing can vary by deployment; normalize with
|
|
745
|
+
`template.status.toLowerCase()` when branching on it.
|
|
341
746
|
|
|
342
747
|
```ts
|
|
343
748
|
// Create a template by uploading a PDF (multipart). The template starts in
|
|
@@ -354,7 +759,7 @@ const created = await client.templates.create(
|
|
|
354
759
|
// pages: [], tags: [], created_at: '2026-…', updated_at: '2026-…'
|
|
355
760
|
// }
|
|
356
761
|
|
|
357
|
-
const { data, meta } = await client.templates.list({ search: 'NDA',
|
|
762
|
+
const { data, meta } = await client.templates.list({ search: 'NDA', 'per-page': 20 });
|
|
358
763
|
const template = await client.templates.get(created.id); // includes pages[] + default_document_tags
|
|
359
764
|
await client.templates.update(created.id, { name: 'NDA v2', message: 'Please sign' });
|
|
360
765
|
const firstPage = template.pages?.[0];
|
|
@@ -383,8 +788,14 @@ await client.documents.estimateCostFromTemplate(templateId, [
|
|
|
383
788
|
// has_sufficient_resources: true, blocking_reason: null, breakdown: [], … }
|
|
384
789
|
```
|
|
385
790
|
|
|
791
|
+
Template signer descriptors also accept
|
|
792
|
+
`verification_method: 'DigitalCertificate'` with the same prerequisites under
|
|
793
|
+
[ICP-Brasil digital certificate](#icp-brasil-digital-certificate).
|
|
794
|
+
|
|
386
795
|
Template creation only uploads the PDF and provisions the default editor role —
|
|
387
796
|
configure roles/fields in the Assinafy editor (or the web UI) afterwards.
|
|
797
|
+
The `download_url` values in template page objects are protected URLs; prefer
|
|
798
|
+
`templates.downloadPage()` so the API credential is attached.
|
|
388
799
|
|
|
389
800
|
### Tags
|
|
390
801
|
|
|
@@ -444,7 +855,8 @@ await client.workspaces.getStats(accountId, {
|
|
|
444
855
|
});
|
|
445
856
|
|
|
446
857
|
await client.workspaces.delete(accountId);
|
|
447
|
-
//
|
|
858
|
+
// `force` cancels an active paid subscription as part of account deletion. It
|
|
859
|
+
// is not a general bypass for unrelated deletion restrictions.
|
|
448
860
|
await client.workspaces.delete(restrictedAccountId, { force: true });
|
|
449
861
|
```
|
|
450
862
|
|
|
@@ -466,7 +878,7 @@ await client.fields.validate(fieldId, '400.676.228-36', { signerAccessCode });
|
|
|
466
878
|
await client.fields.validateMultiple(
|
|
467
879
|
[
|
|
468
880
|
{ field_id: 'f1', value: '1111111111111' },
|
|
469
|
-
{ field_id: 'f2', value: '
|
|
881
|
+
{ field_id: 'f2', value: 'value@example.com' },
|
|
470
882
|
],
|
|
471
883
|
{ signerAccessCode },
|
|
472
884
|
);
|
|
@@ -513,8 +925,17 @@ const daily = await client.users.getStats({
|
|
|
513
925
|
granularity: 'daily',
|
|
514
926
|
month: '2026-06',
|
|
515
927
|
});
|
|
516
|
-
// Each row includes period,
|
|
517
|
-
//
|
|
928
|
+
// Each row includes period, upload/send/certification totals, notification
|
|
929
|
+
// counts for email/WhatsApp/bypass, verification counts for
|
|
930
|
+
// email/WhatsApp/bypass/digital-certificate, viewed, and completed counts.
|
|
931
|
+
|
|
932
|
+
const preferences = await client.users.getNotificationPreferences();
|
|
933
|
+
await client.users.updateNotificationPreferences({
|
|
934
|
+
SignerDeclined: false,
|
|
935
|
+
DocumentExpired: false,
|
|
936
|
+
});
|
|
937
|
+
// Updates merge: omitted keys keep their current value. Both methods return
|
|
938
|
+
// the complete nine-key notification preference map.
|
|
518
939
|
```
|
|
519
940
|
|
|
520
941
|
### Webhooks
|
|
@@ -523,6 +944,7 @@ const daily = await client.users.getStats({
|
|
|
523
944
|
await client.webhooks.register({
|
|
524
945
|
url: 'https://example.com/webhooks/assinafy',
|
|
525
946
|
email: 'admin@example.com',
|
|
947
|
+
is_active: true,
|
|
526
948
|
// events defaults to the current SDK default set below
|
|
527
949
|
events: [
|
|
528
950
|
'document_ready',
|
|
@@ -533,13 +955,91 @@ await client.webhooks.register({
|
|
|
533
955
|
],
|
|
534
956
|
});
|
|
535
957
|
|
|
536
|
-
await client.webhooks.get(); //
|
|
958
|
+
await client.webhooks.get(); // IWebhookSubscription | null
|
|
537
959
|
await client.webhooks.inactivate(); // stop deliveries (no delete route exists)
|
|
538
960
|
await client.webhooks.listEventTypes();
|
|
539
|
-
await client.webhooks.listDispatches({
|
|
540
|
-
|
|
961
|
+
const history = await client.webhooks.listDispatches({
|
|
962
|
+
delivered: false,
|
|
963
|
+
page: 1,
|
|
964
|
+
'per-page': 20,
|
|
965
|
+
}); // { data: IWebhookDispatch[], meta?: PaginationMeta }
|
|
966
|
+
const retried = await client.webhooks.retryDispatch(dispatchId); // IWebhookDispatch
|
|
967
|
+
```
|
|
968
|
+
|
|
969
|
+
`register` sends `{ events, is_active, url, email }` and returns
|
|
970
|
+
`{ events, is_active, url, email, updated_at? }`. Assinafy delivers each event
|
|
971
|
+
as an HTTP `POST` with `Content-Type: application/json` and `Connection: close`.
|
|
972
|
+
Any `2xx` is success. There are at most two automatic attempts, separated by
|
|
973
|
+
three seconds. After ten consecutive failed events, ordinary delivery pauses
|
|
974
|
+
and about 5% of later events are attempted until one succeeds; use
|
|
975
|
+
`retryDispatch()` for an immediate manual redelivery. The dispatch history
|
|
976
|
+
retains only the first 2,000 characters of the receiver's response body.
|
|
977
|
+
|
|
978
|
+
Each history or retry result is an `IWebhookDispatch`:
|
|
979
|
+
|
|
980
|
+
```ts
|
|
981
|
+
{
|
|
982
|
+
resource?: string;
|
|
983
|
+
id: string;
|
|
984
|
+
event: string;
|
|
985
|
+
activity_id: number;
|
|
986
|
+
endpoint: string | null;
|
|
987
|
+
payload: IWebhookPayload | Record<string, unknown> | null;
|
|
988
|
+
delivered: boolean;
|
|
989
|
+
http_status: number | null;
|
|
990
|
+
response_body: string | null;
|
|
991
|
+
error: string | null;
|
|
992
|
+
created_at: string;
|
|
993
|
+
updated_at?: string;
|
|
994
|
+
}
|
|
995
|
+
```
|
|
996
|
+
|
|
997
|
+
Every delivery body uses this envelope:
|
|
998
|
+
|
|
999
|
+
```ts
|
|
1000
|
+
{
|
|
1001
|
+
id: number; // use for idempotent processing
|
|
1002
|
+
event: string;
|
|
1003
|
+
message: string | null;
|
|
1004
|
+
payload: Record<string, unknown> | null;
|
|
1005
|
+
origin: { ip?: string; 'user-agent'?: string } | null;
|
|
1006
|
+
created_at: number; // Unix seconds
|
|
1007
|
+
subject: { type: 'User' | 'Signer' | 'Account' | 'Document' | 'Template'; [key: string]: unknown };
|
|
1008
|
+
object: { type: 'User' | 'Signer' | 'Account' | 'Document' | 'Template'; [key: string]: unknown };
|
|
1009
|
+
account_id: string;
|
|
1010
|
+
}
|
|
541
1011
|
```
|
|
542
1012
|
|
|
1013
|
+
Event-specific values are:
|
|
1014
|
+
|
|
1015
|
+
| `event` | `subject.type` | `object.type` | `payload` keys |
|
|
1016
|
+
| --- | --- | --- | --- |
|
|
1017
|
+
| `document_uploaded` | `User` | `Document` | — |
|
|
1018
|
+
| `document_metadata_ready` | `User` | `Document` | — |
|
|
1019
|
+
| `document_prepared` | `User` | `Document` | — |
|
|
1020
|
+
| `assignment_created` | `User` | `Document` | `user_name`, `user_email`, `user_telephone` |
|
|
1021
|
+
| `document_ready` | `Account` | `Document` | — |
|
|
1022
|
+
| `document_processing_failed` | `Account` | `Document` | `error_message` |
|
|
1023
|
+
| `signature_requested` | `User` | `Document` | `signer_email`, `signer_full_name`, or `signer_whatsapp_phone_number`, according to channel |
|
|
1024
|
+
| `signer_created` | `User` | `Signer` | `signer_full_name` |
|
|
1025
|
+
| `signer_email_verified` | `Signer` | `Document` | `signer_email` |
|
|
1026
|
+
| `signer_whatsapp_verified` | `Signer` | `Document` | `signer_whatsapp_phone_number` |
|
|
1027
|
+
| `signer_data_confirmed` | `Signer` | `Document` | `signer_email` |
|
|
1028
|
+
| `signer_viewed_document` | `Signer` | `Document` | `signer_full_name` |
|
|
1029
|
+
| `signer_signed_document` | `Signer` | `Document` | `signer_full_name` |
|
|
1030
|
+
| `signer_rejected_document` | `Signer` | `Document` | `signer_full_name` |
|
|
1031
|
+
| `user_rejected_document` | `User` | `Document` | `user_name` |
|
|
1032
|
+
| `template_created` | `User` | `Template` | — |
|
|
1033
|
+
| `template_processed` | `User` | `Template` | — |
|
|
1034
|
+
| `template_processing_failed` | `Account` | `Template` | `error_message` |
|
|
1035
|
+
|
|
1036
|
+
`payload`, `subject`, and `object` are event-dependent. Accept unknown fields
|
|
1037
|
+
for forward compatibility and acknowledge only after durable, idempotent
|
|
1038
|
+
processing. Non-`2xx` responses, timeouts, and connection failures all count as
|
|
1039
|
+
failed deliveries. `assignment_created` and `document_metadata_ready` have no
|
|
1040
|
+
guaranteed ordering. For account entities, Assinafy removes the `integration`
|
|
1041
|
+
property before delivery.
|
|
1042
|
+
|
|
543
1043
|
### Webhook verification
|
|
544
1044
|
|
|
545
1045
|
`WebhookVerifier` is an opt-in HMAC-SHA256 utility for integrations whose
|
|
@@ -591,53 +1091,64 @@ only for compatibility with deployments that still expect the legacy query.
|
|
|
591
1091
|
|
|
592
1092
|
```ts
|
|
593
1093
|
await client.signerDocuments.self(accessCode);
|
|
594
|
-
await client.signerDocuments.acceptTerms(accessCode);
|
|
595
1094
|
await client.signerDocuments.verifyEmail({ signerAccessCode: accessCode, verificationCode: '123456' });
|
|
596
1095
|
|
|
597
1096
|
await client.signerDocuments.getCurrent(signerId, accessCode);
|
|
598
|
-
const { data } = await client.signerDocuments.list(signerId, accessCode, {
|
|
1097
|
+
const { data } = await client.signerDocuments.list(signerId, accessCode, { 'per-page': 20 });
|
|
599
1098
|
// Signer-side counterpart of documents.search(), authorised by the access code.
|
|
600
1099
|
const found = await client.signerDocuments.search(signerId, accessCode, 'invoice');
|
|
601
1100
|
await client.signerDocuments.download(signerId, documentId, 'original');
|
|
1101
|
+
// Available only after an ICP-Brasil certificate signer completes signing.
|
|
1102
|
+
await client.signerDocuments.download(signerId, documentId, 'pades');
|
|
602
1103
|
|
|
603
1104
|
await client.signerDocuments.confirmData(documentId, accessCode, {
|
|
604
1105
|
email: 'me@example.com',
|
|
605
1106
|
full_name: 'Example Signer',
|
|
606
1107
|
government_id: '123.456.789-00',
|
|
1108
|
+
has_accepted_terms: true,
|
|
607
1109
|
});
|
|
608
|
-
|
|
1110
|
+
// Alternatively, accept terms separately before getAssignment():
|
|
1111
|
+
// await client.signerDocuments.acceptTerms(accessCode);
|
|
609
1112
|
|
|
610
1113
|
// Signature image management ({ reuse: true } persists it for future documents)
|
|
611
1114
|
await client.signerDocuments.uploadSignature(accessCode, pngBuffer, { imageType: 'signature', reuse: true });
|
|
612
1115
|
await client.signerDocuments.downloadSignature(accessCode, 'signature');
|
|
613
1116
|
|
|
614
1117
|
// Sign / decline
|
|
615
|
-
const
|
|
1118
|
+
const signable = await client.signerDocuments.getAssignment(accessCode);
|
|
1119
|
+
// `sign()` is for collect assignments and requires every placed field value.
|
|
616
1120
|
await client.signerDocuments.sign(documentId, assignmentId, accessCode, [
|
|
617
1121
|
{ itemId, fieldId, pageId, value: 'Signed by John' },
|
|
618
1122
|
]);
|
|
619
1123
|
await client.signerDocuments.decline(documentId, assignmentId, accessCode, 'Not authorized');
|
|
620
1124
|
|
|
621
|
-
//
|
|
1125
|
+
// `signMultiple()` is for virtual assignments only.
|
|
622
1126
|
await client.signerDocuments.signMultiple(['doc-1', 'doc-2'], accessCode);
|
|
623
1127
|
await client.signerDocuments.declineMultiple(['doc-1'], 'Unfavorable terms', accessCode);
|
|
624
1128
|
```
|
|
625
1129
|
|
|
1130
|
+
`sign()` also requires virtual signers to have confirmed their data first, but
|
|
1131
|
+
virtual assignments should normally use `signMultiple()`. Certificate signers
|
|
1132
|
+
cannot use `sign()`; see [Paid signing branches](#paid-signing-branches).
|
|
1133
|
+
|
|
626
1134
|
## High-level helper
|
|
627
1135
|
|
|
628
|
-
Uploads a PDF,
|
|
1136
|
+
Uploads a PDF, reuses or creates signers by email, creates a virtual assignment
|
|
1137
|
+
immediately, and optionally waits for processing before returning.
|
|
629
1138
|
|
|
630
1139
|
```ts
|
|
631
1140
|
const result = await client.uploadAndRequestSignatures({
|
|
632
1141
|
source: { filePath: './contract.pdf' },
|
|
633
1142
|
signers: [
|
|
634
1143
|
{ name: 'John', email: 'john@example.com' },
|
|
635
|
-
{ name: 'Jane', email: 'jane@example.com'
|
|
1144
|
+
{ name: 'Jane', email: 'jane@example.com' },
|
|
636
1145
|
],
|
|
637
1146
|
message: 'Please sign',
|
|
638
|
-
metadata: { year: 2026 },
|
|
1147
|
+
metadata: { year: 2026 }, // compatibility upload part; omit for file-only wire format
|
|
639
1148
|
waitForReady: true,
|
|
640
|
-
|
|
1149
|
+
waitOptions: { maxWaitMs: 30_000, pollIntervalMs: 1_000 },
|
|
1150
|
+
expiresAt: '2027-12-31T00:00:00Z',
|
|
1151
|
+
copyReceivers: ['existing-copy-recipient-signer-id'],
|
|
641
1152
|
});
|
|
642
1153
|
|
|
643
1154
|
result.document; // fully-processed IDocumentDetailsResponse (waitForReady: true, the default);
|
|
@@ -646,10 +1157,20 @@ result.assignment; // IAssignment
|
|
|
646
1157
|
result.signer_ids; // string[]
|
|
647
1158
|
```
|
|
648
1159
|
|
|
649
|
-
`waitForReady: false` skips
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
1160
|
+
`waitForReady: false` skips post-assignment polling and returns the initial
|
|
1161
|
+
upload response. With the default `true`, the helper creates the assignment first and
|
|
1162
|
+
then waits for the current document details. Both production and sandbox allow
|
|
1163
|
+
virtual assignments in `uploaded` and `metadata_processing` and promote them
|
|
1164
|
+
automatically; only `collect` assignments require rendered pages.
|
|
1165
|
+
Every signer above uses the default email channel. A phone-only signer selects
|
|
1166
|
+
the paid WhatsApp branch described earlier. `copyReceivers` accepts existing
|
|
1167
|
+
signer IDs, not email addresses; check the returned assignment before treating
|
|
1168
|
+
a copy receiver as registered.
|
|
1169
|
+
|
|
1170
|
+
The helper is not transactional. A post-assignment polling error includes the
|
|
1171
|
+
created `documentId`, `assignmentId`, and `signerIds` in its `context` (and in
|
|
1172
|
+
`ValidationError.errors` for timeouts); inspect those IDs before deciding
|
|
1173
|
+
whether to retry the workflow.
|
|
653
1174
|
|
|
654
1175
|
## Errors
|
|
655
1176
|
|
|
@@ -674,47 +1195,10 @@ try {
|
|
|
674
1195
|
}
|
|
675
1196
|
```
|
|
676
1197
|
|
|
677
|
-
## Live smoke test
|
|
678
|
-
|
|
679
|
-
A redaction-safe real-network audit under
|
|
680
|
-
[`scripts/live-smoke.ts`](scripts/live-smoke.ts) exercises read-only operations
|
|
681
|
-
by default. Its `--all` mode creates an isolated sandbox workspace, audits
|
|
682
|
-
reversible CRUD, upload, assignment, branding, statistics, and template flows,
|
|
683
|
-
then attempts per-resource cleanup and force-deletes that workspace in `finally`.
|
|
684
|
-
It complements the unit/contract suites. Optional login credentials enable the
|
|
685
|
-
password-login probe; an optional signer access code/OTP enables read-only
|
|
686
|
-
signer-link and OTP probes; and an optional HTTPS webhook receiver enables the
|
|
687
|
-
subscription lifecycle. Password changes, API-key rotation, social-provider
|
|
688
|
-
login/linking, legal consent, identity/signature mutation, signing, and decline
|
|
689
|
-
flows are deliberately never automated by this harness and are always reported
|
|
690
|
-
as explicit `SKIP`s. They require credentials, authorization, or legal fixtures
|
|
691
|
-
that an API key/account/e-mail set cannot safely supply.
|
|
692
|
-
|
|
693
|
-
```bash
|
|
694
|
-
# Read-only account/API checks. The base URL is deliberately mandatory.
|
|
695
|
-
ASSINAFY_BASE_URL=https://sandbox.assinafy.com.br/v1 \
|
|
696
|
-
ASSINAFY_API_KEY=… ASSINAFY_ACCOUNT_ID=… \
|
|
697
|
-
bun scripts/live-smoke.ts
|
|
698
|
-
|
|
699
|
-
# Full reversible audit in a disposable sandbox workspace.
|
|
700
|
-
ASSINAFY_BASE_URL=https://sandbox.assinafy.com.br/v1 \
|
|
701
|
-
ASSINAFY_API_KEY=… ASSINAFY_ACCOUNT_ID=… \
|
|
702
|
-
ASSINAFY_TEST_EMAIL_PRIMARY=first@example.com \
|
|
703
|
-
ASSINAFY_TEST_EMAIL_SECONDARY=second@example.com \
|
|
704
|
-
bun scripts/live-smoke.ts --all
|
|
705
|
-
```
|
|
706
|
-
|
|
707
|
-
Mutation is refused unless the URL's exact host is the Assinafy sandbox. The
|
|
708
|
-
explicit `--confirm-production` escape hatch exists for controlled environments
|
|
709
|
-
but should not be used for routine SDK verification. The script never prints
|
|
710
|
-
credentials, IDs, e-mail addresses, URLs, request/response payloads, or raw API
|
|
711
|
-
errors. See [CONTRIBUTING.md](CONTRIBUTING.md#sandbox-tests) for optional fixture
|
|
712
|
-
variables and safety guidance.
|
|
713
|
-
|
|
714
1198
|
## Development
|
|
715
1199
|
|
|
716
1200
|
```bash
|
|
717
|
-
bun install
|
|
1201
|
+
bun install --frozen-lockfile
|
|
718
1202
|
bun run typecheck # source, script, and test type checks
|
|
719
1203
|
bun run lint
|
|
720
1204
|
bun test # bun:test suites
|