@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.
@@ -1,27 +1,90 @@
1
1
  # API compatibility
2
2
 
3
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`.
4
+ [`/v1/docs/openapi.json`](https://api.assinafy.com.br/v1/docs/openapi.json).
5
+ New integrations should send the published request shape. Compatibility paths
6
+ are narrow, typed, and used only when a caller selects one or when a validation
7
+ response unambiguously requests an older shape.
10
8
 
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.
9
+ ## Host availability
15
10
 
16
- ## Template management live extensions
11
+ The production contract includes these account and authenticated-user routes:
17
12
 
18
- The current OpenAPI document contains only:
13
+ ```text
14
+ GET, POST /v1/accounts
15
+ GET, PUT, DELETE /v1/accounts/{accountId}
16
+ GET, POST, DELETE /v1/accounts/{accountId}/logo
17
+ GET /v1/accounts/{accountId}/theme
18
+ GET /v1/accounts/{accountId}/stats
19
+ GET /v1/users/self
20
+ GET, PUT /v1/users/self/notification-preferences
21
+ GET /v1/users/self/stats
22
+ ```
23
+
24
+ Sandbox deployments can return `404` for user statistics, account statistics,
25
+ or notification preferences while still accepting the production methods on
26
+ the production host. The SDK keeps the official paths and response types. A
27
+ sandbox `404` does not cause the client to route a request elsewhere.
28
+
29
+ Two browser URL helpers used by older deployments remain available:
30
+
31
+ ```text
32
+ GET /v1/auth/authenticate
33
+ GET /v1/login-callback
34
+ ```
35
+
36
+ They map to `auth.getSocialLoginUrl()` and
37
+ `auth.getSocialLoginCallbackUrl()` and are not part of the official
38
+ 89-operation total.
39
+
40
+ The SDK also includes the production contract additions for:
41
+
42
+ - the `pades` document artifact;
43
+ - `DigitalCertificate` verification on assignment and template-document
44
+ creation and cost estimation;
45
+ - typed `display_settings` for collect fields;
46
+ - signer `government_id` updates;
47
+ - signature-image `reuse`;
48
+ - the documented `400` response from `GET /sign`; and
49
+ - the dedicated `SignerSelf` response fields.
50
+
51
+ Older signer-self responses can omit `has_signature`, `has_initial`, and
52
+ `is_signature_reusable`; assignment signers can omit `notification_history`.
53
+ Those fields are optional in the SDK response types.
54
+
55
+ `DocumentStatsRow` separates notification-channel counters from verification
56
+ method counters. Notification counters are not mutually exclusive.
57
+ `signature_requests_verification_{email,whatsapp,bypass,digital_certificate}`
58
+ are mutually exclusive and sum to `signature_requests`. Older unsuffixed email
59
+ and WhatsApp counters remain optional.
60
+
61
+ ## Assignment list account context
62
+
63
+ `GET /v1/assignments` requires the workspace in the camel-case `accountId`
64
+ query parameter even though most account-scoped routes place it in the path.
65
+ The SDK obtains it from the optional method argument or the client default:
66
+
67
+ ```ts
68
+ const page = await client.assignments.list(
69
+ { page: 1, 'per-page': 20 },
70
+ 'account-id',
71
+ );
72
+ // Request query: ?page=1&per-page=20&accountId=account-id
73
+ // Response: { data: IAssignment[], meta?: PaginationMeta }
74
+ ```
75
+
76
+ If neither source supplies an account ID, the SDK throws `ValidationError`
77
+ before making the request.
78
+
79
+ ## Template management extensions
80
+
81
+ The official OpenAPI document contains only:
19
82
 
20
83
  ```text
21
84
  GET /v1/accounts/{accountId}/templates
22
85
  ```
23
86
 
24
- The live API additionally exposes five routes used by existing integrations:
87
+ Existing integrations can also use these routes:
25
88
 
26
89
  ```text
27
90
  POST /v1/accounts/{accountId}/templates
