@assinafy/sdk 2.1.1 → 2.1.2
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 +554 -111
- package/dist/index.d.mts +676 -311
- package/dist/index.d.ts +676 -311
- package/dist/index.js +3064 -1932
- package/dist/index.mjs +3063 -1932
- package/docs/API_COVERAGE.md +28 -30
- package/docs/COMPATIBILITY.md +256 -179
- package/docs/RELEASING.md +42 -11
- package/package.json +10 -9
package/README.md
CHANGED
|
@@ -6,18 +6,19 @@ 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
|
|
9
|
+
routes used by existing integrations and two legacy browser URL helpers are
|
|
10
|
+
retained for compatibility.
|
|
10
11
|
|
|
11
|
-
See [API coverage](docs/API_COVERAGE.md) for the
|
|
12
|
-
[compatibility notes](docs/COMPATIBILITY.md) for
|
|
13
|
-
|
|
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.
|
|
14
15
|
|
|
15
16
|
## Requirements
|
|
16
17
|
|
|
17
18
|
- Node.js 22+ for the built-in `FormData` / `Blob` APIs used by uploads. Packed
|
|
18
19
|
CJS and ESM imports are tested on 22 (maintenance LTS), 24 (active LTS), and
|
|
19
20
|
26 (Current); Node 20 reached end-of-life in April 2026 and is unsupported.
|
|
20
|
-
- or Bun 1.
|
|
21
|
+
- or Bun 1.4.0 (the version pinned for development and CI)
|
|
21
22
|
|
|
22
23
|
## Installation
|
|
23
24
|
|
|
@@ -39,23 +40,30 @@ The package is published to both [npmjs.com](https://www.npmjs.com/package/@assi
|
|
|
39
40
|
```ts
|
|
40
41
|
import { AssinafyClient } from '@assinafy/sdk';
|
|
41
42
|
|
|
43
|
+
const baseUrl = process.env.ASSINAFY_BASE_URL ?? 'https://api.assinafy.com.br/v1';
|
|
42
44
|
const client = new AssinafyClient({
|
|
43
45
|
apiKey: process.env.ASSINAFY_API_KEY!,
|
|
44
46
|
accountId: process.env.ASSINAFY_ACCOUNT_ID!,
|
|
47
|
+
baseUrl,
|
|
45
48
|
});
|
|
46
49
|
|
|
47
50
|
const result = await client.uploadAndRequestSignatures({
|
|
48
51
|
source: { filePath: './contract.pdf' },
|
|
49
52
|
signers: [
|
|
50
|
-
{ name: 'John Doe',
|
|
51
|
-
{ name: 'Jane Smith',
|
|
53
|
+
{ name: 'John Doe', email: 'john@example.com' },
|
|
54
|
+
{ name: 'Jane Smith', email: 'jane@example.com' },
|
|
52
55
|
],
|
|
53
56
|
message: 'Please sign this contract',
|
|
54
57
|
});
|
|
55
58
|
|
|
56
59
|
console.log('Document ID:', result.document.id);
|
|
60
|
+
console.log('Assignment ID:', result.assignment.id);
|
|
57
61
|
```
|
|
58
62
|
|
|
63
|
+
This path uses email verification and notification for every signer. WhatsApp
|
|
64
|
+
and ICP-Brasil certificate signing have separate prerequisites and costs; see
|
|
65
|
+
[Paid signing branches](#paid-signing-branches) before enabling either one.
|
|
66
|
+
|
|
59
67
|
## Authentication
|
|
60
68
|
|
|
61
69
|
The API supports two authentication methods. Prefer `apiKey` — it maps to the `X-Api-Key` header recommended by Assinafy for backend services.
|
|
@@ -85,6 +93,11 @@ await publicClient.signerDocuments.self(signerAccessCode);
|
|
|
85
93
|
Protected methods still require `apiKey` or `token`; the API returns its normal
|
|
86
94
|
`401` response if one is called without credentials.
|
|
87
95
|
|
|
96
|
+
Every SDK transport, including public and signer-access-code requests, sends
|
|
97
|
+
`User-Agent: Assinafy-Typescript-SDK/v<VERSION>`, where `<VERSION>` is the
|
|
98
|
+
installed package version. The exact value is also exported as
|
|
99
|
+
`SDK_USER_AGENT` for custom transport checks and observability rules.
|
|
100
|
+
|
|
88
101
|
## Configuration
|
|
89
102
|
|
|
90
103
|
| Option | Type | Default | Description |
|
|
@@ -92,7 +105,7 @@ Protected methods still require `apiKey` or `token`; the API returns its normal
|
|
|
92
105
|
| `apiKey` | string | — | Preferred credential (sent as `X-Api-Key`). |
|
|
93
106
|
| `token` | string | — | Access token (sent as `Authorization: Bearer`). |
|
|
94
107
|
| `accountId` | string | — | Default workspace/account ID. |
|
|
95
|
-
| `baseUrl` | string | `https://api.assinafy.com.br/v1` |
|
|
108
|
+
| `baseUrl` | string | `https://api.assinafy.com.br/v1` | Absolute HTTP(S) API base without credentials, query, or fragment. |
|
|
96
109
|
| `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
110
|
| `timeout` | number | `30000` | Request timeout in milliseconds. |
|
|
98
111
|
| `maxRetries` | number | `2` | Auto-retries eligible HTTP 429 responses, honoring `Retry-After`. `0` disables. |
|
|
@@ -102,9 +115,12 @@ Protected methods still require `apiKey` or `token`; the API returns its normal
|
|
|
102
115
|
|
|
103
116
|
On an HTTP `429`, the client automatically retries up to `maxRetries` times,
|
|
104
117
|
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
|
-
|
|
118
|
+
before each attempt. Automatic replay is limited to read-safe `GET`, `HEAD`,
|
|
119
|
+
`OPTIONS`, and `DELETE` requests. `GET /sign` is excluded because it records
|
|
120
|
+
that the signer viewed the assignment. Writes are not replayed by default.
|
|
121
|
+
A non-empty `Idempotency-Key` opts a custom request into SDK replay, but it is
|
|
122
|
+
not part of the current Assinafy OpenAPI contract: confirm that the target
|
|
123
|
+
route deduplicates that key server-side first. No other HTTP status is retried.
|
|
108
124
|
|
|
109
125
|
### Factories
|
|
110
126
|
|
|
@@ -122,8 +138,8 @@ const client = AssinafyClient.fromConfig({
|
|
|
122
138
|
## Endpoint coverage
|
|
123
139
|
|
|
124
140
|
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
|
-
|
|
141
|
+
covered. The table below is the resource-level summary; the detailed operation
|
|
142
|
+
ledger is in [docs/API_COVERAGE.md](docs/API_COVERAGE.md).
|
|
127
143
|
|
|
128
144
|
| Resource | Endpoints |
|
|
129
145
|
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -136,7 +152,7 @@ operation-by-operation ledger is in [docs/API_COVERAGE.md](docs/API_COVERAGE.md)
|
|
|
136
152
|
| `client.webhooks` | register, get, inactivate, listEventTypes, listDispatches, retryDispatch |
|
|
137
153
|
| `client.fields` | create, list, get, update, delete, validate, validateMultiple, listTypes |
|
|
138
154
|
| `client.auth` | getSocialLoginUrl, getSocialLoginCallbackUrl, login, socialLogin, linkSocialLogin, createApiKey, getApiKey, deleteApiKey, changePassword, requestPasswordReset, resetPassword |
|
|
139
|
-
| `client.users` | getCurrent, getStats
|
|
155
|
+
| `client.users` | getCurrent, getStats, getNotificationPreferences, updateNotificationPreferences |
|
|
140
156
|
| `client.signerDocuments` | getCurrent, list, **search**, download, signMultiple, declineMultiple, self, acceptTerms, verifyEmail, confirmData, uploadSignature, downloadSignature, getAssignment, sign, decline |
|
|
141
157
|
| `client.webhookVerifier` | verify, extractEvent, getEventType, getEventData |
|
|
142
158
|
|
|
@@ -148,7 +164,251 @@ inline in the generated declarations. Editors expose the reference on hover,
|
|
|
148
164
|
and declaration files ship with the package. The coverage ledger links those
|
|
149
165
|
typed methods back to each upstream operation without duplicating the schema.
|
|
150
166
|
|
|
151
|
-
##
|
|
167
|
+
## Document lifecycle
|
|
168
|
+
|
|
169
|
+
The normal integration has an account-owner phase, a signer phase, and a final
|
|
170
|
+
artifact phase. The example below keeps every signer on email and uses a
|
|
171
|
+
`virtual` assignment, so no page coordinates or paid notification channel are
|
|
172
|
+
required.
|
|
173
|
+
|
|
174
|
+
### 1. Upload the PDF
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
const uploaded = await client.documents.upload({ filePath: './contract.pdf' });
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The official multipart body contains the `file` part. The SDK also supports a
|
|
181
|
+
display-name override (used as that file part's filename) and an optional JSON
|
|
182
|
+
`metadata` part for deployments that accept it. A successful response is
|
|
183
|
+
`IDocumentUploadResponse`:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
{
|
|
187
|
+
resource?: string;
|
|
188
|
+
id: string;
|
|
189
|
+
account_id: string;
|
|
190
|
+
template_id: string | null;
|
|
191
|
+
name: string;
|
|
192
|
+
status: DocumentStatus;
|
|
193
|
+
assignment?: IAssignment | null;
|
|
194
|
+
artifacts: {
|
|
195
|
+
original: string;
|
|
196
|
+
certificated?: string;
|
|
197
|
+
'certificate-page'?: string;
|
|
198
|
+
pades?: string;
|
|
199
|
+
bundle?: string;
|
|
200
|
+
thumbnail?: string;
|
|
201
|
+
};
|
|
202
|
+
signing_url?: string;
|
|
203
|
+
pages: Array<{ id: string; number: number; height: number; width: number; download_url: string }>;
|
|
204
|
+
tags?: Array<{ id: string; name: string; color?: string | null }>;
|
|
205
|
+
created_at: string;
|
|
206
|
+
updated_at: string;
|
|
207
|
+
is_closed: boolean;
|
|
208
|
+
decline_reason: string | null;
|
|
209
|
+
declined_by: ISigner | null;
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
`DocumentStatus` covers `uploading`, `uploaded`, `metadata_processing`,
|
|
214
|
+
`metadata_ready`, `pending_signature`, `expired`, `certificating`,
|
|
215
|
+
`certificated`, `rejected_by_signer`, `rejected_by_user`, and `failed`.
|
|
216
|
+
|
|
217
|
+
Uploads must be PDFs, at most 25 MB and at most 2,000 pages. The SDK checks the
|
|
218
|
+
extension, size, and `%PDF-` header before sending. A new upload can have an
|
|
219
|
+
empty `pages` array until metadata processing finishes. Wait before creating a
|
|
220
|
+
`collect` assignment because its fields refer to rendered page IDs; a
|
|
221
|
+
`virtual` assignment may be created immediately.
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
const prepared = await client.documents.waitUntilReady(uploaded.id, {
|
|
225
|
+
maxWaitMs: 30_000,
|
|
226
|
+
pollIntervalMs: 2_000,
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### 2. Create or reuse the email signers
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
const signerA = await client.signers.create({
|
|
234
|
+
full_name: 'John Doe',
|
|
235
|
+
email: 'john@example.com',
|
|
236
|
+
});
|
|
237
|
+
const signerB = await client.signers.create({
|
|
238
|
+
full_name: 'Jane Smith',
|
|
239
|
+
email: 'jane@example.com',
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
The wire body is `{ full_name, email }`. Each response is an `ISigner`:
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
{
|
|
247
|
+
resource?: string;
|
|
248
|
+
id: string;
|
|
249
|
+
full_name: string;
|
|
250
|
+
email: string | null;
|
|
251
|
+
whatsapp_phone_number?: string | null;
|
|
252
|
+
cpf?: string | null; // compatibility type; not echoed by the API
|
|
253
|
+
has_accepted_terms?: boolean;
|
|
254
|
+
has_signature?: boolean; // signer-self response only
|
|
255
|
+
has_initial?: boolean; // signer-self response only
|
|
256
|
+
is_signature_reusable?: boolean; // signer-self response only
|
|
257
|
+
metadata?: Record<string, unknown>;
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
When an email is present, `signers.create()` first looks up that email in the
|
|
262
|
+
workspace and reuses the matching signer; a name-only or phone-only request
|
|
263
|
+
always creates a new signer.
|
|
264
|
+
|
|
265
|
+
### 3. Price, then request signatures
|
|
266
|
+
|
|
267
|
+
Cost estimation takes channel descriptors, not signer IDs:
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
const estimate = await client.assignments.estimateCost(uploaded.id, {
|
|
271
|
+
method: 'virtual',
|
|
272
|
+
signers: [{}, {}], // `{}` selects Email for each signer
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
if (!estimate.has_sufficient_resources) {
|
|
276
|
+
throw new Error(estimate.blocking_reason ?? estimate.message ?? 'Insufficient resources');
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
The response is `ICostEstimate`:
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
{
|
|
284
|
+
documents: number;
|
|
285
|
+
credits: number;
|
|
286
|
+
needs_extra_document: boolean;
|
|
287
|
+
extra_document_cost: number;
|
|
288
|
+
total_credits: number;
|
|
289
|
+
breakdown: Array<{ code: string; name: string; cost: number; quantity?: number; unit_cost?: number }>;
|
|
290
|
+
document_balance: number;
|
|
291
|
+
credit_balance: number;
|
|
292
|
+
has_sufficient_resources: boolean;
|
|
293
|
+
blocking_reason: 'PendingPayment' | 'InsufficientDocuments' | 'InsufficientCredits' | null;
|
|
294
|
+
message: string | null;
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Create the email assignment only after accepting that estimate:
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
const assignment = await client.assignments.create(uploaded.id, {
|
|
302
|
+
method: 'virtual',
|
|
303
|
+
signers: [
|
|
304
|
+
{ id: signerA.id, verification_method: 'Email', notification_methods: ['Email'] },
|
|
305
|
+
{ id: signerB.id, verification_method: 'Email', notification_methods: ['Email'] },
|
|
306
|
+
],
|
|
307
|
+
message: 'Please review and sign',
|
|
308
|
+
expires_at: '2027-12-31T23:59:00Z',
|
|
309
|
+
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
The request returns an `IAssignment`:
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
{
|
|
316
|
+
resource?: string;
|
|
317
|
+
id: string;
|
|
318
|
+
sender_email?: string;
|
|
319
|
+
method: 'virtual' | 'collect';
|
|
320
|
+
expires_at?: string | null;
|
|
321
|
+
expiration?: string;
|
|
322
|
+
message?: string | null;
|
|
323
|
+
signers: IAssignmentSigner[];
|
|
324
|
+
copy_receivers?: Array<Record<string, unknown>>;
|
|
325
|
+
items?: IAssignmentItem[];
|
|
326
|
+
summary?: {
|
|
327
|
+
signer_count: number;
|
|
328
|
+
completed_count: number;
|
|
329
|
+
signers: Array<ISigner & { completed?: boolean }>;
|
|
330
|
+
};
|
|
331
|
+
signing_urls?: Array<{ signer_id: string; url: string }>;
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
The URLs and delivered messages contain signer credentials; treat them as
|
|
336
|
+
secrets.
|
|
337
|
+
|
|
338
|
+
### 4. Complete the email signer flow
|
|
339
|
+
|
|
340
|
+
Assinafy sends each signer a link containing their access code and sends the
|
|
341
|
+
one-time verification code through the selected channel. Neither value is
|
|
342
|
+
returned as a standalone owner-side API field. A custom signer portal must
|
|
343
|
+
obtain both values from the signer-delivery flow; do not manufacture them or
|
|
344
|
+
log them.
|
|
345
|
+
|
|
346
|
+
```ts
|
|
347
|
+
// Signer-side client: no account API credential is needed or sent.
|
|
348
|
+
const signerClient = new AssinafyClient({
|
|
349
|
+
baseUrl,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
const self = await signerClient.signerDocuments.self(accessCode); // ISignerSelf
|
|
353
|
+
|
|
354
|
+
// Query: signer-access-code=<accessCode>
|
|
355
|
+
// Body: { 'verification-code': '<six-digit code>' }
|
|
356
|
+
await signerClient.signerDocuments.verifyEmail({
|
|
357
|
+
signerAccessCode: accessCode,
|
|
358
|
+
verificationCode,
|
|
359
|
+
}); // Promise<void>
|
|
360
|
+
|
|
361
|
+
const confirmed = await signerClient.signerDocuments.confirmData(
|
|
362
|
+
uploaded.id,
|
|
363
|
+
accessCode,
|
|
364
|
+
{ full_name: self.full_name, email: self.email ?? undefined },
|
|
365
|
+
); // ISigner
|
|
366
|
+
|
|
367
|
+
const signable = await signerClient.signerDocuments.getAssignment(accessCode, true);
|
|
368
|
+
// `getAssignment` returns IDocumentDetailsResponse and records that the signer
|
|
369
|
+
// viewed the assignment. Do not issue it merely as a health check.
|
|
370
|
+
|
|
371
|
+
await signerClient.signerDocuments.signMultiple([signable.id], accessCode);
|
|
372
|
+
// Wire body: { document_ids: [signable.id] }; acknowledgement has no data.
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Repeat this phase separately for each signer with that signer's own access code
|
|
376
|
+
and one-time code. The two signers in this example share the default step and
|
|
377
|
+
can sign in parallel.
|
|
378
|
+
|
|
379
|
+
`signMultiple` is only for `virtual` assignments. For `collect`, read
|
|
380
|
+
`signable.assignment.items`, then call `sign(documentId, assignmentId,
|
|
381
|
+
accessCode, entries)` with a non-empty array of
|
|
382
|
+
`{ itemId, fieldId, pageId, value }`. A virtual signer must confirm their data
|
|
383
|
+
before signing. A `DigitalCertificate` signer cannot call `sign`; that branch
|
|
384
|
+
uses Assinafy's certificate-start and certificate-complete flow, which is not
|
|
385
|
+
part of this SDK's current 89-operation surface.
|
|
386
|
+
|
|
387
|
+
### 5. Observe completion and download artifacts
|
|
388
|
+
|
|
389
|
+
Subscribe to `document_ready` for event-driven completion, or fetch
|
|
390
|
+
`documents.details(documentId)` until `status === 'certificated'`. Webhook
|
|
391
|
+
deliveries can repeat, so use their numeric `id` as an
|
|
392
|
+
idempotency key. Once complete:
|
|
393
|
+
|
|
394
|
+
```ts
|
|
395
|
+
const finalDocument = await client.documents.details(uploaded.id);
|
|
396
|
+
const signedPdf = await client.documents.download(uploaded.id, 'certificated');
|
|
397
|
+
const certificatePage = await client.documents.download(uploaded.id, 'certificate-page');
|
|
398
|
+
const bundleZip = await client.documents.download(uploaded.id, 'bundle');
|
|
399
|
+
|
|
400
|
+
// Validate an Assinafy signature hash when your workflow has extracted it.
|
|
401
|
+
const validation = await client.documents.verify(documentSignatureHash);
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
`original`, `certificated`, and `certificate-page` are PDFs. `bundle` is a ZIP
|
|
405
|
+
containing those three artifacts and also `pades` when the document had an
|
|
406
|
+
ICP-Brasil certificate signer. The `pades` PDF exists only for documents that
|
|
407
|
+
had certificate signers. An artifact can return `404` before generation has
|
|
408
|
+
finished. `decline_reason` is included in document details only when the access
|
|
409
|
+
token belongs to the document creator.
|
|
410
|
+
|
|
411
|
+
## Resource reference
|
|
152
412
|
|
|
153
413
|
Most account-scoped methods accept an optional `accountId` that overrides the
|
|
154
414
|
client default. Workspace `get`, `update`, `delete`, branding, and statistics
|
|
@@ -162,13 +422,15 @@ const doc = await client.documents.upload(
|
|
|
162
422
|
{ filePath: './contract.pdf' },
|
|
163
423
|
{ name: 'Service agreement', metadata: { type: 'service' } },
|
|
164
424
|
);
|
|
425
|
+
// `name` and `metadata` are compatibility multipart parts outside the published
|
|
426
|
+
// file-only request schema.
|
|
165
427
|
// `name` is optional and defaults to the file's own name. The API derives the
|
|
166
428
|
// display name from the uploaded filename and appends `.pdf` when absent, so
|
|
167
429
|
// the document above is stored as 'Service agreement.pdf'. Accents are
|
|
168
430
|
// transliterated by the API ('Contrato de Serviço' → 'Contrato de Servico.pdf').
|
|
169
431
|
// → {
|
|
170
432
|
// resource: 'document', id: '1031…', account_id: '102d…', template_id: null,
|
|
171
|
-
// name: '
|
|
433
|
+
// name: 'Service agreement.pdf', status: 'uploaded',
|
|
172
434
|
// artifacts: { original: 'https://…/download/original' },
|
|
173
435
|
// signing_url: 'https://app…/sign/1031…',
|
|
174
436
|
// pages: [], // populated once status reaches `metadata_ready`
|
|
@@ -179,7 +441,7 @@ const doc = await client.documents.upload(
|
|
|
179
441
|
await client.documents.upload({ buffer, fileName: 'contract.pdf' });
|
|
180
442
|
|
|
181
443
|
// List → { data: IDocumentListItem[], meta?: { current_page, per_page, total, last_page } }
|
|
182
|
-
const { data, meta } = await client.documents.list({ page: 1, per_page: 20, sort: '
|
|
444
|
+
const { data, meta } = await client.documents.list({ page: 1, per_page: 20, sort: 'updated_at' });
|
|
183
445
|
|
|
184
446
|
// Search is the lightweight alternative to list: same item shape, but the API
|
|
185
447
|
// skips the expanded `assignment`/`pages`. Prefer it for name lookups.
|
|
@@ -194,7 +456,11 @@ await client.documents.waitUntilReady(doc.id, { maxWaitMs: 30_000 });
|
|
|
194
456
|
// (Passing `name` to upload() avoids both the round-trip and the race.)
|
|
195
457
|
await client.documents.rename(doc.id, 'Signed service agreement.pdf');
|
|
196
458
|
|
|
197
|
-
await client.documents.download(doc.id, 'certificated'); //
|
|
459
|
+
await client.documents.download(doc.id, 'certificated'); // signed PDF
|
|
460
|
+
await client.documents.download(doc.id, 'certificate-page');
|
|
461
|
+
await client.documents.download(doc.id, 'bundle'); // ZIP
|
|
462
|
+
// `pades` exists only when at least one signer used DigitalCertificate.
|
|
463
|
+
await client.documents.download(doc.id, 'pades');
|
|
198
464
|
await client.documents.thumbnail(doc.id);
|
|
199
465
|
await client.documents.downloadPage(doc.id, pageId);
|
|
200
466
|
|
|
@@ -222,11 +488,18 @@ const urgentTag = await client.tags.create({ name: 'Urgent' });
|
|
|
222
488
|
await client.documents.listTags(doc.id);
|
|
223
489
|
await client.documents.replaceTags(doc.id, [contractsTag.id, quarterTag.id]); // [] detaches all
|
|
224
490
|
await client.documents.addTags(doc.id, [urgentTag.id]); // append
|
|
225
|
-
await client.documents.detachTag(doc.id, urgentTag.id);
|
|
491
|
+
await client.documents.detachTag(doc.id, urgentTag.id); // → { detached: true }
|
|
226
492
|
```
|
|
227
493
|
|
|
228
494
|
Uploads are validated locally: only `.pdf` files up to 25 MB whose bytes begin
|
|
229
|
-
with the PDF magic header (`%PDF-`) are accepted
|
|
495
|
+
with the PDF magic header (`%PDF-`) are accepted. The API also limits documents
|
|
496
|
+
to 2,000 pages.
|
|
497
|
+
|
|
498
|
+
Page and artifact URLs embedded in JSON responses still require the same
|
|
499
|
+
account authentication as their download operations. Prefer
|
|
500
|
+
`documents.downloadPage()` and `documents.download()` so the SDK applies the
|
|
501
|
+
credential and returns a `Buffer`. `bundle` contains `original`, `certificated`,
|
|
502
|
+
and `certificate-page`, plus `pades` when available.
|
|
230
503
|
|
|
231
504
|
List endpoints return `{ data, meta }` where `meta` is populated from the `X-Pagination-*` headers returned by the API.
|
|
232
505
|
|
|
@@ -236,38 +509,36 @@ List endpoints return `{ data, meta }` where `meta` is populated from the `X-Pag
|
|
|
236
509
|
await client.signers.create({
|
|
237
510
|
full_name: 'John Doe',
|
|
238
511
|
email: 'john@example.com',
|
|
239
|
-
|
|
240
|
-
cpf: '123.456.789-00', // optional Brazilian tax ID — non-digits are stripped automatically
|
|
512
|
+
cpf: '123.456.789-00', // legacy compatibility input; non-digits are stripped
|
|
241
513
|
});
|
|
242
514
|
// → { id: '19e6…', full_name: 'John Doe', email: 'john@example.com',
|
|
243
|
-
// whatsapp_phone_number:
|
|
515
|
+
// whatsapp_phone_number: null, has_accepted_terms: false }
|
|
244
516
|
// (note: `cpf` is accepted on input but never echoed back by the API)
|
|
245
517
|
|
|
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.
|
|
518
|
+
// Both contacts are optional. A name-only signer cannot be notified until a
|
|
519
|
+
// contact is added.
|
|
253
520
|
await client.signers.create({ full_name: 'Contact Pending' });
|
|
254
521
|
|
|
255
|
-
// PHP SDK compatibility aliases are also accepted
|
|
256
522
|
await client.signers.create({
|
|
257
523
|
full_name: 'Jane Doe',
|
|
258
524
|
email: 'jane@example.com',
|
|
259
|
-
phone: '+5548999991111', // alias for whatsapp_phone_number
|
|
260
525
|
});
|
|
261
526
|
|
|
262
527
|
await client.signers.get(signerId);
|
|
263
528
|
await client.signers.list({ page: 1, per_page: 50, search: 'john' });
|
|
264
|
-
await client.signers.update(signerId, {
|
|
529
|
+
await client.signers.update(signerId, {
|
|
530
|
+
full_name: 'Johnny Doe',
|
|
531
|
+
government_id: '390.533.447-05', // official update field; sent as digits
|
|
532
|
+
});
|
|
265
533
|
await client.signers.delete(signerId);
|
|
266
534
|
|
|
267
535
|
const existing = await client.signers.findByEmail('john@example.com');
|
|
268
536
|
```
|
|
269
537
|
|
|
270
|
-
When an `email` is supplied, `signers.create()` is idempotent by email
|
|
538
|
+
When an `email` is supplied, `signers.create()` is idempotent by email: it
|
|
539
|
+
reuses an existing signer when the same email is already present in the
|
|
540
|
+
workspace. Signers without email are always created fresh. See
|
|
541
|
+
[Paid signing branches](#paid-signing-branches) for phone-only signers.
|
|
271
542
|
|
|
272
543
|
### Assignments
|
|
273
544
|
|
|
@@ -281,8 +552,8 @@ await client.assignments.create(documentId, {
|
|
|
281
552
|
method: 'virtual',
|
|
282
553
|
signers: ['signer-1', 'signer-2'],
|
|
283
554
|
message: 'Please review and sign',
|
|
284
|
-
expires_at: '
|
|
285
|
-
copy_receivers: ['
|
|
555
|
+
expires_at: '2027-12-31T23:59:00Z',
|
|
556
|
+
copy_receivers: ['copy-recipient-signer-id'],
|
|
286
557
|
});
|
|
287
558
|
|
|
288
559
|
// Sequential signing: `step` controls signing order (parallel within a step).
|
|
@@ -294,31 +565,47 @@ await client.assignments.create(documentId, {
|
|
|
294
565
|
],
|
|
295
566
|
});
|
|
296
567
|
|
|
568
|
+
// Collect fields use 150-DPI page-image pixels measured from the upper-left.
|
|
569
|
+
await client.assignments.create(documentId, {
|
|
570
|
+
method: 'collect',
|
|
571
|
+
signers: [{ id: signerId }],
|
|
572
|
+
entries: [{
|
|
573
|
+
page_id: pageId,
|
|
574
|
+
fields: [{
|
|
575
|
+
signer_id: signerId,
|
|
576
|
+
field_id: fieldId,
|
|
577
|
+
display_settings: {
|
|
578
|
+
left: 69, top: 282, width: 421, height: 45.86, fontSize: 22,
|
|
579
|
+
fontFamily: 'Arial', backgroundColor: '#D5EBFF',
|
|
580
|
+
},
|
|
581
|
+
}],
|
|
582
|
+
}],
|
|
583
|
+
});
|
|
584
|
+
|
|
297
585
|
// Estimate cost (the endpoint prices channel descriptors, not signer IDs) → ICostEstimate
|
|
298
586
|
await client.assignments.estimateCost(documentId, { signers: [{}] }); // default Email
|
|
299
|
-
await client.assignments.estimateCost(documentId, {
|
|
300
|
-
signers: [{ verification_method: 'Whatsapp' }],
|
|
301
|
-
});
|
|
302
587
|
// → {
|
|
303
588
|
// documents: 1, credits: 0, needs_extra_document: false, extra_document_cost: 0,
|
|
304
589
|
// total_credits: 0, breakdown: [], document_balance: 67, credit_balance: 0,
|
|
305
590
|
// has_sufficient_resources: true, blocking_reason: null, message: null
|
|
306
591
|
// }
|
|
307
592
|
|
|
308
|
-
await client.assignments.resetExpiration(documentId, assignmentId, '
|
|
309
|
-
|
|
593
|
+
await client.assignments.resetExpiration(documentId, assignmentId, '2027-06-30T00:00:00Z');
|
|
594
|
+
// Compatibility only: the published request requires a date-time string.
|
|
595
|
+
// Confirm target support before using `null` to clear an expiration.
|
|
596
|
+
await client.assignments.resetExpiration(documentId, assignmentId, null);
|
|
310
597
|
|
|
311
598
|
await client.assignments.resendNotification(documentId, assignmentId, signerId);
|
|
312
599
|
// → { is_sent: true, document_id: '…', signer_id: '…' }
|
|
313
600
|
|
|
314
|
-
await client.assignments.estimateResendCost(documentId, assignmentId, signerId);
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
|
|
318
|
-
await client.assignments.listWhatsAppNotifications(documentId, assignmentId); // → IWhatsAppNotification[]
|
|
601
|
+
const resendCost = await client.assignments.estimateResendCost(documentId, assignmentId, signerId);
|
|
602
|
+
// Official response: ICostEstimate. Older deployments can return the compact
|
|
603
|
+
// IResendCostEstimate branch with `total` and `has_sufficient_credits`; narrow
|
|
604
|
+
// with `'total_credits' in resendCost` before reading branch-specific fields.
|
|
319
605
|
```
|
|
320
606
|
|
|
321
|
-
The `create` response is an `IAssignment`: `{ id, method, signers: [...],
|
|
607
|
+
The `create` response is an `IAssignment`: `{ id, method, signers: [...],
|
|
608
|
+
items: [{ display_settings, ... }], signing_urls: [{ signer_id, url }], … }`.
|
|
322
609
|
|
|
323
610
|
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
611
|
|
|
@@ -329,15 +616,92 @@ await client.documents.delete(documentId); // w
|
|
|
329
616
|
await client.signerDocuments.decline(documentId, assignmentId, accessCode, 'No longer needed'); // signer-side
|
|
330
617
|
```
|
|
331
618
|
|
|
619
|
+
### Paid signing branches
|
|
620
|
+
|
|
621
|
+
Keep the email flow as the default. Enable either branch below only after the
|
|
622
|
+
workspace has the required plan or feature and the returned cost estimate is
|
|
623
|
+
acceptable.
|
|
624
|
+
|
|
625
|
+
#### WhatsApp verification and notification
|
|
626
|
+
|
|
627
|
+
WhatsApp is available only on paid subscriptions and costs 0.45 credit per
|
|
628
|
+
notification. Create a phone-only signer or add a phone to an existing signer,
|
|
629
|
+
then request the `Whatsapp` channel explicitly:
|
|
630
|
+
|
|
631
|
+
```ts
|
|
632
|
+
const phoneSigner = await client.signers.create({
|
|
633
|
+
full_name: 'Mobile Signer',
|
|
634
|
+
whatsapp_phone_number: '+5511999990000',
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
const whatsappCost = await client.assignments.estimateCost(documentId, {
|
|
638
|
+
method: 'virtual',
|
|
639
|
+
signers: [{ verification_method: 'Whatsapp', notification_methods: ['Whatsapp'] }],
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
const whatsappAssignment = await client.assignments.create(documentId, {
|
|
643
|
+
method: 'virtual',
|
|
644
|
+
signers: [{
|
|
645
|
+
id: phoneSigner.id,
|
|
646
|
+
verification_method: 'Whatsapp',
|
|
647
|
+
notification_methods: ['Whatsapp'],
|
|
648
|
+
}],
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
const notices = await client.assignments.listWhatsAppNotifications(
|
|
652
|
+
documentId,
|
|
653
|
+
whatsappAssignment.id,
|
|
654
|
+
);
|
|
655
|
+
// IWhatsAppNotification[]:
|
|
656
|
+
// [{ sent_at, header, body, buttons: [{ text, url? }], phone_number, signer_id }]
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
The high-level helper selects this paid branch for a signer that has a phone
|
|
660
|
+
number but no email. Button URLs can contain signer credentials; do not log or
|
|
661
|
+
forward them outside the signing flow.
|
|
662
|
+
|
|
663
|
+
#### ICP-Brasil digital certificate
|
|
664
|
+
|
|
665
|
+
`DigitalCertificate` requires the account feature, a CPF or CNPJ in the
|
|
666
|
+
signer's `government_id`, and exactly one certificate signer in that signing
|
|
667
|
+
step. It costs two credits per certificate signer in addition to the selected
|
|
668
|
+
notification cost.
|
|
669
|
+
|
|
670
|
+
```ts
|
|
671
|
+
const certificateSigner = await client.signers.update(signerId, {
|
|
672
|
+
government_id: '390.533.447-05',
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
const certificateCost = await client.assignments.estimateCost(documentId, {
|
|
676
|
+
method: 'virtual',
|
|
677
|
+
signers: [{ verification_method: 'DigitalCertificate', notification_methods: ['Email'] }],
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
await client.assignments.create(documentId, {
|
|
681
|
+
method: 'virtual',
|
|
682
|
+
signers: [{
|
|
683
|
+
id: certificateSigner.id,
|
|
684
|
+
step: 1,
|
|
685
|
+
verification_method: 'DigitalCertificate',
|
|
686
|
+
notification_methods: ['Email'],
|
|
687
|
+
}],
|
|
688
|
+
});
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
Before opening the assignment, the signer must confirm identity data and accept
|
|
692
|
+
terms with `confirmData(..., { has_accepted_terms: true })` or `acceptTerms()`.
|
|
693
|
+
The regular `sign()` endpoint rejects certificate signers; they complete the
|
|
694
|
+
ICP-Brasil flow through Assinafy's browser integration. After completion,
|
|
695
|
+
`documents.download(documentId, 'pades')` returns the qualified PAdES artifact.
|
|
696
|
+
|
|
332
697
|
### Templates
|
|
333
698
|
|
|
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.
|
|
699
|
+
`templates.list()` is part of the current OpenAPI document. Existing
|
|
700
|
+
integrations can also use five template-management routes—`create`, `get`,
|
|
701
|
+
`update`, `delete`, and `downloadPage`—that are absent from that document. See
|
|
702
|
+
[compatibility notes](docs/COMPATIBILITY.md#template-management-extensions).
|
|
703
|
+
Template status casing can vary by deployment; normalize with
|
|
704
|
+
`template.status.toLowerCase()` when branching on it.
|
|
341
705
|
|
|
342
706
|
```ts
|
|
343
707
|
// Create a template by uploading a PDF (multipart). The template starts in
|
|
@@ -383,8 +747,14 @@ await client.documents.estimateCostFromTemplate(templateId, [
|
|
|
383
747
|
// has_sufficient_resources: true, blocking_reason: null, breakdown: [], … }
|
|
384
748
|
```
|
|
385
749
|
|
|
750
|
+
Template signer descriptors also accept
|
|
751
|
+
`verification_method: 'DigitalCertificate'` with the same prerequisites under
|
|
752
|
+
[ICP-Brasil digital certificate](#icp-brasil-digital-certificate).
|
|
753
|
+
|
|
386
754
|
Template creation only uploads the PDF and provisions the default editor role —
|
|
387
755
|
configure roles/fields in the Assinafy editor (or the web UI) afterwards.
|
|
756
|
+
The `download_url` values in template page objects are protected URLs; prefer
|
|
757
|
+
`templates.downloadPage()` so the API credential is attached.
|
|
388
758
|
|
|
389
759
|
### Tags
|
|
390
760
|
|
|
@@ -444,7 +814,8 @@ await client.workspaces.getStats(accountId, {
|
|
|
444
814
|
});
|
|
445
815
|
|
|
446
816
|
await client.workspaces.delete(accountId);
|
|
447
|
-
//
|
|
817
|
+
// `force` cancels an active paid subscription as part of account deletion. It
|
|
818
|
+
// is not a general bypass for unrelated deletion restrictions.
|
|
448
819
|
await client.workspaces.delete(restrictedAccountId, { force: true });
|
|
449
820
|
```
|
|
450
821
|
|
|
@@ -466,7 +837,7 @@ await client.fields.validate(fieldId, '400.676.228-36', { signerAccessCode });
|
|
|
466
837
|
await client.fields.validateMultiple(
|
|
467
838
|
[
|
|
468
839
|
{ field_id: 'f1', value: '1111111111111' },
|
|
469
|
-
{ field_id: 'f2', value: '
|
|
840
|
+
{ field_id: 'f2', value: 'value@example.com' },
|
|
470
841
|
],
|
|
471
842
|
{ signerAccessCode },
|
|
472
843
|
);
|
|
@@ -513,8 +884,17 @@ const daily = await client.users.getStats({
|
|
|
513
884
|
granularity: 'daily',
|
|
514
885
|
month: '2026-06',
|
|
515
886
|
});
|
|
516
|
-
// Each row includes period,
|
|
517
|
-
//
|
|
887
|
+
// Each row includes period, upload/send/certification totals, notification
|
|
888
|
+
// counts for email/WhatsApp/bypass, verification counts for
|
|
889
|
+
// email/WhatsApp/bypass/digital-certificate, viewed, and completed counts.
|
|
890
|
+
|
|
891
|
+
const preferences = await client.users.getNotificationPreferences();
|
|
892
|
+
await client.users.updateNotificationPreferences({
|
|
893
|
+
SignerDeclined: false,
|
|
894
|
+
DocumentExpired: false,
|
|
895
|
+
});
|
|
896
|
+
// Updates merge: omitted keys keep their current value. Both methods return
|
|
897
|
+
// the complete nine-key notification preference map.
|
|
518
898
|
```
|
|
519
899
|
|
|
520
900
|
### Webhooks
|
|
@@ -523,6 +903,7 @@ const daily = await client.users.getStats({
|
|
|
523
903
|
await client.webhooks.register({
|
|
524
904
|
url: 'https://example.com/webhooks/assinafy',
|
|
525
905
|
email: 'admin@example.com',
|
|
906
|
+
is_active: true,
|
|
526
907
|
// events defaults to the current SDK default set below
|
|
527
908
|
events: [
|
|
528
909
|
'document_ready',
|
|
@@ -533,13 +914,91 @@ await client.webhooks.register({
|
|
|
533
914
|
],
|
|
534
915
|
});
|
|
535
916
|
|
|
536
|
-
await client.webhooks.get(); //
|
|
917
|
+
await client.webhooks.get(); // IWebhookSubscription | null
|
|
537
918
|
await client.webhooks.inactivate(); // stop deliveries (no delete route exists)
|
|
538
919
|
await client.webhooks.listEventTypes();
|
|
539
|
-
await client.webhooks.listDispatches({
|
|
540
|
-
|
|
920
|
+
const history = await client.webhooks.listDispatches({
|
|
921
|
+
delivered: false,
|
|
922
|
+
page: 1,
|
|
923
|
+
'per-page': 20,
|
|
924
|
+
}); // { data: IWebhookDispatch[], meta?: PaginationMeta }
|
|
925
|
+
const retried = await client.webhooks.retryDispatch(dispatchId); // IWebhookDispatch
|
|
541
926
|
```
|
|
542
927
|
|
|
928
|
+
`register` sends `{ events, is_active, url, email }` and returns
|
|
929
|
+
`{ events, is_active, url, email, updated_at? }`. Assinafy delivers each event
|
|
930
|
+
as an HTTP `POST` with `Content-Type: application/json` and `Connection: close`.
|
|
931
|
+
Any `2xx` is success. There are at most two automatic attempts, separated by
|
|
932
|
+
three seconds. After ten consecutive failed events, ordinary delivery pauses
|
|
933
|
+
and about 5% of later events are attempted until one succeeds; use
|
|
934
|
+
`retryDispatch()` for an immediate manual redelivery. The dispatch history
|
|
935
|
+
retains only the first 2,000 characters of the receiver's response body.
|
|
936
|
+
|
|
937
|
+
Each history or retry result is an `IWebhookDispatch`:
|
|
938
|
+
|
|
939
|
+
```ts
|
|
940
|
+
{
|
|
941
|
+
resource?: string;
|
|
942
|
+
id: string;
|
|
943
|
+
event: string;
|
|
944
|
+
activity_id: number;
|
|
945
|
+
endpoint: string | null;
|
|
946
|
+
payload: IWebhookPayload | Record<string, unknown> | null;
|
|
947
|
+
delivered: boolean;
|
|
948
|
+
http_status: number | null;
|
|
949
|
+
response_body: string | null;
|
|
950
|
+
error: string | null;
|
|
951
|
+
created_at: string;
|
|
952
|
+
updated_at?: string;
|
|
953
|
+
}
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
Every delivery body uses this envelope:
|
|
957
|
+
|
|
958
|
+
```ts
|
|
959
|
+
{
|
|
960
|
+
id: number; // use for idempotent processing
|
|
961
|
+
event: string;
|
|
962
|
+
message: string | null;
|
|
963
|
+
payload: Record<string, unknown> | null;
|
|
964
|
+
origin: { ip?: string; 'user-agent'?: string } | null;
|
|
965
|
+
created_at: number; // Unix seconds
|
|
966
|
+
subject: { type: 'User' | 'Signer' | 'Account' | 'Document' | 'Template'; [key: string]: unknown };
|
|
967
|
+
object: { type: 'User' | 'Signer' | 'Account' | 'Document' | 'Template'; [key: string]: unknown };
|
|
968
|
+
account_id: string;
|
|
969
|
+
}
|
|
970
|
+
```
|
|
971
|
+
|
|
972
|
+
Event-specific values are:
|
|
973
|
+
|
|
974
|
+
| `event` | `subject.type` | `object.type` | `payload` keys |
|
|
975
|
+
| --- | --- | --- | --- |
|
|
976
|
+
| `document_uploaded` | `User` | `Document` | — |
|
|
977
|
+
| `document_metadata_ready` | `User` | `Document` | — |
|
|
978
|
+
| `document_prepared` | `User` | `Document` | — |
|
|
979
|
+
| `assignment_created` | `User` | `Document` | `user_name`, `user_email`, `user_telephone` |
|
|
980
|
+
| `document_ready` | `Account` | `Document` | — |
|
|
981
|
+
| `document_processing_failed` | `Account` | `Document` | `error_message` |
|
|
982
|
+
| `signature_requested` | `User` | `Document` | `signer_email`, `signer_full_name`, or `signer_whatsapp_phone_number`, according to channel |
|
|
983
|
+
| `signer_created` | `User` | `Signer` | `signer_full_name` |
|
|
984
|
+
| `signer_email_verified` | `Signer` | `Document` | `signer_email` |
|
|
985
|
+
| `signer_whatsapp_verified` | `Signer` | `Document` | `signer_whatsapp_phone_number` |
|
|
986
|
+
| `signer_data_confirmed` | `Signer` | `Document` | `signer_email` |
|
|
987
|
+
| `signer_viewed_document` | `Signer` | `Document` | `signer_full_name` |
|
|
988
|
+
| `signer_signed_document` | `Signer` | `Document` | `signer_full_name` |
|
|
989
|
+
| `signer_rejected_document` | `Signer` | `Document` | `signer_full_name` |
|
|
990
|
+
| `user_rejected_document` | `User` | `Document` | `user_name` |
|
|
991
|
+
| `template_created` | `User` | `Template` | — |
|
|
992
|
+
| `template_processed` | `User` | `Template` | — |
|
|
993
|
+
| `template_processing_failed` | `Account` | `Template` | `error_message` |
|
|
994
|
+
|
|
995
|
+
`payload`, `subject`, and `object` are event-dependent. Accept unknown fields
|
|
996
|
+
for forward compatibility and acknowledge only after durable, idempotent
|
|
997
|
+
processing. Non-`2xx` responses, timeouts, and connection failures all count as
|
|
998
|
+
failed deliveries. `assignment_created` and `document_metadata_ready` have no
|
|
999
|
+
guaranteed ordering. For account entities, Assinafy removes the `integration`
|
|
1000
|
+
property before delivery.
|
|
1001
|
+
|
|
543
1002
|
### Webhook verification
|
|
544
1003
|
|
|
545
1004
|
`WebhookVerifier` is an opt-in HMAC-SHA256 utility for integrations whose
|
|
@@ -591,7 +1050,6 @@ only for compatibility with deployments that still expect the legacy query.
|
|
|
591
1050
|
|
|
592
1051
|
```ts
|
|
593
1052
|
await client.signerDocuments.self(accessCode);
|
|
594
|
-
await client.signerDocuments.acceptTerms(accessCode);
|
|
595
1053
|
await client.signerDocuments.verifyEmail({ signerAccessCode: accessCode, verificationCode: '123456' });
|
|
596
1054
|
|
|
597
1055
|
await client.signerDocuments.getCurrent(signerId, accessCode);
|
|
@@ -599,45 +1057,57 @@ const { data } = await client.signerDocuments.list(signerId, accessCode, { per_p
|
|
|
599
1057
|
// Signer-side counterpart of documents.search(), authorised by the access code.
|
|
600
1058
|
const found = await client.signerDocuments.search(signerId, accessCode, 'invoice');
|
|
601
1059
|
await client.signerDocuments.download(signerId, documentId, 'original');
|
|
1060
|
+
// Available only after an ICP-Brasil certificate signer completes signing.
|
|
1061
|
+
await client.signerDocuments.download(signerId, documentId, 'pades');
|
|
602
1062
|
|
|
603
1063
|
await client.signerDocuments.confirmData(documentId, accessCode, {
|
|
604
1064
|
email: 'me@example.com',
|
|
605
1065
|
full_name: 'Example Signer',
|
|
606
1066
|
government_id: '123.456.789-00',
|
|
1067
|
+
has_accepted_terms: true,
|
|
607
1068
|
});
|
|
608
|
-
|
|
1069
|
+
// Alternatively, accept terms separately before getAssignment():
|
|
1070
|
+
// await client.signerDocuments.acceptTerms(accessCode);
|
|
609
1071
|
|
|
610
1072
|
// Signature image management ({ reuse: true } persists it for future documents)
|
|
611
1073
|
await client.signerDocuments.uploadSignature(accessCode, pngBuffer, { imageType: 'signature', reuse: true });
|
|
612
1074
|
await client.signerDocuments.downloadSignature(accessCode, 'signature');
|
|
613
1075
|
|
|
614
1076
|
// Sign / decline
|
|
615
|
-
const
|
|
1077
|
+
const signable = await client.signerDocuments.getAssignment(accessCode);
|
|
1078
|
+
// `sign()` is for collect assignments and requires every placed field value.
|
|
616
1079
|
await client.signerDocuments.sign(documentId, assignmentId, accessCode, [
|
|
617
1080
|
{ itemId, fieldId, pageId, value: 'Signed by John' },
|
|
618
1081
|
]);
|
|
619
1082
|
await client.signerDocuments.decline(documentId, assignmentId, accessCode, 'Not authorized');
|
|
620
1083
|
|
|
621
|
-
//
|
|
1084
|
+
// `signMultiple()` is for virtual assignments only.
|
|
622
1085
|
await client.signerDocuments.signMultiple(['doc-1', 'doc-2'], accessCode);
|
|
623
1086
|
await client.signerDocuments.declineMultiple(['doc-1'], 'Unfavorable terms', accessCode);
|
|
624
1087
|
```
|
|
625
1088
|
|
|
1089
|
+
`sign()` also requires virtual signers to have confirmed their data first, but
|
|
1090
|
+
virtual assignments should normally use `signMultiple()`. Certificate signers
|
|
1091
|
+
cannot use `sign()`; see [Paid signing branches](#paid-signing-branches).
|
|
1092
|
+
|
|
626
1093
|
## High-level helper
|
|
627
1094
|
|
|
628
|
-
Uploads a PDF,
|
|
1095
|
+
Uploads a PDF, reuses or creates signers by email, creates a virtual assignment
|
|
1096
|
+
immediately, and optionally waits for processing before returning.
|
|
629
1097
|
|
|
630
1098
|
```ts
|
|
631
1099
|
const result = await client.uploadAndRequestSignatures({
|
|
632
1100
|
source: { filePath: './contract.pdf' },
|
|
633
1101
|
signers: [
|
|
634
1102
|
{ name: 'John', email: 'john@example.com' },
|
|
635
|
-
{ name: 'Jane', email: 'jane@example.com'
|
|
1103
|
+
{ name: 'Jane', email: 'jane@example.com' },
|
|
636
1104
|
],
|
|
637
1105
|
message: 'Please sign',
|
|
638
|
-
metadata: { year: 2026 },
|
|
1106
|
+
metadata: { year: 2026 }, // compatibility upload part; omit for file-only wire format
|
|
639
1107
|
waitForReady: true,
|
|
640
|
-
|
|
1108
|
+
waitOptions: { maxWaitMs: 30_000, pollIntervalMs: 1_000 },
|
|
1109
|
+
expiresAt: '2027-12-31T00:00:00Z',
|
|
1110
|
+
copyReceivers: ['existing-copy-recipient-signer-id'],
|
|
641
1111
|
});
|
|
642
1112
|
|
|
643
1113
|
result.document; // fully-processed IDocumentDetailsResponse (waitForReady: true, the default);
|
|
@@ -646,10 +1116,20 @@ result.assignment; // IAssignment
|
|
|
646
1116
|
result.signer_ids; // string[]
|
|
647
1117
|
```
|
|
648
1118
|
|
|
649
|
-
`waitForReady: false` skips
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
1119
|
+
`waitForReady: false` skips post-assignment polling and returns the initial
|
|
1120
|
+
upload response. With the default `true`, the helper creates the assignment first and
|
|
1121
|
+
then waits for the current document details. Both production and sandbox allow
|
|
1122
|
+
virtual assignments in `uploaded` and `metadata_processing` and promote them
|
|
1123
|
+
automatically; only `collect` assignments require rendered pages.
|
|
1124
|
+
Every signer above uses the default email channel. A phone-only signer selects
|
|
1125
|
+
the paid WhatsApp branch described earlier. `copyReceivers` accepts existing
|
|
1126
|
+
signer IDs, not email addresses; check the returned assignment before treating
|
|
1127
|
+
a copy receiver as registered.
|
|
1128
|
+
|
|
1129
|
+
The helper is not transactional. A post-assignment polling error includes the
|
|
1130
|
+
created `documentId`, `assignmentId`, and `signerIds` in its `context` (and in
|
|
1131
|
+
`ValidationError.errors` for timeouts); inspect those IDs before deciding
|
|
1132
|
+
whether to retry the workflow.
|
|
653
1133
|
|
|
654
1134
|
## Errors
|
|
655
1135
|
|
|
@@ -674,47 +1154,10 @@ try {
|
|
|
674
1154
|
}
|
|
675
1155
|
```
|
|
676
1156
|
|
|
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
1157
|
## Development
|
|
715
1158
|
|
|
716
1159
|
```bash
|
|
717
|
-
bun install
|
|
1160
|
+
bun install --frozen-lockfile
|
|
718
1161
|
bun run typecheck # source, script, and test type checks
|
|
719
1162
|
bun run lint
|
|
720
1163
|
bun test # bun:test suites
|