@assinafy/sdk 2.0.0 → 2.1.1

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.
@@ -0,0 +1,209 @@
1
+ # API coverage
2
+
3
+ This ledger maps the official Assinafy OpenAPI document to the public SDK API.
4
+ It was reconciled on 2026-08-06 against
5
+ [`GET /v1/docs/openapi.json`](https://api.assinafy.com.br/v1/docs/openapi.json):
6
+ 68 paths, 89 HTTP operations, and 37 component schemas. The audited response's
7
+ SHA-256 digest is
8
+ `7e5957082002e8e96c5abc2cadf7b4b463eaa5bd61b76e26f64b90a8b922088c`.
9
+
10
+ All paths below include the `/v1` prefix shown by the OpenAPI document. The
11
+ SDK's default `baseUrl` already ends in `/v1`, so resource implementations use
12
+ the corresponding relative path without repeating that prefix.
13
+
14
+ Status meanings:
15
+
16
+ - **Covered** — a public, typed SDK method sends the operation.
17
+ - **URL helper** — the operation is a browser redirect/callback; the SDK builds
18
+ its absolute URL instead of following it inside the server process.
19
+ - **Live extension** — verified route retained for compatibility, but absent
20
+ from the current OpenAPI path set and excluded from the 89-operation total.
21
+
22
+ ## Accounts — 10/10
23
+
24
+ | Method | Path | SDK method | Status |
25
+ | --- | --- | --- | --- |
26
+ | `GET` | `/v1/accounts` | `client.workspaces.list()` | Covered |
27
+ | `POST` | `/v1/accounts` | `client.workspaces.create(payload)` | Covered |
28
+ | `GET` | `/v1/accounts/{accountId}` | `client.workspaces.get(accountId)` | Covered |
29
+ | `PUT` | `/v1/accounts/{accountId}` | `client.workspaces.update(accountId, payload)` | Covered |
30
+ | `DELETE` | `/v1/accounts/{accountId}` | `client.workspaces.delete(accountId, options?)` | Covered |
31
+ | `GET` | `/v1/accounts/{accountId}/theme` | `client.workspaces.getTheme(accountId)` | Covered |
32
+ | `GET` | `/v1/accounts/{accountId}/logo` | `client.workspaces.downloadLogo(accountId)` | Covered |
33
+ | `POST` | `/v1/accounts/{accountId}/logo` | `client.workspaces.uploadLogo(accountId, source)` | Covered |
34
+ | `DELETE` | `/v1/accounts/{accountId}/logo` | `client.workspaces.deleteLogo(accountId)` | Covered |
35
+ | `GET` | `/v1/accounts/{accountId}/stats` | `client.workspaces.getStats(accountId, params?)` | Covered |
36
+
37
+ ## Assignments — 7/7
38
+
39
+ | Method | Path | SDK method | Status |
40
+ | --- | --- | --- | --- |
41
+ | `GET` | `/v1/assignments` | `client.assignments.list(params?, accountId?)` | Covered |
42
+ | `POST` | `/v1/documents/{documentId}/assignments` | `client.assignments.create(documentId, payload)` | Covered |
43
+ | `POST` | `/v1/documents/{documentId}/assignments/estimate-cost` | `client.assignments.estimateCost(documentId, payload)` | Covered |
44
+ | `PUT` | `/v1/documents/{documentId}/assignments/{assignmentId}/signers/{signerId}/resend` | `client.assignments.resendNotification(documentId, assignmentId, signerId)` | Covered |
45
+ | `POST` | `/v1/documents/{documentId}/assignments/{assignmentId}/signers/{signerId}/estimate-resend-cost` | `client.assignments.estimateResendCost(documentId, assignmentId, signerId)` | Covered |
46
+ | `PUT` | `/v1/documents/{documentId}/assignments/{assignmentId}/reset-expiration` | `client.assignments.resetExpiration(documentId, assignmentId, expiresAt)` | Covered |
47
+ | `GET` | `/v1/documents/{documentId}/assignments/{assignmentId}/whatsapp-notifications` | `client.assignments.listWhatsAppNotifications(documentId, assignmentId)` | Covered |
48
+
49
+ ## Authentication — 11/11
50
+
51
+ | Method | Path | SDK method | Status |
52
+ | --- | --- | --- | --- |
53
+ | `GET` | `/v1/auth/authenticate` | `client.auth.getSocialLoginUrl(authClient?)` | URL helper |
54
+ | `GET` | `/v1/login-callback` | `client.auth.getSocialLoginCallbackUrl()` | URL helper |
55
+ | `POST` | `/v1/login` | `client.auth.login(email, password)` | Covered |
56
+ | `PUT` | `/v1/authentication/request-password-reset` | `client.auth.requestPasswordReset(email)` | Covered |
57
+ | `PUT` | `/v1/authentication/reset-password` | `client.auth.resetPassword(payload)` | Covered |
58
+ | `PUT` | `/v1/authentication/change-password` | `client.auth.changePassword(payload)` | Covered |
59
+ | `POST` | `/v1/authentication/social-login` | `client.auth.socialLogin(payload)` | Covered |
60
+ | `POST` | `/v1/auth/link-social-login` | `client.auth.linkSocialLogin(payload)` | Covered |
61
+ | `GET` | `/v1/users/api-keys` | `client.auth.getApiKey()` | Covered |
62
+ | `POST` | `/v1/users/api-keys` | `client.auth.createApiKey(password)` | Covered |
63
+ | `DELETE` | `/v1/users/api-keys` | `client.auth.deleteApiKey()` | Covered |
64
+
65
+ ## Documents — 18/18
66
+
67
+ | Method | Path | SDK method | Status |
68
+ | --- | --- | --- | --- |
69
+ | `GET` | `/v1/accounts/{accountId}/documents` | `client.documents.list(params?, accountId?)` | Covered |
70
+ | `POST` | `/v1/accounts/{accountId}/documents` | `client.documents.upload(source, options?)` | Covered |
71
+ | `GET` | `/v1/accounts/{accountId}/documents/search` | `client.documents.search(params?, accountId?)` | Covered |
72
+ | `GET` | `/v1/documents/statuses` | `client.documents.statuses()` | Covered |
73
+ | `GET` | `/v1/documents/{documentId}` | `client.documents.details(documentId)` / `get(documentId)` | Covered |
74
+ | `DELETE` | `/v1/documents/{documentId}` | `client.documents.delete(documentId)` | Covered |
75
+ | `PATCH` | `/v1/documents/{documentId}` | `client.documents.rename(documentId, name)` | Covered |
76
+ | `GET` | `/v1/documents/{documentId}/activities` | `client.documents.activities(documentId)` | Covered |
77
+ | `GET` | `/v1/documents/{documentId}/download/{artifactName}` | `client.documents.download(documentId, artifactName)` | Covered |
78
+ | `GET` | `/v1/documents/{documentSignatureHash}/verify` | `client.documents.verify(documentSignatureHash)` | Covered |
79
+ | `GET` | `/v1/documents/{documentId}/thumbnail` | `client.documents.thumbnail(documentId)` | Covered |
80
+ | `GET` | `/v1/documents/{documentId}/pages/{pageId}/download` | `client.documents.downloadPage(documentId, pageId)` | Covered |
81
+ | `GET` | `/v1/accounts/{accountId}/documents/{documentId}/tags` | `client.documents.listTags(documentId, accountId?)` | Covered |
82
+ | `PUT` | `/v1/accounts/{accountId}/documents/{documentId}/tags` | `client.documents.replaceTags(documentId, tagIds, accountId?)` | Covered |
83
+ | `POST` | `/v1/accounts/{accountId}/documents/{documentId}/tags` | `client.documents.addTags(documentId, tagIds, accountId?)` | Covered |
84
+ | `DELETE` | `/v1/accounts/{accountId}/documents/{documentId}/tags/{tagId}` | `client.documents.detachTag(documentId, tagId, accountId?)` | Covered |
85
+ | `POST` | `/v1/accounts/{accountId}/templates/{templateId}/documents` | `client.documents.createFromTemplate(templateId, signers, options?, accountId?)` | Covered |
86
+ | `POST` | `/v1/accounts/{accountId}/templates/{templateId}/documents/estimate-cost` | `client.documents.estimateCostFromTemplate(templateId, signers, accountId?)` | Covered |
87
+
88
+ ## Fields — 8/8
89
+
90
+ | Method | Path | SDK method | Status |
91
+ | --- | --- | --- | --- |
92
+ | `GET` | `/v1/accounts/{accountId}/fields` | `client.fields.list(params?, accountId?)` | Covered |
93
+ | `POST` | `/v1/accounts/{accountId}/fields` | `client.fields.create(payload, accountId?)` | Covered |
94
+ | `GET` | `/v1/accounts/{accountId}/fields/{fieldId}` | `client.fields.get(fieldId, accountId?)` | Covered |
95
+ | `PUT` | `/v1/accounts/{accountId}/fields/{fieldId}` | `client.fields.update(fieldId, payload, accountId?)` | Covered |
96
+ | `DELETE` | `/v1/accounts/{accountId}/fields/{fieldId}` | `client.fields.delete(fieldId, accountId?)` | Covered |
97
+ | `POST` | `/v1/accounts/{accountId}/fields/{fieldId}/validate` | `client.fields.validate(fieldId, value, options?)` | Covered |
98
+ | `POST` | `/v1/accounts/{accountId}/fields/validate-multiple` | `client.fields.validateMultiple(entries, options?)` | Covered |
99
+ | `GET` | `/v1/field-types` | `client.fields.listTypes()` | Covered |
100
+
101
+ ## Signers — 5/5
102
+
103
+ | Method | Path | SDK method | Status |
104
+ | --- | --- | --- | --- |
105
+ | `GET` | `/v1/accounts/{accountId}/signers` | `client.signers.list(params?, accountId?)` | Covered |
106
+ | `POST` | `/v1/accounts/{accountId}/signers` | `client.signers.create(payload, accountId?)` | Covered |
107
+ | `GET` | `/v1/accounts/{accountId}/signers/{signerId}` | `client.signers.get(signerId, accountId?)` | Covered |
108
+ | `PUT` | `/v1/accounts/{accountId}/signers/{signerId}` | `client.signers.update(signerId, payload, accountId?)` | Covered |
109
+ | `DELETE` | `/v1/accounts/{accountId}/signers/{signerId}` | `client.signers.delete(signerId, accountId?)` | Covered |
110
+
111
+ ## Signing and public flows — 17/17
112
+
113
+ All methods in this section use the auth-free transport. Most signer operations
114
+ authenticate with the `signer-access-code` query parameter required by the
115
+ OpenAPI security scheme. The artifact download is explicitly public and accepts
116
+ an optional fourth access-code argument only for legacy compatibility.
117
+
118
+ | Method | Path | SDK method | Status |
119
+ | --- | --- | --- | --- |
120
+ | `GET` | `/v1/public/documents/{documentId}` | `client.documents.getPublic(documentId)` | Covered |
121
+ | `PUT` | `/v1/public/documents/{documentId}/send-token` | `client.documents.sendToken(documentId, email)` | Covered |
122
+ | `GET` | `/v1/signers/self` | `client.signerDocuments.self(accessCode)` | Covered |
123
+ | `GET` | `/v1/signers/{signerId}/document` | `client.signerDocuments.getCurrent(signerId, accessCode)` | Covered |
124
+ | `GET` | `/v1/sign` | `client.signerDocuments.getAssignment(accessCode, hasAcceptedTerms?)` | Covered |
125
+ | `POST` | `/v1/documents/{documentId}/assignments/{assignmentId}` | `client.signerDocuments.sign(documentId, assignmentId, accessCode, entries)` | Covered |
126
+ | `PUT` | `/v1/documents/{documentId}/assignments/{assignmentId}/reject` | `client.signerDocuments.decline(documentId, assignmentId, accessCode, reason)` | Covered |
127
+ | `PUT` | `/v1/signers/documents/sign-multiple` | `client.signerDocuments.signMultiple(documentIds, accessCode)` | Covered |
128
+ | `PUT` | `/v1/signers/documents/decline-multiple` | `client.signerDocuments.declineMultiple(documentIds, reason, accessCode)` | Covered |
129
+ | `POST` | `/v1/verify` | `client.signerDocuments.verifyEmail(payload)` | Covered |
130
+ | `PUT` | `/v1/documents/{documentId}/signers/confirm-data` | `client.signerDocuments.confirmData(documentId, accessCode, payload)` | Covered |
131
+ | `PUT` | `/v1/signers/accept-terms` | `client.signerDocuments.acceptTerms(accessCode)` | Covered |
132
+ | `POST` | `/v1/signature` | `client.signerDocuments.uploadSignature(accessCode, image, options?)` | Covered |
133
+ | `GET` | `/v1/signature/{signatureType}` | `client.signerDocuments.downloadSignature(accessCode, signatureType)` | Covered |
134
+ | `GET` | `/v1/signers/{signerId}/documents` | `client.signerDocuments.list(signerId, accessCode, params?)` | Covered |
135
+ | `GET` | `/v1/signers/{signerId}/documents/search` | `client.signerDocuments.search(signerId, accessCode, search)` | Covered |
136
+ | `GET` | `/v1/signers/{signerId}/documents/{documentId}/download/{artifactName}` | `client.signerDocuments.download(signerId, documentId, artifactName)` | Covered |
137
+
138
+ ## Tags — 4/4
139
+
140
+ | Method | Path | SDK method | Status |
141
+ | --- | --- | --- | --- |
142
+ | `GET` | `/v1/accounts/{accountId}/tags` | `client.tags.list(params?, accountId?)` | Covered |
143
+ | `POST` | `/v1/accounts/{accountId}/tags` | `client.tags.create(payload, accountId?)` | Covered |
144
+ | `PUT` | `/v1/accounts/{accountId}/tags/{tagId}` | `client.tags.update(tagId, payload, accountId?)` | Covered |
145
+ | `DELETE` | `/v1/accounts/{accountId}/tags/{tagId}` | `client.tags.delete(tagId, options?)` | Covered |
146
+
147
+ ## Templates — 1/1 official
148
+
149
+ | Method | Path | SDK method | Status |
150
+ | --- | --- | --- | --- |
151
+ | `GET` | `/v1/accounts/{accountId}/templates` | `client.templates.list(params?, accountId?)` | Covered |
152
+
153
+ Five additional live routes are documented separately under
154
+ [Live template extensions](#live-template-extensions).
155
+
156
+ ## Users — 2/2
157
+
158
+ | Method | Path | SDK method | Status |
159
+ | --- | --- | --- | --- |
160
+ | `GET` | `/v1/users/self` | `client.users.getCurrent()` | Covered |
161
+ | `GET` | `/v1/users/self/stats` | `client.users.getStats(params?)` | Covered |
162
+
163
+ ## Webhooks — 6/6
164
+
165
+ | Method | Path | SDK method | Status |
166
+ | --- | --- | --- | --- |
167
+ | `GET` | `/v1/accounts/{accountId}/webhooks/subscriptions` | `client.webhooks.get(accountId?)` | Covered |
168
+ | `PUT` | `/v1/accounts/{accountId}/webhooks/subscriptions` | `client.webhooks.register(payload, accountId?)` | Covered |
169
+ | `PUT` | `/v1/accounts/{accountId}/webhooks/inactivate` | `client.webhooks.inactivate(accountId?)` | Covered |
170
+ | `GET` | `/v1/webhooks/event-types` | `client.webhooks.listEventTypes()` | Covered |
171
+ | `GET` | `/v1/accounts/{accountId}/webhooks` | `client.webhooks.listDispatches(params?, accountId?)` | Covered |
172
+ | `POST` | `/v1/accounts/{accountId}/webhooks/{historyId}/retry` | `client.webhooks.retryDispatch(historyId, accountId?)` | Covered |
173
+
174
+ ## Live template extensions
175
+
176
+ These routes are available in the live API and covered by the SDK, but they do
177
+ not appear in the 2026-08-06 OpenAPI path set. They are intentionally excluded
178
+ from the official 89-operation coverage count.
179
+
180
+ | Method | Live path | SDK method | Status |
181
+ | --- | --- | --- | --- |
182
+ | `POST` | `/v1/accounts/{accountId}/templates` | `client.templates.create(source, options?)` | Live extension |
183
+ | `GET` | `/v1/accounts/{accountId}/templates/{templateId}` | `client.templates.get(templateId, accountId?)` | Live extension |
184
+ | `PUT` | `/v1/accounts/{accountId}/templates/{templateId}` | `client.templates.update(templateId, payload, accountId?)` | Live extension |
185
+ | `DELETE` | `/v1/accounts/{accountId}/templates/{templateId}` | `client.templates.delete(templateId, accountId?)` | Live extension |
186
+ | `GET` | `/v1/accounts/{accountId}/templates/{templateId}/pages/{pageId}/download` | `client.templates.downloadPage(templateId, pageId, accountId?)` | Live extension |
187
+
188
+ ## SDK conveniences
189
+
190
+ These helpers compose or derive official operations and therefore do not add to
191
+ the endpoint count:
192
+
193
+ - `client.uploadAndRequestSignatures(options)` validates the complete input,
194
+ uploads a document, waits for metadata readiness, reuses or creates signers,
195
+ creates an assignment, and optionally fetches the final document snapshot.
196
+ - `client.documents.waitUntilReady(documentId, options?)` polls the official
197
+ document-details operation with terminal-state and timeout handling.
198
+ - `client.documents.isFullySigned(documentId)` and
199
+ `getSigningProgress(documentId)` derive state from document details.
200
+ - `client.signers.findByEmail(email, accountId?)` uses the official signer list.
201
+ - `client.webhookVerifier` parses webhook envelopes and optionally verifies a
202
+ caller-configured HMAC contract; signature verification itself is not in the
203
+ current OpenAPI document.
204
+
205
+ For request and response payload definitions, use the exported TypeScript
206
+ interfaces and each method's JSDoc examples together with the
207
+ [official API reference](https://api.assinafy.com.br/v1/docs). Contract
208
+ differences that require compatibility handling are recorded in
209
+ [COMPATIBILITY.md](COMPATIBILITY.md).
@@ -0,0 +1,285 @@
1
+ # API compatibility
2
+
3
+ The SDK targets the official Assinafy contract at
4
+ [`/v1/docs/openapi.json`](https://api.assinafy.com.br/v1/docs/openapi.json) and
5
+ keeps compatibility behavior narrow, explicit, and typed. This document records
6
+ the differences observed during the 2026-08-06 contract and sandbox audit. It
7
+ contains no credentials, account identifiers, signer data, or reusable test
8
+ artifacts. The audited OpenAPI response's SHA-256 digest is
9
+ `7e5957082002e8e96c5abc2cadf7b4b463eaa5bd61b76e26f64b90a8b922088c`.
10
+
11
+ The governing rule is simple: new integrations should send the published
12
+ contract. A compatibility path is used only when the caller selects it
13
+ explicitly or the server returns a validation error that unambiguously requests
14
+ the older shape.
15
+
16
+ ## Template management live extensions
17
+
18
+ The current OpenAPI document contains only:
19
+
20
+ ```text
21
+ GET /v1/accounts/{accountId}/templates
22
+ ```
23
+
24
+ The live API additionally exposes five routes used by existing integrations:
25
+
26
+ ```text
27
+ POST /v1/accounts/{accountId}/templates
28
+ GET /v1/accounts/{accountId}/templates/{templateId}
29
+ PUT /v1/accounts/{accountId}/templates/{templateId}
30
+ DELETE /v1/accounts/{accountId}/templates/{templateId}
31
+ GET /v1/accounts/{accountId}/templates/{templateId}/pages/{pageId}/download
32
+ ```
33
+
34
+ They map to `client.templates.create`, `get`, `update`, `delete`, and
35
+ `downloadPage`. They are documented and tested as **live compatibility
36
+ extensions**, not counted as official OpenAPI operations. Their absence from a
37
+ future schema is not by itself grounds for removal; removal requires a live
38
+ regression test, a deprecation period, and a major-version decision.
39
+
40
+ Template status examples have also differed in casing (`Uploaded`/`Ready` in
41
+ live extension responses versus `uploaded`/`ready` in the published schema).
42
+ The exported type intentionally remains `string`. Applications should normalize
43
+ before comparing:
44
+
45
+ ```ts
46
+ if (template.status.toLowerCase() === 'ready') {
47
+ // rendered pages are available
48
+ }
49
+ ```
50
+
51
+ ## Public send-token request
52
+
53
+ The official operation is:
54
+
55
+ ```http
56
+ PUT /v1/public/documents/{documentId}/send-token
57
+ Content-Type: application/json
58
+
59
+ { "email": "signer@example.com" }
60
+ ```
61
+
62
+ That is the default SDK call:
63
+
64
+ ```ts
65
+ await client.documents.sendToken(documentId, 'signer@example.com');
66
+ ```
67
+
68
+ Some older environments require `{ recipient, channel }`. The explicit
69
+ three-argument overload sends that shape:
70
+
71
+ ```ts
72
+ await client.documents.sendToken(documentId, '+5511999990000', 'whatsapp');
73
+ ```
74
+
75
+ For a two-argument call, the SDK starts with `{ email }` and retries the older
76
+ email shape only when the API's validation body specifically says that
77
+ `recipient` or `channel` is required. Unrelated errors are never swallowed or
78
+ retried under this compatibility rule.
79
+
80
+ ## Document tag identifiers
81
+
82
+ The current OpenAPI request schemas for replacing and attaching document tags
83
+ define `tags` as an array of existing **tag IDs**. The SDK follows that contract:
84
+
85
+ ```ts
86
+ const tag = await client.tags.create({ name: 'Contracts' });
87
+ await client.documents.addTags(documentId, [tag.id]);
88
+ await client.documents.replaceTags(documentId, [tag.id]);
89
+ ```
90
+
91
+ Older environments have accepted tag names and auto-created unknown names. The
92
+ wire type remains `string[]` so those deployments are not broken, but name-based
93
+ attachment is a legacy extension and is not the documented default. Production
94
+ code should create/list tags first and submit IDs.
95
+
96
+ ## Account branding fields on create and update
97
+
98
+ The official `Account` response schema includes `primary_color` and
99
+ `secondary_color`, while the current create/update request schemas list only
100
+ `name` and `notification_sender_type`. The sandbox also accepts the two color
101
+ fields on create/update, and the SDK preserves them for existing integrations.
102
+ They must be six hexadecimal characters without a leading `#`.
103
+
104
+ The sandbox audited on 2026-08-06 rejects the official optional
105
+ `notification_sender_type` field with `400` on account creation, while accepting
106
+ the same field on update. The SDK still exposes and sends it because it is part
107
+ of the production OpenAPI contract. The live audit creates its prerequisite
108
+ workspace with a name only, then tests the field independently via update so
109
+ the create-side deployment lag cannot prevent the rest of the suite.
110
+
111
+ The branding read and file operations—`getTheme`, `downloadLogo`, `uploadLogo`,
112
+ and `deleteLogo`—are official operations and are not extensions.
113
+
114
+ ## Field-definition request extensions
115
+
116
+ The official field-create body defines `type`, `name`, nullable `regex`, and
117
+ `is_required`; the update body defines `name`, nullable `regex`, and
118
+ `is_active`. The audited sandbox also accepts `is_active` on create and
119
+ `type`/`is_required` on update. Those three properties remain typed as explicit
120
+ live extensions so existing integrations keep working. New code should prefer
121
+ the operation-specific official fields.
122
+
123
+ The field-validation schemas also omit the `signer-access-code` query parameter
124
+ accepted by signer-portal deployments. `fields.validate` and `validateMultiple`
125
+ retain the typed `signerAccessCode` option for those deployments. The 2026-08-06
126
+ full audit exercised account-authenticated validation; it did not have the
127
+ signer-code fixture needed to re-certify this compatibility query.
128
+
129
+ ## Retained signer request compatibility
130
+
131
+ The current signer create/update schemas use `full_name`, `email`, and
132
+ `whatsapp_phone_number`. The SDK also retains three older integration inputs:
133
+
134
+ - `phone` is a client-only alias normalized to `whatsapp_phone_number` before
135
+ transmission;
136
+ - `cpf` is normalized to digits and forwarded; and
137
+ - create-time `metadata` is forwarded unchanged.
138
+
139
+ These inputs remain source-compatible because removing them without a live
140
+ regression would break existing consumers. They were unit/request-contract
141
+ tested but were not separately re-probed in the 2026-08-06 disposable signer
142
+ matrix, so new integrations should prefer only the published fields.
143
+
144
+ The official signer `confirm-data` body contains only `full_name`, `email`, and
145
+ `government_id`. Its primary overload exposes exactly those fields. A deprecated
146
+ compatibility overload retains `whatsapp_phone_number`, which was not live-
147
+ certified in this audit. It also preserves the pre-audit `has_accepted_terms`
148
+ pass-through because that behavior could not be live-tested with the supplied
149
+ fixtures. This field is outside the current contract and must **not** be treated
150
+ as legal consent or as a substitute for the separate official `acceptTerms()`
151
+ request. Production signer UIs should call `acceptTerms()` explicitly.
152
+
153
+ ## Signature-image media type
154
+
155
+ `POST /signature` officially accepts a raw `image/png` body. That is the SDK's
156
+ typed/default request and the only media type claimed by the API contract. The
157
+ deprecated `contentType` compatibility overload is retained so older consumers
158
+ are not silently broken, but non-PNG values were not live-certified because the
159
+ provided audit fixtures contained no signer access code or legal-consent flow.
160
+ Do not use the override in new integrations without verifying the target
161
+ deployment.
162
+
163
+ ## Document upload metadata
164
+
165
+ The multipart upload schema documents the PDF file but not the SDK's optional
166
+ JSON `metadata` part. The audited sandbox accepted and processed a disposable
167
+ document carrying metadata on 2026-08-06. The option remains an explicit live
168
+ extension; callers should treat metadata keys and values as application-owned,
169
+ opaque JSON.
170
+
171
+ ## Reset-expiration null and public signer-download compatibility
172
+
173
+ The reset-expiration schema declares `expires_at` as a date-time string. The SDK
174
+ retains `null` as a compatibility value used by older integrations to clear an
175
+ expiration, but the full live audit tested only a future timestamp; `null` is
176
+ unit/request-contract tested and remains live-unverified.
177
+
178
+ The signer artifact download is the opposite case: OpenAPI explicitly marks it
179
+ public and defines no `signer-access-code` query. The official three-argument SDK
180
+ call therefore sends no code. An optional fourth code remains available for
181
+ older deployments, but it is a compatibility query rather than part of the
182
+ published operation.
183
+
184
+ ## Resend-cost response variants
185
+
186
+ The published
187
+ `POST /documents/{documentId}/assignments/{assignmentId}/signers/{signerId}/estimate-resend-cost`
188
+ response references the full `CostEstimate` schema. A smaller resend-specific
189
+ shape has also been observed live. The SDK therefore returns the honest union
190
+ `IResendCostEstimate`:
191
+
192
+ ```ts
193
+ const estimate = await client.assignments.estimateResendCost(
194
+ documentId,
195
+ assignmentId,
196
+ signerId,
197
+ );
198
+
199
+ if ('total_credits' in estimate) {
200
+ console.log(estimate.has_sufficient_resources);
201
+ } else {
202
+ console.log(estimate.total, estimate.has_sufficient_credits);
203
+ }
204
+ ```
205
+
206
+ The SDK does not synthesize missing fields, so callers never mistake invented
207
+ zeroes for server-provided balances.
208
+
209
+ ## Public-document response variants
210
+
211
+ The official `GET /public/documents/{documentId}` response references the full
212
+ `Document` schema. Older public responses can be compact and expose only fields
213
+ such as `id`, `name`, `page_count`, and `created_by`. `IPublicDocumentInfo`
214
+ requires the stable `id` and `name`, makes expanded document fields optional,
215
+ and retains the compact fields. Check optional fields before using them:
216
+
217
+ ```ts
218
+ const document = await client.documents.getPublic(documentId);
219
+ if (document.pages) {
220
+ console.log(document.pages.length);
221
+ } else {
222
+ console.log(Number(document.page_count ?? 0));
223
+ }
224
+ ```
225
+
226
+ ## Empty acknowledgements
227
+
228
+ Several write operations return a successful status/message envelope with no
229
+ `data` field, while a few older responses use an empty array or object. Methods
230
+ whose contract has no meaningful result resolve to `Promise<void>` and validate
231
+ the HTTP/envelope status. This applies to token dispatch, signer OTP/terms,
232
+ signature-image upload, bulk signer actions, and deletion operations. Success is
233
+ not represented as a fabricated object.
234
+
235
+ ## Webhook signature verification is not in the OpenAPI contract
236
+
237
+ The official webhook operations define subscription management, event types,
238
+ delivery history, and retry. The current OpenAPI document does **not** define a
239
+ shared-secret field, signature algorithm, digest encoding, or signature header
240
+ for incoming deliveries.
241
+
242
+ `client.webhookVerifier` is retained as an opt-in HMAC-SHA256 utility for
243
+ environments whose separate Assinafy agreement provides a shared secret and a
244
+ hex digest. Confirm the header name and signing procedure with Assinafy for the
245
+ target environment before enforcing it. Do not assume an
246
+ `X-Assinafy-Signature` header solely from this SDK.
247
+
248
+ ## Authentication isolation
249
+
250
+ Public documents, login/social login, password-reset, OAuth URL, and
251
+ signer-access-code flows use a separate HTTP transport without `X-Api-Key` or
252
+ `Authorization` defaults. This is a security boundary rather than a
253
+ wire-contract deviation: a credentialless `new AssinafyClient()` can drive
254
+ those flows, and credentials configured for protected resources are not leaked
255
+ to them.
256
+
257
+ Protected methods still use the authenticated transport and receive the API's
258
+ normal `401` response if credentials are absent or invalid.
259
+
260
+ ## Sandbox user and statistics deployment lag
261
+
262
+ The current OpenAPI schema defines `GET /users/self` as a direct `AuthUser`
263
+ payload. The sandbox audited on 2026-08-06 still wraps that value as
264
+ `{ user, accounts }` and adds `user.is_password_set`. `users.getCurrent()`
265
+ normalizes both forms to `IAuthenticatedUser`; `is_password_set` is an optional
266
+ live-compatibility field.
267
+
268
+ The same sandbox returns `404` for the official `GET /users/self/stats` and
269
+ `GET /accounts/{accountId}/stats` routes. The production host recognizes both
270
+ routes (an unauthenticated probe receives `401`), so the SDK retains the exact
271
+ published paths and types. The live audit reports those sandbox-only `404`s as
272
+ explicit `SKIP`s rather than claiming they passed or rewriting calls to an
273
+ undocumented route.
274
+
275
+ ## Updating this record
276
+
277
+ When the upstream API changes:
278
+
279
+ 1. Diff the new official OpenAPI paths and schemas against the snapshot date
280
+ above.
281
+ 2. Add or update typed methods and request/response tests.
282
+ 3. Verify live-only behavior in the sandbox without logging secrets or tokens.
283
+ 4. Update [API_COVERAGE.md](API_COVERAGE.md) and this file in the same change.
284
+ 5. Keep compatibility behavior isolated and feature-detectable; do not silently
285
+ broaden retries or coerce malformed success payloads.
@@ -0,0 +1,138 @@
1
+ # Releasing
2
+
3
+ GitLab is the canonical repository and push-mirrors branches and tags to
4
+ GitHub. Publishing is performed by `.github/workflows/release.yml` when GitHub
5
+ receives a tag matching `v*`; a mirrored tag does not create a GitHub `release`
6
+ event, and the workflow intentionally does not depend on one.
7
+
8
+ ## Release support matrix
9
+
10
+ | Runtime | Release responsibility |
11
+ | --- | --- |
12
+ | Bun 1.3.14 | Locked install, tests, build, audit, and packaging |
13
+ | Node.js 22 | Minimum supported consumer runtime |
14
+ | Node.js 24 LTS | Packaging and registry publishing runtime |
15
+ | Node.js 26 Current | Forward-compatibility consumer test |
16
+
17
+ Keep this table, `package.json`, GitLab CI, and the GitHub workflows synchronized
18
+ when changing runtime support.
19
+
20
+ ## One-time registry setup
21
+
22
+ ### npm trusted publishing
23
+
24
+ Configure the npm package `@assinafy/sdk` with a GitHub Actions trusted
25
+ publisher using these repository coordinates:
26
+
27
+ | npm setting | Value |
28
+ | --- | --- |
29
+ | Organization or user | `assinafy` |
30
+ | Repository | `typescript-sdk` |
31
+ | Workflow filename | `release.yml` |
32
+ | Environment | Leave unset unless the workflow is updated to use one |
33
+
34
+ The `publish-npm` job grants only `contents: read` and `id-token: write`.
35
+ Modern npm exchanges GitHub's short-lived OIDC identity for publish credentials;
36
+ no long-lived `NPM_TOKEN` is required. Test trusted publishing before deleting a
37
+ legacy token, then remove that token from repository and organization secrets.
38
+
39
+ ### GitHub Packages
40
+
41
+ The `publish-gh` job uses the workflow-scoped `GITHUB_TOKEN` with
42
+ `packages: write`. Confirm that organization policy permits Actions to publish
43
+ the `@assinafy` scope and that the package remains linked to this repository.
44
+ No separate personal access token should be stored.
45
+
46
+ ### Mirror and tag protection
47
+
48
+ The GitLab push mirror must include tags and be able to update the GitHub
49
+ repository. Protect release tags in GitLab so only release maintainers can
50
+ create patterns matching `v*`. GitHub Actions must be enabled on the mirror.
51
+
52
+ ## Preparing a release
53
+
54
+ 1. Merge the complete change through GitLab and confirm both GitLab and mirrored
55
+ GitHub CI are green.
56
+ 2. Choose the semantic version and update `package.json`. Keep the tag exactly
57
+ `v<package-version>`; the workflow rejects a mismatch.
58
+ 3. Update `CHANGELOG.md`, API coverage, compatibility notes, and public examples
59
+ for all user-visible changes.
60
+ 4. Install and run the complete local release gate from a clean checkout:
61
+
62
+ ```sh
63
+ bun install --frozen-lockfile
64
+ bun run verify
65
+ bun run audit
66
+ bun run audit:api
67
+ ```
68
+
69
+ 5. For API changes, run the live audit against a dedicated sandbox. Start
70
+ read-only:
71
+
72
+ ```sh
73
+ ASSINAFY_API_KEY='...' \
74
+ ASSINAFY_ACCOUNT_ID='...' \
75
+ ASSINAFY_BASE_URL='https://sandbox.assinafy.com.br/v1' \
76
+ bun scripts/live-smoke.ts
77
+ ```
78
+
79
+ Then run the reversible suite with two controlled recipients:
80
+
81
+ ```sh
82
+ ASSINAFY_API_KEY='...' \
83
+ ASSINAFY_ACCOUNT_ID='...' \
84
+ ASSINAFY_BASE_URL='https://sandbox.assinafy.com.br/v1' \
85
+ ASSINAFY_TEST_EMAIL_PRIMARY='first@example.com' \
86
+ ASSINAFY_TEST_EMAIL_SECONDARY='second@example.com' \
87
+ bun scripts/live-smoke.ts --all
88
+ ```
89
+
90
+ The suite creates and force-deletes a disposable workspace, but an
91
+ interrupted process can still leave fixtures behind; inspect the sandbox
92
+ before releasing. Treat every `FAIL` as a release blocker and review each
93
+ `SKIP` to confirm its password, provider-token, signer-code/OTP, legal-action,
94
+ final-artifact, or webhook-receiver prerequisite was intentionally absent.
95
+ Never use production credentials or uncontrolled recipients.
96
+
97
+ 6. Commit the version and release notes, merge them to the canonical default
98
+ branch, then create a signed or annotated `v<version>` tag on that exact
99
+ commit and push the tag to GitLab.
100
+ 7. Confirm that the tag reaches GitHub and starts the **Release** workflow. Do
101
+ not create or move a second tag to work around mirror delay.
102
+
103
+ ## What the workflow publishes
104
+
105
+ The workflow serializes releases repository-wide and performs these steps:
106
+
107
+ 1. verify that the tag and `package.json` versions match;
108
+ 2. install from `bun.lock`, run `verify` and `audit`, and build once;
109
+ 3. create one `.tgz` with lifecycle scripts disabled and record its SHA-256;
110
+ 4. upload that archive and checksum as a one-day workflow artifact;
111
+ 5. verify and publish the archive to npm through trusted-publisher OIDC; and
112
+ 6. verify and publish the **same bytes** to GitHub Packages using
113
+ `GITHUB_TOKEN`.
114
+
115
+ The GitHub Packages job depends on npm publishing, preventing a GitHub-only
116
+ release when npm fails. Both registry jobs validate `SHA256SUMS`; neither
117
+ rebuilds or repacks the SDK. This immutable-artifact flow is part of the release
118
+ contract and must be preserved when editing the workflow.
119
+
120
+ ## Verification and recovery
121
+
122
+ After publishing, confirm that the expected version appears on npm and GitHub
123
+ Packages and that a clean Node.js consumer can import both the ESM and CommonJS
124
+ entrypoints. Compare the downloaded artifacts when registry tooling permits.
125
+
126
+ If packaging or npm publishing fails, fix the source, increment or retain the
127
+ version as registry state allows, and create a new tag only after review. Never
128
+ move a tag that has published a package.
129
+
130
+ If npm succeeds and GitHub Packages fails, rerun only the failed job while the
131
+ original one-day workflow artifact is retained. This preserves the exact
132
+ archive already published to npm. Do not repack an approximation or unpublish a
133
+ released npm version. If the artifact has expired, stop and review recovery with
134
+ the maintainers before changing workflow dependencies or registry state.
135
+
136
+ For a defective published release, prefer deprecating the affected version and
137
+ shipping a reviewed patch. Record the incident and remediation in the
138
+ changelog and any applicable security advisory.