@@ -31,16 +94,9 @@ DELETE /v1/accounts/{accountId}/templates/{templateId}
31
94
  GET /v1/accounts/{accountId}/templates/{templateId}/pages/{pageId}/download
32
95
  ```
33
96
 
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:
97
+ They map to `templates.create`, `get`, `update`, `delete`, and `downloadPage`
98
+ and are excluded from the official operation count. Template status casing can
99
+ vary, so normalize before branching:
44
100
 
45
101
  ```ts
46
102
  if (template.status.toLowerCase() === 'ready') {
@@ -48,9 +104,13 @@ if (template.status.toLowerCase() === 'ready') {
48
104
  }
49
105
  ```
50
106
 
107
+ A page object's `download_url` is protected by account authentication. Prefer
108
+ `templates.downloadPage(templateId, pageId)` so the SDK attaches credentials
109
+ and returns the JPEG bytes as a `Buffer`.
110
+
51
111
  ## Public send-token request
52
112
 
53
- The official operation is:
113
+ The official operation sends an email body:
54
114
 
55
115
  ```http
56
116
  PUT /v1/public/documents/{documentId}/send-token
@@ -59,13 +119,11 @@ Content-Type: application/json
59
119
  { "email": "signer@example.com" }
60
120
  ```
61
121
 
62
- That is the default SDK call:
63
-
64
122
  ```ts
65
123
  await client.documents.sendToken(documentId, 'signer@example.com');
66
124
  ```
67
125
 
68
- Some older environments require `{ recipient, channel }`. The explicit
126
+ Older environments can require `{ recipient, channel }`. The explicit
69
127
  three-argument overload sends that shape:
70
128
 
71
129
  ```ts
@@ -73,121 +131,142 @@ await client.documents.sendToken(documentId, '+5511999990000', 'whatsapp');
73
131
  ```
74
132
 
75
133
  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.
134
+ email shape only when the validation response names `recipient` or `channel` as
135
+ required. Other errors are returned unchanged.
79
136
 
80
- ## Document tag identifiers
137
+ ## Document tags
81
138
 
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:
139
+ Replace and attach requests require existing tag IDs:
84
140
 
85
141
  ```ts
86
- const tag = await client.tags.create({ name: 'Contracts' });
142
+ const tag = await client.tags.create({ name: 'Contracts', color: '#ff8800' });
87
143
  await client.documents.addTags(documentId, [tag.id]);
88
144
  await client.documents.replaceTags(documentId, [tag.id]);
145
+ const result = await client.documents.detachTag(documentId, tag.id);
146
+ // result → { detached: true }
89
147
  ```
90
148
 
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.
149
+ The API accepts a tag color with or without a leading `#` and returns the
150
+ stored six-character value without it. Unknown tag names are not the normal
151
+ attachment input; create or list the tag first and send its ID. An empty array
152
+ passed to `replaceTags` detaches every tag.
95
153
 
96
- ## Account branding fields on create and update
154
+ ## Account branding fields
97
155
 
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 `#`.
156
+ The official account create/update request schemas define `name` and
157
+ `notification_sender_type`. The response includes `primary_color` and
158
+ `secondary_color`. Some deployments also accept those two color fields on
159
+ create/update, so the SDK retains them as optional inputs. Account colors must
160
+ be six hexadecimal characters without a leading `#`.
103
161
 
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.
162
+ Some sandbox plans reject `notification_sender_type` during account creation
163
+ while accepting it on update. If that occurs, create with `{ name }` and apply
164
+ the sender type in a separate update. `getTheme`, `downloadLogo`, `uploadLogo`,
165
+ and `deleteLogo` are official operations.
110
166
 
111
- The branding read and file operations—`getTheme`, `downloadLogo`, `uploadLogo`,
112
- and `deleteLogo`—are official operations and are not extensions.
167
+ `workspaces.delete(accountId, { force: true })` requests cancellation of an
168
+ active paid subscription as part of account deletion. It is not a general
169
+ override for unrelated deletion restrictions.
113
170
 
114
- ## Field-definition request extensions
171
+ ## Field-definition extensions
115
172
 
116
173
  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.
174
+ `is_required`; update defines `name`, nullable `regex`, and `is_active`. The SDK
175
+ also retains `is_active` on create and `type` or `is_required` on update for
176
+ deployments that accept them. New code should prefer the operation-specific
177
+ official fields.
122
178
 
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.
179
+ The field-validation schemas omit the `signer-access-code` query parameter used
180
+ by some signer portals. `fields.validate()` and `validateMultiple()` retain the
181
+ typed `signerAccessCode` option for those environments.
128
182
 
129
- ## Retained signer request compatibility
183
+ ## Signer request extensions
130
184
 
131
- The current signer create/update schemas use `full_name`, `email`, and
132
- `whatsapp_phone_number`. The SDK also retains three older integration inputs:
185
+ The official signer-create schema uses `full_name`, `email`, and
186
+ `whatsapp_phone_number`. Signer update adds `government_id`, which the SDK
187
+ normalizes to digits. Three older integration inputs remain accepted:
133
188
 
134
- - `phone` is a client-only alias normalized to `whatsapp_phone_number` before
135
- transmission;
189
+ - `phone` is normalized to `whatsapp_phone_number` before transmission;
136
190
  - `cpf` is normalized to digits and forwarded; and
137
191
  - create-time `metadata` is forwarded unchanged.
138
192
 
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.
193
+ New integrations should use the official create fields and `government_id` on
194
+ update. `cpf` is not an alias for the official update field, and signer
195
+ responses do not return it.
196
+
197
+ ## Digital certificate and collect placement
198
+
199
+ `DigitalCertificate` requires the account feature, a CPF or CNPJ stored in the
200
+ signer's `government_id`, and exactly one certificate signer in that signing
201
+ step. It costs two credits per signer in addition to the selected notification
202
+ cost. Notification methods remain `Email` and `Whatsapp`.
203
+
204
+ For a `collect` assignment, each field can include `display_settings`.
205
+ `left`, `top`, `width`, `height`, and `fontSize` are required; `fontFamily` and
206
+ `backgroundColor` are optional. Values use Assinafy's 150-DPI page-image pixels
207
+ from the upper-left corner and must stay within the page.
208
+
209
+ ## Document responses and artifacts
210
+
211
+ `documents.rename()` can return a document without `pages` or `assignment`.
212
+ `IRenameDocumentResponse` therefore keeps those two properties optional while
213
+ retaining the other document fields.
214
+
215
+ `documents.details()` returns `decline_reason` only when the access token
216
+ belongs to the document creator. Do not infer the absence of a decline merely
217
+ because that field is missing for another authenticated user.
218
+
219
+ Owner and signer downloads accept `original`, `certificated`,
220
+ `certificate-page`, `pades`, and `bundle`:
221
+
222
+ - `pades` exists only when the document had an ICP-Brasil certificate signer;
223
+ - `bundle` is a ZIP containing `original`, `certificated`, and
224
+ `certificate-page`, plus `pades` when it exists; and
225
+ - a generated artifact can return `404` until processing or certification is
226
+ complete.
227
+
228
+ Document and page download URLs in JSON responses are protected. Prefer the
229
+ typed download methods so credentials are applied and binary data is returned
230
+ as a `Buffer`.
231
+
232
+ ## Signer-side preconditions
233
+
234
+ The signer `confirm-data` body contains `full_name`, `email`,
235
+ `government_id`, and `has_accepted_terms`. A certificate signer must confirm
236
+ data and accept terms before `getAssignment()`; either send
237
+ `has_accepted_terms: true` with `confirmData()` or call `acceptTerms()` first.
238
+ The `has_accepted_terms` query on `getAssignment()` is too late to open that
239
+ gate for a certificate signer.
240
+
241
+ `getAssignment()` uses `GET /sign`, but the API records the signer as having
242
+ viewed the assignment. The SDK therefore excludes this request from automatic
243
+ HTTP 429 replay.
244
+
245
+ `sign()` sends a non-empty array of `{ itemId, fieldId, pageId, value }` and is
246
+ intended for collect assignments. A virtual signer must confirm their data
247
+ before signing and should use `signMultiple()`, which accepts only virtual
248
+ documents. Certificate signers cannot use `sign()`; they complete the
249
+ certificate-start and certificate-complete browser flow.
250
+
251
+ Signature image upload sends raw PNG bytes with `Content-Type: image/png`.
252
+ `reuse: true` persists the image for later documents. A deprecated
253
+ `contentType` option remains for older integrations, but the official contract
254
+ supports PNG only.
255
+
256
+ ## Upload metadata and expiration reset
257
+
258
+ Document upload accepts a PDF of at most 25 MB and 2,000 pages. The multipart
259
+ schema documents the file; the SDK's optional `metadata` JSON part is retained
260
+ for deployments that accept application-owned opaque metadata.
261
+
262
+ The reset-expiration schema requires an ISO-8601 date-time string. The SDK also
263
+ accepts `null` for older integrations that clear an expiration this way. Confirm
264
+ support in the target deployment before sending `null`.
183
265
 
184
266
  ## Resend-cost response variants
185
267
 
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`:
268
+ The official resend-cost response is `ICostEstimate`. Older deployments can
269
+ return a compact branch, so the SDK exposes `IResendCostEstimate`:
191
270
 
192
271
  ```ts
193
272
  const estimate = await client.assignments.estimateResendCost(
@@ -203,83 +282,81 @@ if ('total_credits' in estimate) {
203
282
  }
204
283
  ```
205
284
 
206
- The SDK does not synthesize missing fields, so callers never mistake invented
207
- zeroes for server-provided balances.
285
+ The SDK does not add absent balance fields.
208
286
 
209
- ## Public-document response variants
287
+ ## Public document and signer download
210
288
 
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:
289
+ The official public-document response is the full `Document` schema. Compact
290
+ responses can contain only `id`, `name`, `page_count`, and `created_by`.
291
+ `IPublicDocumentInfo` requires `id` and `name`, keeps expanded fields optional,
292
+ and retains the compact fields:
216
293
 
217
294
  ```ts
218
295
  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
- }
296
+ const pages = document.pages?.length ?? Number(document.page_count ?? 0);
224
297
  ```
225
298
 
299
+ Signer artifact download is public in the OpenAPI document and requires no
300
+ access-code query in the official three-argument call. An optional fourth code
301
+ is available only for older deployments that require it.
302
+
226
303
  ## Empty acknowledgements
227
304
 
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.
305
+ Several write operations return a success envelope without `data`; older
306
+ responses can use an empty array or object. Methods with no meaningful result
307
+ resolve to `Promise<void>` after validating the HTTP and envelope status. This
308
+ applies to token dispatch, signer OTP and terms, signature-image upload, bulk
309
+ signer actions, and deletions without a documented result. Tag deletion and
310
+ document-tag detachment preserve `{ deleted: boolean }` and
311
+ `{ detached: boolean }`.
312
+
313
+ ## WhatsApp notification buttons
314
+
315
+ The published notification button schema requires `text`. Some deployments
316
+ also return `url`, so the SDK types it as optional. A button URL can contain a
317
+ signer access or verification value; treat it as a credential and never log it.
318
+
319
+ ## Webhook delivery
320
+
321
+ Assinafy sends webhook events as HTTP `POST` JSON requests with
322
+ `Connection: close`. Any `2xx` is successful. There are at most two automatic
323
+ attempts per event with a three-second wait. After ten consecutive failed
324
+ events, ordinary delivery pauses and about 5% of later events are attempted
325
+ until one succeeds. `webhooks.retryDispatch()` requests immediate redelivery.
326
+ Only the first 2,000 characters of the receiver response body are retained in
327
+ dispatch history.
328
+
329
+ The common body contains `id`, `event`, nullable `message`, nullable `payload`,
330
+ nullable `origin`, Unix-second `created_at`, polymorphic `subject` and `object`,
331
+ and `account_id`. Use `id` for idempotent handling and accept unknown fields.
234
332
 
235
333
  ## Webhook signature verification is not in the OpenAPI contract
236
334
 
237
335
  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.
336
+ delivery history, and retry. They do not define a shared-secret field,
337
+ signature algorithm, digest encoding, or signature header for incoming events.
241
338
 
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.
339
+ `client.webhookVerifier` is an opt-in HMAC-SHA256 utility for environments whose
340
+ separate Assinafy agreement provides a shared secret and hex digest. Confirm the
341
+ header name and signing procedure for the target environment before enforcing
342
+ it. Do not assume an `X-Assinafy-Signature` header solely from this SDK.
247
343
 
248
344
  ## Authentication isolation
249
345
 
250
- Public documents, login/social login, password-reset, OAuth URL, and
346
+ Public documents, login and social login, password reset, OAuth URLs, and
251
347
  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.
348
+ `Authorization` defaults. A credentialless `new AssinafyClient()` can drive
349
+ those flows, and credentials configured for protected resources are not sent.
350
+
351
+ Protected methods use the authenticated transport and receive the API's normal
352
+ `401` response when credentials are absent or invalid. The authenticated
353
+ transport accepts same-origin absolute URLs, rejects cross-origin requests
354
+ before dispatch, and treats credentials as redirect-sensitive. Use a separate
355
+ HTTP client for unrelated origins.
356
+
357
+ ## Authenticated-user response variants
358
+
359
+ `GET /users/self` officially returns `AuthUser` directly. Some sandbox
360
+ deployments return `{ user, accounts }` and add `user.is_password_set`.
361
+ `users.getCurrent()` normalizes both forms to `IAuthenticatedUser`, where
362
+ `is_password_set` is optional.
package/docs/RELEASING.md CHANGED
@@ -9,7 +9,7 @@ event, and the workflow intentionally does not depend on one.
9
9
 
10
10
  | Runtime | Release responsibility |
11
11
  | --- | --- |
12
- | Bun 1.3.14 | Locked install, tests, build, audit, and packaging |
12
+ | Bun 1.4.0 | Locked install, tests, build, contract checks, and packaging |
13
13
  | Node.js 22 | Minimum supported consumer runtime |
14
14
  | Node.js 24 LTS | Packaging and registry publishing runtime |
15
15
  | Node.js 26 Current | Forward-compatibility consumer test |
@@ -29,12 +29,16 @@ publisher using these repository coordinates:
29
29
  | Organization or user | `assinafy` |
30
30
  | Repository | `typescript-sdk` |
31
31
  | Workflow filename | `release.yml` |
32
- | Environment | Leave unset unless the workflow is updated to use one |
32
+ | Environment | `release` |
33
+ | Allowed actions | `npm publish` |
33
34
 
34
35
  The `publish-npm` job grants only `contents: read` and `id-token: write`.
35
36
  Modern npm exchanges GitHub's short-lived OIDC identity for publish credentials;
36
37
  no long-lived `NPM_TOKEN` is required. Test trusted publishing before deleting a
37
38
  legacy token, then remove that token from repository and organization secrets.
39
+ Keep package access **public** and select npm's most restrictive publishing
40
+ access option: **Require two-factor authentication and disallow bypass 2FA
41
+ tokens**. Trusted-publisher OIDC remains compatible with that setting.
38
42
 
39
43
  ### GitHub Packages
40
44
 
@@ -43,11 +47,33 @@ The `publish-gh` job uses the workflow-scoped `GITHUB_TOKEN` with
43
47
  the `@assinafy` scope and that the package remains linked to this repository.
44
48
  No separate personal access token should be stored.
45
49
 
50
+ ### Sandbox integration environment
51
+
52
+ Create a protected GitHub environment named `sandbox`. Restrict deployments to
53
+ the mirrored `main` branch, require reviewer approval, and store these required
54
+ environment secrets:
55
+
56
+ - `ASSINAFY_API_KEY`
57
+ - `ASSINAFY_ACCOUNT_ID`
58
+
59
+ The `disposable-full` mode also requires `ASSINAFY_TEST_EMAIL_PRIMARY` and
60
+ `ASSINAFY_TEST_EMAIL_SECONDARY`. Optional coverage uses
61
+ `ASSINAFY_TEST_WEBHOOK_URL`, `ASSINAFY_TEST_LOGIN_EMAIL`,
62
+ `ASSINAFY_TEST_LOGIN_PASSWORD`, `ASSINAFY_SIGNER_ACCESS_CODE`,
63
+ `ASSINAFY_SIGNER_OTP`, and `ASSINAFY_PUBLIC_DOCUMENT_ID`.
64
+
65
+ Keep secret values out of repository variables, files, and logs. Run the manual
66
+ **Sandbox integration** workflow from mirrored `main`; use `read-only` first
67
+ and select `disposable-full` only for a deliberate reversible test run.
68
+
46
69
  ### Mirror and tag protection
47
70
 
48
71
  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.
72
+ repository. Protect `v*` tags in GitLab so only release maintainers can create
73
+ them, and add a GitHub tag ruleset that restricts creating, updating, and
74
+ deleting the same pattern to the mirror/release maintainers. Protect the GitHub
75
+ `release` environment with required reviewers and a deployment tag rule for
76
+ `v*`. GitHub Actions must be enabled on the mirror.
51
77
 
52
78
  ## Preparing a release
53
79
 
@@ -66,8 +92,8 @@ create patterns matching `v*`. GitHub Actions must be enabled on the mirror.
66
92
  bun run audit:api
67
93
  ```
68
94
 
69
- 5. For API changes, run the live audit against a dedicated sandbox. Start
70
- read-only:
95
+ 5. For API changes, run the sandbox smoke script against a dedicated account.
96
+ Start read-only:
71
97
 
72
98
  ```sh
73
99
  ASSINAFY_API_KEY='...' \
@@ -105,9 +131,10 @@ create patterns matching `v*`. GitHub Actions must be enabled on the mirror.
105
131
  The workflow serializes releases repository-wide and performs these steps:
106
132
 
107
133
  1. verify that the tag and `package.json` versions match;
108
- 2. install from `bun.lock`, run `verify` and `audit`, and build once;
134
+ 2. install from `bun.lock`, run `verify`, dependency and API-contract checks,
135
+ and build once;
109
136
  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;
137
+ 4. upload that archive and checksum as a seven-day workflow artifact;
111
138
  5. verify and publish the archive to npm through trusted-publisher OIDC; and
112
139
  6. verify and publish the **same bytes** to GitHub Packages using
113
140
  `GITHUB_TOKEN`.
@@ -120,15 +147,19 @@ contract and must be preserved when editing the workflow.
120
147
  ## Verification and recovery
121
148
 
122
149
  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.
150
+ Packages, that a clean Node.js consumer can import both the ESM and CommonJS
151
+ entrypoints, and that registry downloads have the expected checksum.
125
152
 
126
153
  If packaging or npm publishing fails, fix the source, increment or retain the
127
154
  version as registry state allows, and create a new tag only after review. Never
128
155
  move a tag that has published a package.
129
156
 
157
+ Do not rerun an old release after changing its workflow or trusted-publisher
158
+ coordinates: GitHub reruns use the original commit and ref. Merge the fix and
159
+ create a reviewed new version and tag instead.
160
+
130
161
  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
162
+ original seven-day workflow artifact is retained. This preserves the exact
132
163
  archive already published to npm. Do not repack an approximation or unpublish a
133
164
  released npm version. If the artifact has expired, stop and review recovery with
134
165
  the maintainers before changing workflow dependencies or registry state.