assinafy 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d2a0b97db3a9ad302307e31bccf8f06751fe310dd253417bb129ad921b0a4b3f
4
- data.tar.gz: 94eb893e22a78777a0b4102d572e274e0f22c5b50cd3768d348f2deac69541ab
3
+ metadata.gz: 1d6db456898234647465325a55c4580054a8ab93f39aff3d6ca067ffe6be9e49
4
+ data.tar.gz: 55577984ae91aec1ad66715a9b34a8ae10e991f015d6f4e3fe01f5bebf2b9503
5
5
  SHA512:
6
- metadata.gz: 43838b1c27e22fc5210a8e877a4e1897f50030a6e32801cf490f101de6d18805ed79bff013120801ce90ab53369531142bf678cbe0e04f543139aa72790bab11
7
- data.tar.gz: f0e442b567d84643b2237ec21edf5b59de9155268451986dcdcc19f841ccf1514e1fd11e112fedc0ff1b8c04439feb411fec7ec37bc06128a86409996745798e
6
+ metadata.gz: 93a92268707f3afa45e7f16c837ab3ded90b7ab8859060ff48a0d7eeb81a244dde3b53ada834852de4f83df7e72f738bbffc1468e8ae7de24dfffba56709edb6
7
+ data.tar.gz: 384b02e9dd54fb89ea78dc61b6f7a507e8e317431d30996b0dcfd16f4a6d3251a03821d0f6d59d28541b2425a24cbd98062c68b2945f0ef2fb252af8569a7ad4
data/CHANGELOG.md CHANGED
@@ -2,6 +2,29 @@
2
2
 
3
3
  All notable changes to the `assinafy` Ruby gem are documented here.
4
4
 
5
+ ## [2.1.0] - 2026-10-28
6
+
7
+ ### Added
8
+
9
+ - `ContractResource` and `client.contracts` cover listing, reading, updating, relating, renewing and terminating
10
+ the contract terms tracked on documents, including nullable fields and optimistic `updated_at` preconditions.
11
+ - User notification preferences support `ContractRenewalNotice`, `ContractExpiring`, `ContractExpired` and
12
+ `ContractRenewed`.
13
+
14
+ ### Changed
15
+
16
+ - New or changed webhook endpoint URLs must be absolute HTTPS URLs; invalid URLs raise `ValidationError` before a
17
+ request.
18
+ - Contract, document, assignment, webhook and reminder-cost response fields reflect the current API contract.
19
+
20
+ ## [2.0.1] - 2026-10-19
21
+
22
+ ### Fixed
23
+
24
+ - Assignment creation and cost estimation reject `DigitalCertificate` signers unless the assignment method is
25
+ `virtual`, before an unsignable collect assignment can invite the signer or consume credits. The local
26
+ `ValidationError` exposes the stable code `digital_certificate_requires_virtual` in `errors[:code]`.
27
+
5
28
  ## [2.0.0] - 2026-10-16
6
29
 
7
30
  ### Added
data/README.en.md CHANGED
@@ -394,9 +394,11 @@ the missing side is inferred from the other, and when both are omitted both defa
394
394
  - The certificate charge appears in the cost breakdown under the code `SignatureDigitalCertificate`. Resending a
395
395
  notification charges the notification again.
396
396
  - `DigitalCertificate` needs the Digital Certificate account feature (Standard and Pro plans), a CPF or CNPJ in the
397
- signer's `government_id`, and the signer alone in its signing step. A CPF requires that person's certificate (an
398
- e-CPF, or an e-CNPJ naming them as legal representative); a CNPJ requires an e-CNPJ for that company, from any of
399
- its representatives.
397
+ signer's `government_id`, a `method: 'virtual'` assignment, and the signer alone in its signing step. A collect
398
+ assignment, including one inferred from a template with positioned signer fields, cannot use certificate
399
+ signing. Direct assignment creation and estimation reject it with `ValidationError` code
400
+ `digital_certificate_requires_virtual`. A CPF requires that person's certificate (an e-CPF, or an e-CNPJ naming
401
+ them as legal representative); a CNPJ requires an e-CNPJ for that company, from any of its representatives.
400
402
  - `AssignmentResource::VERIFICATION_METHODS` and `::NOTIFICATION_METHODS` publish the enums. `build_payload`
401
403
  validates values, pairings, and `step` locally, so a typo fails before the request is sent.
402
404
 
@@ -433,10 +435,11 @@ assignment = client.assignments.create(
433
435
  assignment['signing_urls'] # => [{ 'signer_id' => 'signer-id', 'url' => 'https://.../sign/...' }, ...]
434
436
  ```
435
437
 
436
- `virtual` has no positioned fields; `collect` adds `entries` of positioned fields (see
437
- [Assignments](#assignments)). Both require a non-empty `signers` array. Steps are all-or-none, positive, and
438
- contiguous from 1; equal steps sign in parallel, and each step is notified when the previous one finishes. A
439
- certificate signer is alone in its step. Deadlines are zoned ISO 8601 and at least one hour ahead.
438
+ `virtual` has no positioned fields and is required for `DigitalCertificate`; `collect` adds `entries` of
439
+ positioned fields (see [Assignments](#assignments)). Both require a non-empty `signers` array. Steps are
440
+ all-or-none, positive, and contiguous from 1; equal steps sign in parallel, and each step is notified when the
441
+ previous one finishes. A certificate signer is alone in its step. Deadlines are zoned ISO 8601 and at least one
442
+ hour ahead.
440
443
 
441
444
  For a template, bind one distinct existing signer to each role and create the document and assignment together:
442
445
 
@@ -688,7 +691,7 @@ See [Errors](#errors) for the full hierarchy.
688
691
 
689
692
  ## Resource reference
690
693
 
691
- `Assinafy::Client` exposes thirteen accessors — twelve API resources plus the local `webhook_verifier` helper:
694
+ `Assinafy::Client` exposes fourteen accessors — thirteen API resources plus the local `webhook_verifier` helper:
692
695
 
693
696
  | Accessor | What it covers |
694
697
  | --------------------------- | ------------------------------------------------------------------ |
@@ -697,6 +700,7 @@ See [Errors](#errors) for the full hierarchy.
697
700
  | `client.accounts` | Account CRUD, theme, KPI stats, brand logo |
698
701
  | `client.users` | User profile, notification preferences, cross-account KPIs |
699
702
  | `client.documents` | Upload, list, search, rename, download, delete, verify, tags |
703
+ | `client.contracts` | Contract terms, amendments, renewals, terminations |
700
704
  | `client.signers` | Workspace signer CRUD + signer self-service endpoints |
701
705
  | `client.signer_documents` | Signer-authenticated multi-document operations + search |
702
706
  | `client.assignments` | List/create/sign/decline/resend/estimate assignments |
@@ -778,6 +782,27 @@ client.documents.signing_progress('document-id') # => { signed: 1, total: 2, p
778
782
  An Array `tags` filter is sent as comma-separated tag IDs. The tag arrays of `replace_tags`/`append_tags` take tag
779
783
  IDs.
780
784
 
785
+ ### Contracts
786
+
787
+ ```ruby
788
+ contract = client.contracts.update(
789
+ 'document-id',
790
+ status: 'active', counterparty_name: 'Example Ltda', value: 120_000, currency: 'BRL',
791
+ start_date: '2026-11-01', end_date: '2027-10-31', auto_renew: true,
792
+ renewal_term_months: 12, notice_period_days: 30
793
+ )
794
+
795
+ client.contracts.get('document-id')
796
+ client.contracts.list(status: ['active'], ending_within_days: 30)
797
+ client.contracts.related('document-id')
798
+ client.contracts.renew('document-id', term: contract['term'], updated_at: contract['updated_at'])
799
+ client.contracts.terminate('document-id', reason: 'Ended by the parties')
800
+ ```
801
+
802
+ `update` is partial and preserves explicit `nil` values as JSON `null`. Link an amendment document with
803
+ `parent_document_id` and `relation_type`. Reads require OAuth scope `documents:read`; writes require
804
+ `documents:write` and a plan with contract tracking. `get` and `related` return `nil` when no tracked terms exist.
805
+
781
806
  ### Signers (workspace CRUD)
782
807
 
783
808
  ```ruby
data/README.md CHANGED
@@ -531,9 +531,12 @@ dois, ambos assumem `Email`.
531
531
  ### Certificado digital ICP-Brasil (A1/A3)
532
532
 
533
533
  Exige o recurso **Certificado Digital** na conta (planos Standard e Pro), CPF ou CNPJ em
534
- `government_id` do signatário, e que cada signatário por certificado esteja **sozinho no seu
535
- passo**. Um CPF exige o certificado daquela pessoa (e-CPF, ou e-CNPJ que a nomeie como
536
- representante legal); um CNPJ exige um e-CNPJ da empresa, de qualquer um de seus representantes.
534
+ `government_id` do signatário, assignment com `method: 'virtual'` e que cada signatário por certificado esteja
535
+ **sozinho no seu passo**. Um assignment `collect`, inclusive quando inferido de um template com campos de
536
+ signatário posicionados, não aceita certificado digital. A criação e a estimativa diretas levantam
537
+ `ValidationError` com o código estável `digital_certificate_requires_virtual`. Um CPF exige o certificado daquela
538
+ pessoa (e-CPF, ou e-CNPJ que a nomeie como representante legal); um CNPJ exige um e-CNPJ da empresa, de qualquer
539
+ um de seus representantes.
537
540
 
538
541
  ---
539
542
 
@@ -963,6 +966,38 @@ verificacao['agreement_code'] # código impresso no certificado do documento
963
966
  Ela devolve o resultado da Assinafy; não valida independentemente a assinatura do PDF nem a
964
967
  cadeia de certificação.
965
968
 
969
+ ### Acompanhar o contrato
970
+
971
+ Quando o plano da conta inclui acompanhamento de contratos, registre os termos no documento e
972
+ use o `updated_at` devolvido pela API como precondição para evitar sobrescrever uma alteração
973
+ concorrente:
974
+
975
+ ```ruby
976
+ contrato = client.contracts.update(
977
+ documento['id'],
978
+ status: 'active',
979
+ counterparty_name: 'Empresa Exemplo Ltda',
980
+ value: 120_000,
981
+ currency: 'BRL',
982
+ start_date: '2026-11-01',
983
+ end_date: '2027-10-31',
984
+ auto_renew: true,
985
+ renewal_term_months: 12,
986
+ notice_period_days: 30
987
+ )
988
+
989
+ client.contracts.get(documento['id'])
990
+ client.contracts.list(status: ['active'], ending_within_days: 30)
991
+ client.contracts.related(documento['id'])
992
+ client.contracts.renew(documento['id'], term: contrato['term'], updated_at: contrato['updated_at'])
993
+ client.contracts.terminate(documento['id'], reason: 'Encerrado pelas partes')
994
+ ```
995
+
996
+ `update` é parcial e preserva `nil` como JSON `null` para limpar campos. Um documento de aditivo
997
+ pode ser ligado com `parent_document_id` e `relation_type`. As leituras usam o escopo OAuth
998
+ `documents:read`; alterações usam `documents:write` e exigem que o plano tenha acompanhamento de
999
+ contratos. `get` e `related` devolvem `nil` quando o documento não tem termos acompanhados.
1000
+
966
1001
  ---
967
1002
 
968
1003
  ## 12. Organizar e limpar
@@ -1069,6 +1104,7 @@ end
1069
1104
  | `client.accounts` | workspaces, tema, KPIs, logo |
1070
1105
  | `client.users` | perfil próprio, KPIs, preferências de notificação |
1071
1106
  | `client.documents` | upload, listagem, download, ciclo de vida, tags, verificação |
1107
+ | `client.contracts` | termos, aditivos, renovações e encerramentos de contratos |
1072
1108
  | `client.signers` | CRUD de signatários e todo o autoatendimento do signatário |
1073
1109
  | `client.signer_documents` | documentos do signatário, assinar/recusar em lote |
1074
1110
  | `client.assignments` | pedidos de assinatura, custos, reenvios, assinatura, recusa |
@@ -1,7 +1,7 @@
1
1
  # Assinafy Ruby SDK API Reference
2
2
 
3
- > Contract source: Assinafy API v1 OpenAPI 3.1.0, retrieved 2026-10-16 from
4
- > `https://api.assinafy.com.br/v1/docs/openapi.json` (85 paths, 112 operations, 45 schemas).
3
+ > Contract source: Assinafy API v1 OpenAPI 3.1.0, retrieved 2026-10-28 from
4
+ > `https://api.assinafy.com.br/v1/docs/openapi.json` (90 paths, 118 operations, 49 schemas).
5
5
  > `scripts/check_api_contract.rb` validates contract compatibility weekly.
6
6
 
7
7
  This is the SDK-facing contract reference. Paths below are wire paths; Ruby methods return the unwrapped
@@ -50,6 +50,12 @@ Required parameters and object properties are marked with `*`.
50
50
  | GET | `/v1/accounts/{accountId}` | `AccountResource#get` | Bearer token or `X-Api-Key` | path `accountId*`: string | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`Account`](#account) } |
51
51
  | PUT | `/v1/accounts/{accountId}` | `AccountResource#update` | Bearer token or `X-Api-Key` | path `accountId*`: string | required; `application/json` object { `name`: string; `notification_sender_type`: string } | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`Account`](#account) } |
52
52
  | DELETE | `/v1/accounts/{accountId}` | `AccountResource#delete` | Bearer token or `X-Api-Key` | path `accountId*`: string | optional; `application/json` object { `force`: boolean } | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: Array<any> } |
53
+ | GET | `/v1/accounts/{accountId}/contracts` | `ContractResource#list` | Bearer token or `X-Api-Key`; OAuth `documents:read` | path `accountId*`: string<br>query `status`: comma-separated array of `draft`, `active`, `expired`, `terminated`<br>query `counterparty`, `search`, `end_date_from`, `end_date_to`, `ending_within_days`, `external_system`, `external_id`, `sort`, `expand`, `page`, `per-page` | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: Array<[`DocumentContract`](#documentcontract)> }; pagination headers become `{ data:, meta: }` |
54
+ | GET | `/v1/accounts/{accountId}/documents/{documentId}/contract` | `ContractResource#get` | Bearer token or `X-Api-Key`; OAuth `documents:read` | path `accountId*`: string<br>path `documentId*`: string | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`DocumentContract`](#documentcontract) or [`DocumentContractAmendment`](#documentcontractamendment) }; SDK returns `nil` on `404` |
55
+ | PUT | `/v1/accounts/{accountId}/documents/{documentId}/contract` | `ContractResource#update` | Bearer token or `X-Api-Key`; OAuth `documents:write` | path `accountId*`: string<br>path `documentId*`: string | required non-empty [`DocumentContractInput`](#documentcontractinput); explicit `nil` is sent as JSON null | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`DocumentContract`](#documentcontract) or [`DocumentContractAmendment`](#documentcontractamendment) }; `403` without contract tracking, `409` on stale `updated_at` |
56
+ | GET | `/v1/accounts/{accountId}/documents/{documentId}/contract/related` | `ContractResource#related` | Bearer token or `X-Api-Key`; OAuth `documents:read` | path `accountId*`: string<br>path `documentId*`: string | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: root [`DocumentContract`](#documentcontract) with `amendments` }; SDK returns `nil` on `404` |
57
+ | POST | `/v1/accounts/{accountId}/documents/{documentId}/contract/renew` | `ContractResource#renew` | Bearer token or `X-Api-Key`; OAuth `documents:write` | path `accountId*`: string<br>path `documentId*`: string | optional; `application/json` object { `end_date`: string (date, nullable); `value`: number (nullable); `term`: integer; `updated_at`: string (date-time) }; a valid request requires `term` or `end_date` | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`DocumentContract`](#documentcontract) }; `403` without contract tracking, `409` on stale `updated_at` |
58
+ | POST | `/v1/accounts/{accountId}/documents/{documentId}/contract/terminate` | `ContractResource#terminate` | Bearer token or `X-Api-Key`; OAuth `documents:write` | path `accountId*`: string<br>path `documentId*`: string | optional; `application/json` object { `reason`: string (nullable, max 500); `updated_at`: string (date-time) } | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`DocumentContract`](#documentcontract) }; `403` without contract tracking, `409` on stale `updated_at` |
53
59
  | GET | `/v1/accounts/{accountId}/documents` | `DocumentResource#list` | Bearer token or `X-Api-Key` | path `accountId*`: string<br>query `status`: string<br>query `method`: string<br>query `search`: string<br>query `tags`: string<br>query `sort`: string<br>query `page`: integer<br>query `per-page`: integer | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: Array<[`Document`](#document)> } |
54
60
  | POST | `/v1/accounts/{accountId}/documents` | `DocumentResource#upload` | Bearer token or `X-Api-Key` | path `accountId*`: string | required; `multipart/form-data` object { `file*`: string (binary) } | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`Document`](#document) } |
55
61
  | GET | `/v1/accounts/{accountId}/documents/search` | `DocumentResource#search` | Bearer token or `X-Api-Key` | path `accountId*`: string<br>query `search`: string<br>query `status`: string<br>query `page`: integer<br>query `per-page`: integer | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: Array<[`Document`](#document)> } |
@@ -111,7 +117,7 @@ Required parameters and object properties are marked with `*`.
111
117
  | POST | `/v1/documents/{documentId}/assignments/{assignmentId}` | `AssignmentResource#sign` | `signer-access-code` query parameter | path `documentId*`: string<br>path `assignmentId*`: string | required; `application/json` Array<object { `itemId*`: string; `fieldId*`: string; `pageId*`: string; `value*`: string }> | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: object } |
112
118
  | PUT | `/v1/documents/{documentId}/assignments/{assignmentId}/reject` | `AssignmentResource#decline` | `signer-access-code` query parameter | path `documentId*`: string<br>path `assignmentId*`: string | required; `application/json` object { `decline_reason*`: string } | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: Array<any> } |
113
119
  | PUT | `/v1/documents/{documentId}/assignments/{assignmentId}/reset-expiration` | `AssignmentResource#reset_expiration` | Bearer token or `X-Api-Key` | path `documentId*`: string<br>path `assignmentId*`: string | required; `application/json` object { `expires_at*`: string (date-time) or `null` to clear } | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`Assignment`](#assignment) } |
114
- | POST | `/v1/documents/{documentId}/assignments/{assignmentId}/signers/{signerId}/estimate-resend-cost` | `AssignmentResource#estimate_resend_cost` | Bearer token or `X-Api-Key` | path `documentId*`: string<br>path `assignmentId*`: string<br>path `signerId*`: string | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`CostEstimate`](#costestimate) } |
120
+ | POST | `/v1/documents/{documentId}/assignments/{assignmentId}/signers/{signerId}/estimate-resend-cost` | `AssignmentResource#estimate_resend_cost` | Bearer token or `X-Api-Key` | path `documentId*`: string<br>path `assignmentId*`: string<br>path `signerId*`: string | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`ResendCostEstimate`](#resendcostestimate) } |
115
121
  | GET | `/v1/documents/{documentId}/assignments/{assignmentId}/signers/{signerId}/identity-verification` | `AssignmentResource#identity_review` | Bearer token or `X-Api-Key` | path `documentId*`, `assignmentId*`, `signerId*`: string | None | `200` envelope data [`SignerIdentityReview`](#signeridentityreview); `404` when no verification exists |
116
122
  | GET | `/v1/documents/{documentId}/assignments/{assignmentId}/signers/{signerId}/identity-verification/images/{name}` | `AssignmentResource#identity_review_image` | Bearer token or `X-Api-Key` | path `documentId*`, `assignmentId*`, `signerId*`: string; `name*`: enum `document_front`, `document_back`, `document_pdf`, `face` | None | `200` binary `image/jpeg`, `image/png` or `application/pdf`, `Cache-Control: no-store`; `404` unless evidence exists and review is InReview |
117
123
  | POST | `/v1/documents/{documentId}/assignments/{assignmentId}/signers/{signerId}/identity-verification/review` | `AssignmentResource#decide_identity_review` | Bearer token or `X-Api-Key` | path `documentId*`, `assignmentId*`, `signerId*`: string | required; `application/json` { `decision*`: enum `Approved`, `Rejected`; `note`: string or null (max 2000 characters) } | `200` envelope data [`SignerIdentityReview`](#signeridentityreview); `400` unless InReview and document is pending_signature or pending_identity_review |
@@ -151,8 +157,8 @@ Required parameters and object properties are marked with `*`.
151
157
  | PUT | `/v1/users/self/mfa/totp/confirm` | `AuthResource#confirm_totp_enrollment` | Bearer token or `X-Api-Key` | None | required; `application/json` object { `id*`: string (SDK `method_id:`); `code*`: string; `password`: string (password); `reauth_code`: string } — `password` or `reauth_code` only when replacing a confirmed method | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: object { `recovery_codes`: Array<string> (shown once) } } |
152
158
  | POST | `/v1/users/self/mfa/recovery-codes` | `AuthResource#regenerate_recovery_codes` | Bearer token or `X-Api-Key` | None | required; `application/json` object { `password`: string (password); `code`: string } — one of the two is required (checked locally) | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: object { `recovery_codes`: Array<string> } } |
153
159
  | DELETE | `/v1/users/self/mfa/{customId}` | `AuthResource#delete_mfa_method` | Bearer token or `X-Api-Key` | path `customId*`: string | required; `application/json` object { `password`: string (password); `code`: string } — one of the two is required (checked locally) | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: object { `is_mfa_enabled`: boolean } } |
154
- | GET | `/v1/users/self/notification-preferences` | `UserResource#notification_preferences` | Bearer token or `X-Api-Key` | None | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`NotificationPreferences`](#notificationpreferences) } |
155
- | PUT | `/v1/users/self/notification-preferences` | `UserResource#update_notification_preferences` | Bearer token or `X-Api-Key` | None | required; `application/json` [`NotificationPreferences`](#notificationpreferences) | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`NotificationPreferences`](#notificationpreferences) } |
160
+ | GET | `/v1/users/self/notification-preferences` | `UserResource#notification_preferences` | User bearer token or `X-Api-Key`; unavailable to third-party OAuth apps | None | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`NotificationPreferences`](#notificationpreferences) } |
161
+ | PUT | `/v1/users/self/notification-preferences` | `UserResource#update_notification_preferences` | User bearer token or `X-Api-Key`; unavailable to third-party OAuth apps | None | required; `application/json` [`NotificationPreferences`](#notificationpreferences) | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`NotificationPreferences`](#notificationpreferences) } |
156
162
  | GET | `/v1/users/self/stats` | `UserResource#stats` | Bearer token or `X-Api-Key` | query `granularity`: string<br>query `month`: string | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: Array<[`DocumentStatsRow`](#documentstatsrow)> } |
157
163
  | POST | `/v1/verify` | `SignerResource#verify_email` | `signer-access-code` query parameter | None | required; `application/json` object { `verification-code*`: string } | `200` `application/json` [`Envelope`](#envelope) |
158
164
  | GET | `/v1/webhooks/event-types` | `WebhookResource#list_event_types` | Bearer token or `X-Api-Key` | None | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: Array<[`WebhookEventType`](#webhookeventtype)> } |
@@ -249,6 +255,21 @@ locally together with the `.pdf` extension and `%PDF-` header.
249
255
  with commas; matching documents carry every listed tag. `DocumentResource#replace_tags` and `#append_tags` accept
250
256
  arrays of tag IDs. Use a tag ID with `DocumentResource#detach_tag`.
251
257
 
258
+ ### Contract tracking
259
+
260
+ Contract terms belong to an existing document; setting terms never uploads, sends, or signs the document.
261
+ `ContractResource#update` creates or partially changes the terms. Explicit `nil` values remain JSON null so they
262
+ clear nullable fields. To record a signed amendment, renewal, or termination instrument, upload that document and
263
+ set its `parent_document_id`, `relation_type`, and changed terms; the delta applies after the instrument is
264
+ certificated. `#get` reads the terms or an instrument, and `#related` always reads the root family.
265
+
266
+ The hourly lifecycle moves active contracts through notice, expiry, and automatic renewal. `#renew` starts the
267
+ next term immediately; sending the current `term` makes a lost-response retry safe. `#terminate` closes the
268
+ contract and records its optional reason. Writers can send the `updated_at` they read as an optimistic-lock
269
+ precondition; the API refuses a stale write with `409`. Reads remain available without the contract-tracking
270
+ plan, while writes answer `403` when the plan lacks the feature. OAuth reads require `documents:read`, and writes
271
+ require `documents:write`.
272
+
252
273
  ### KPI statistics
253
274
 
254
275
  `UserResource#stats` and `AccountResource#stats` validate `granularity` (`monthly` or `daily`) and `month`
@@ -308,9 +329,15 @@ on top of its notification, shown in cost estimates under the breakdown code `Si
308
329
 
309
330
  `DigitalCertificate` has the signer sign with their own ICP-Brasil certificate (A1 or A3) through the Web PKI
310
331
  browser extension, producing a qualified PAdES signature. It requires the Digital Certificate account feature, a
311
- CPF or CNPJ in the signer's `government_id` (accepted by both `SignerResource#create` and `#update`), and that the
312
- signer is alone in its signing step. A CPF requires that person's certificate (an e-CPF, or an e-CNPJ naming them
313
- as legal representative); a CNPJ requires an e-CNPJ for that company, from any of its representatives.
332
+ CPF or CNPJ in the signer's `government_id` (accepted by both `SignerResource#create` and `#update`), a
333
+ `method: 'virtual'` assignment, and that the signer is alone in its signing step. `AssignmentResource#create` and
334
+ `#estimate_cost` reject any other assignment method with `ValidationError`; its stable
335
+ `errors[:code]` is `digital_certificate_requires_virtual`. A CPF requires that person's certificate (an e-CPF, or
336
+ an e-CNPJ naming them as legal representative); a CNPJ requires an e-CNPJ for that company, from any of its
337
+ representatives.
338
+
339
+ A template with positioned signer fields creates a collect assignment, so it cannot be used with a
340
+ `DigitalCertificate` signer. Use a template without positioned signer fields for certificate signing.
314
341
 
315
342
  Certificate signers complete the signature in Assinafy's hosted signing flow, reached through the assignment's
316
343
  `signing_urls`. The SDK does not wrap the two-step Web PKI handshake (`/signers/certificate/start`,
@@ -351,7 +378,9 @@ if lost, delete and recreate the endpoint. `#list_endpoints(url: registered_url)
351
378
  `#delete_endpoint_by_url(registered_url)` deletes that endpoint, returning `nil`. `WebhookResource#create_endpoint`
352
379
  and `#update_endpoint` accept only `url`, `email`, `events`, `name`, `is_active`, and `signing_enabled`.
353
380
  `#register`, `#get`, and `#inactivate` (`/webhooks/subscriptions`, `/webhooks/inactivate`) act on the account's
354
- oldest endpoint; `#register` accepts only `url`, `email`, `events`, and `is_active`.
381
+ oldest endpoint; `#register` accepts only `url`, `email`, `events`, and `is_active`. New or changed endpoint URLs
382
+ must be absolute HTTPS URLs; the SDK rejects other schemes locally, while the API also refuses private, loopback,
383
+ link-local, and other reserved destination addresses.
355
384
 
356
385
  Each delivery is a JSON `POST` carrying `webhook-id` (stable across attempts of one event to one endpoint; use it to
357
386
  deduplicate), `webhook-timestamp` (Unix seconds), and, when signing is enabled, `webhook-signature`. Any `2xx` is
@@ -379,7 +408,7 @@ return-value, and usage details.
379
408
  | `Assinafy::Client.from_config`, `.from_hash` | Construct a client from string- or symbol-keyed configuration. | [`client.rb`](../lib/assinafy/client.rb) |
380
409
  | `Client#faraday_connection` | Return the configured Faraday connection for advanced integration. | [`client.rb`](../lib/assinafy/client.rb) |
381
410
  | `Client#upload_and_request_signatures` | Upload, optionally wait, create signers, and create a virtual assignment. | [`client.rb`](../lib/assinafy/client.rb) |
382
- | `Client#auth`, `#oauth`, `#accounts`, `#users`, `#documents`, `#signers`, `#signer_documents`, `#assignments`, `#webhooks`, `#templates`, `#fields`, `#tags`, `#webhook_verifier` | Return the client's resource and helper instances. | [`client.rb`](../lib/assinafy/client.rb) |
411
+ | `Client#auth`, `#oauth`, `#accounts`, `#users`, `#documents`, `#contracts`, `#signers`, `#signer_documents`, `#assignments`, `#webhooks`, `#templates`, `#fields`, `#tags`, `#webhook_verifier` | Return the client's resource and helper instances. | [`client.rb`](../lib/assinafy/client.rb) |
383
412
  | `Assinafy::Configuration.new`, `.from_hash` | Build configuration directly or from string/symbol keys. | [`configuration.rb`](../lib/assinafy/configuration.rb) |
384
413
  | `Configuration#auth_headers` | Return the selected API-key, bearer, or empty authentication header set. | [`configuration.rb`](../lib/assinafy/configuration.rb) |
385
414
  | Configuration readers/writers: `api_key`, `token`, `account_id`, `base_url`, `webhook_secret`, `timeout`, `logger` | Read or update configuration values; `base_url=` and `timeout=` validate like the constructor. Construct a new client to apply changes. | [`configuration.rb`](../lib/assinafy/configuration.rb) |
@@ -433,6 +462,8 @@ A workspace account (organization).
433
462
  | `notification_sender_type` | string | No | No | enum: `["User", "Account"]` |
434
463
  | `roles` | Array<string> | No | No | — |
435
464
  | `is_delete_allowed` | boolean | No | No | — |
465
+ | `limits` | object | No | No | Current-plan limits; present only on the single-account response. More keys may be added. |
466
+ | `limits.webhook_endpoints` | integer | No | No | Maximum webhook endpoints: 1, or 3 with the Multiple Webhooks feature. |
436
467
  | `created_at` | string (date-time) | No | No | — |
437
468
 
438
469
  ### AccountTheme
@@ -460,7 +491,9 @@ A request for signers to sign a document.
460
491
  | --- | --- | --- | --- | --- |
461
492
  | `resource` | string | No | No | — |
462
493
  | `id` | string | No | No | — |
463
- | `sender_email` | string (email) | No | No | — |
494
+ | `document_id` | string | No | Yes | Document this assignment belongs to; null only after the document is deleted. |
495
+ | `created_at` | string (date-time) | No | No | — |
496
+ | `sender_email` | string (email) | No | Yes | Null once the sender has deleted their account. |
464
497
  | `method` | string | No | No | enum: `["virtual", "collect"]` |
465
498
  | `expires_at` | string (date-time) | No | Yes | — |
466
499
  | `message` | string | No | Yes | — |
@@ -602,12 +635,14 @@ A document and its current lifecycle state.
602
635
  | `artifacts` | object | No | No | Artifact download URLs keyed by name. Always `original`, plus `thumbnail` once one exists. A certificated document also carries `certificated`, `certificate-page` and `bundle`, and `pades` when it was signed with a digital certificate — the PAdES version holds the signers' ICP-Brasil signatures, which certification flattens out of the certificated PDF. |
603
636
  | `is_closed` | boolean | No | No | — |
604
637
  | `signing_url` | string | No | No | — |
638
+ | `verification_code` | string | No | Yes | Code printed on the certificate page; null until certificated. |
639
+ | `verification_url` | string (uri) | No | Yes | Public verification page; null until certificated. |
605
640
  | `decline_reason` | string | No | Yes | — |
606
641
  | `declined_by` | any JSON value | No | Yes | — |
607
642
  | `tags` | Array<object> | No | No | — |
608
643
  | `tags[].id` | string | No | No | — |
609
644
  | `tags[].name` | string | No | No | — |
610
- | `assignment` | object | No | Yes | Expanded assignment data when included via ?expand=assignment; null otherwise. |
645
+ | `assignment` | object | No | Yes | Included by list/details/signer-list/template-generation responses; search and upload omit it. Only rename follows `?expand=assignment`. |
611
646
  | `assignment.resource` | string | No | No | — |
612
647
  | `assignment.id` | string | No | No | — |
613
648
  | `assignment.sender_email` | string (email) | No | No | — |
@@ -620,6 +655,7 @@ A document and its current lifecycle state.
620
655
  | `assignment.summary` | [`AssignmentSummary`](#assignmentsummary) | No | No | — |
621
656
  | `assignment.signing_urls` | Array<[`SigningUrl`](#signingurl)> | No | No | — |
622
657
  | `pages` | Array<[`DocumentPage`](#documentpage)> | No | No | — |
658
+ | `has_contract` | boolean | No | No | Only on workspace `GET /documents/{documentId}` when contract tracking is available. A missing key means unknown. |
623
659
  | `created_at` | string (date-time) | No | No | — |
624
660
  | `updated_at` | string (date-time) | No | No | — |
625
661
 
@@ -638,6 +674,85 @@ A document activity event.
638
674
  | `origin.user-agent` | string | No | No | — |
639
675
  | `created_at` | string (date-time) | No | No | — |
640
676
 
677
+ ### DocumentContract
678
+
679
+ Contract terms tracked on a document. The single-contract and related-family reads can include amendments; the
680
+ workspace list can include the document when `expand=document` is requested.
681
+
682
+ | Property | Type | Required | Nullable | Constraints / description |
683
+ | --- | --- | --- | --- | --- |
684
+ | `resource` | string | No | No | `document_contract` in single-resource responses. |
685
+ | `document_id` | string | No | No | Document that owns the terms. |
686
+ | `status` | string | No | No | enum: `["draft", "active", "expired", "terminated"]` |
687
+ | `counterparty_name` | string | No | Yes | — |
688
+ | `counterparty_taxpayer_id` | string | No | Yes | Normalized CPF/CNPJ, digits only. |
689
+ | `value` | number | No | Yes | At most two decimal places. |
690
+ | `currency` | string | No | Yes | ISO 4217 code; defaults to BRL when a value is set. |
691
+ | `start_date` | string (date) | No | Yes | — |
692
+ | `end_date` | string (date) | No | Yes | — |
693
+ | `auto_renew` | boolean | No | No | — |
694
+ | `renewal_term_months` | integer | No | Yes | — |
695
+ | `notice_period_days` | integer | No | Yes | — |
696
+ | `term` | integer | No | No | Renewal term number, starting at 1. |
697
+ | `owner_id` | string | No | Yes | Active workspace member responsible for lifecycle email. |
698
+ | `metadata` | object | No | Yes | Up to 50 caller-defined string values. |
699
+ | `external_references` | Array<object> | No | No | IDs in outside systems, sorted by system and ID. |
700
+ | `external_references[].system` | string | No | No | Outside system name. |
701
+ | `external_references[].id` | string | No | No | ID in that system. |
702
+ | `document` | [`Document`](#document) | No | No | Only on list results with `expand=document`. |
703
+ | `amendments` | Array<[`DocumentContractAmendment`](#documentcontractamendment)> | No | No | Only on single-contract/family reads. |
704
+ | `created_at` | string (date-time) | No | No | — |
705
+ | `updated_at` | string (date-time) | No | No | Moves on every direct or lifecycle change. |
706
+
707
+ ### DocumentContractAmendment
708
+
709
+ A document linked as an amendment, renewal, or termination instrument. Its terms are a delta applied to the
710
+ parent after this document is certificated.
711
+
712
+ | Property | Type | Required | Nullable | Constraints / description |
713
+ | --- | --- | --- | --- | --- |
714
+ | `resource` | string | No | No | `document_contract_amendment` in single-resource responses. |
715
+ | `document_id` | string | No | No | The instrument document. |
716
+ | `document_name` | string | No | No | Instrument document name. |
717
+ | `parent_document_id` | string | No | No | Root contract document. |
718
+ | `relation_type` | string | No | No | enum: `["amendment", "renewal", "termination"]` |
719
+ | `number` | integer | No | Yes | Applied position; null while pending. |
720
+ | `status` | string | No | No | enum: `["pending", "applied"]` |
721
+ | `document_status` | string | No | No | Current lifecycle status of the instrument document. |
722
+ | `apply_error` | string | No | Yes | Why a signed instrument has not applied yet. |
723
+ | `currency` | string | No | Yes | Currency used by amount values in `terms` and `changes`. |
724
+ | `terms` | object | No | Yes | Only contract terms changed by this instrument. |
725
+ | `changes` | object | No | Yes | Changed fields as `{ from:, to: }`, previewed while pending and fixed once applied. |
726
+ | `applied_at` | string (date-time) | No | Yes | — |
727
+ | `created_at` | string (date-time) | No | No | — |
728
+ | `updated_at` | string (date-time) | No | No | — |
729
+
730
+ ### DocumentContractInput
731
+
732
+ Partial body for setting terms or linking an instrument. Only sent fields change. JSON null clears a nullable
733
+ term; for a pending instrument it removes that term from the delta. `external_references` replaces the whole list.
734
+
735
+ | Property | Type | Required | Nullable | Constraints / description |
736
+ | --- | --- | --- | --- | --- |
737
+ | `status` | string | No | No | enum: `["draft", "active", "expired", "terminated"]`; closed statuses are creation-only. |
738
+ | `counterparty_name` | string | No | Yes | Maximum 255 characters. |
739
+ | `counterparty_taxpayer_id` | string | No | Yes | Valid formatted or unformatted CPF/CNPJ. |
740
+ | `value` | number | No | Yes | 0–9,999,999,999,999.99; at most two decimal places. |
741
+ | `currency` | string | No | Yes | Three-letter code, stored uppercase. |
742
+ | `start_date` | string (date) | No | Yes | `YYYY-MM-DD`, year 1000 or later. |
743
+ | `end_date` | string (date) | No | Yes | On or after `start_date`. |
744
+ | `auto_renew` | boolean | No | Yes | Null resets to false. |
745
+ | `renewal_term_months` | integer | No | Yes | 1–600; defaults to 12 when unset. |
746
+ | `notice_period_days` | integer | No | Yes | 0–3650. |
747
+ | `owner_id` | string | No | Yes | Active member of the workspace. |
748
+ | `metadata` | object | No | Yes | Up to 50 keys of 1–40 letters, digits, `_`, `-`, or `.`, each holding at most 500 characters. |
749
+ | `external_references` | Array<object> | No | Yes | At most 20; null or an empty array clears the list. |
750
+ | `external_references[].system` | string | Yes | No | Up to 64 letters, digits, `_`, `-`, or `.`; stored lowercase. |
751
+ | `external_references[].id` | string | Yes | No | Up to 255 characters. |
752
+ | `parent_document_id` | string | No | Yes | Links to a contract family; null unlinks a pending instrument. |
753
+ | `relation_type` | string | No | No | Required for the first link; enum: `["amendment", "renewal", "termination"]`. |
754
+ | `updated_at` | string (date-time) | No | Yes | Optional optimistic-lock value; a stale value returns `409`. Null is last-write-wins. |
755
+
641
756
  ### DocumentPage
642
757
 
643
758
  | Property | Type | Required | Nullable | Constraints / description |
@@ -789,6 +904,10 @@ Owner-facing document notifications, keyed by notification type. `true` means th
789
904
  | `DocumentProcessingFailed` | boolean | No | No | An uploaded document could not be processed. |
790
905
  | `TemplateProcessingFailed` | boolean | No | No | A template could not be processed. |
791
906
  | `SignerWhatsappFailed` | boolean | No | No | A WhatsApp notification to a signer could not be delivered. |
907
+ | `ContractRenewalNotice` | boolean | No | No | A tracked contract entered its notice period. |
908
+ | `ContractExpiring` | boolean | No | No | A tracked contract ends in 45 or 30 days. |
909
+ | `ContractExpired` | boolean | No | No | A tracked contract ended without renewal. |
910
+ | `ContractRenewed` | boolean | No | No | A tracked contract was automatically renewed into its next term. |
792
911
 
793
912
  ### OAuthRevokeRequest
794
913
 
@@ -819,6 +938,20 @@ Body of `POST /v1/oauth/token`. Sent form-encoded per RFC 6749, or as JSON.
819
938
  | `subject_token_type` | string | No | No | enum: `["urn:ietf:params:oauth:token-type:access_token"]`; Token-exchange grant only. Required; only `urn:ietf:params:oauth:token-type:access_token` is supported. |
820
939
  | `requested_token_type` | string | No | No | enum: `["urn:ietf:params:oauth:token-type:access_token"]`; Token-exchange grant only. Optional; when present it must agree with the only type this server issues. |
821
940
 
941
+ ### ResendCostEstimate
942
+
943
+ Credits needed to resend one signer's notification. A reminder consumes no document allowance.
944
+
945
+ | Property | Type | Required | Nullable | Constraints / description |
946
+ | --- | --- | --- | --- | --- |
947
+ | `total` | number | No | No | Sum of the breakdown costs. |
948
+ | `breakdown` | Array<object> | No | No | Per-charge resend costs. |
949
+ | `breakdown[].code` | string | No | No | For example `NotificationWhatsappResend`. |
950
+ | `breakdown[].name` | string | No | No | Human-readable charge name. |
951
+ | `breakdown[].cost` | number | No | No | Credits charged for this item. |
952
+ | `credit_balance` | number | No | Yes | Available credits; null when credits are unlimited. |
953
+ | `has_sufficient_credits` | boolean | No | No | Whether the workspace can pay for the resend. |
954
+
822
955
  ### Signer
823
956
 
824
957
  A signing party belonging to a workspace account.
@@ -981,6 +1114,10 @@ A single webhook delivery-history entry.
981
1114
  | `endpoint` | string | No | Yes | URL that received the request. |
982
1115
  | `payload` | object | No | Yes | JSON payload sent to the endpoint. |
983
1116
  | `delivered` | boolean | No | No | Whether delivery succeeded. |
1117
+ | `webhook_id` | string | No | Yes | Stable `webhook-id` header across attempts of this event to this endpoint. |
1118
+ | `attempt` | integer | No | Yes | Attempt number, starting at 1. |
1119
+ | `kind` | string | No | No | enum: `["scheduled", "probe", "manual"]` |
1120
+ | `will_retry` | boolean | No | No | Whether another attempt follows this entry. |
984
1121
  | `http_status` | integer | No | Yes | HTTP status returned (null if connection failed). |
985
1122
  | `response_body` | string | No | Yes | Endpoint response body, truncated to 2000 chars. |
986
1123
  | `error` | string | No | Yes | Delivery error message, if any. |
@@ -993,12 +1130,15 @@ A URL that receives the account's webhook events. Every active endpoint subscrib
993
1130
 
994
1131
  | Property | Type | Required | Nullable | Constraints / description |
995
1132
  | --- | --- | --- | --- | --- |
1133
+ | `resource` | string | No | No | Always `activity_event_subscription` in single-resource responses. |
996
1134
  | `id` | string | No | No | Endpoint ID. |
997
1135
  | `name` | string | No | Yes | Label to tell endpoints apart. |
998
- | `url` | string (uri) | No | No | URL that receives the events (http or https). |
1136
+ | `url` | string (uri) | No | No | HTTPS URL that receives the events. |
999
1137
  | `email` | string (email) | No | No | Contact email for delivery-failure notices. |
1000
1138
  | `events` | Array<string> | No | No | Event types delivered to this endpoint. |
1001
1139
  | `is_active` | boolean | No | No | Whether events are delivered to this endpoint. |
1140
+ | `is_held` | boolean | No | No | Whether deliveries are paused after repeated failures. |
1141
+ | `held_since` | integer | No | Yes | Unix timestamp when the pause began; null while deliveries run. |
1002
1142
  | `signing_enabled` | boolean | No | No | Whether deliveries carry a `webhook-signature` header. |
1003
1143
  | `created_at` | string (date-time) | No | No | — |
1004
1144
  | `updated_at` | string (date-time) | No | No | — |
@@ -1048,6 +1188,8 @@ An account's webhook subscription configuration: the account's oldest webhook en
1048
1188
  | --- | --- | --- | --- | --- |
1049
1189
  | `events` | Array<string> | No | No | Event types subscribed for delivery. |
1050
1190
  | `is_active` | boolean | No | No | Whether webhook delivery is active. |
1191
+ | `is_held` | boolean | No | No | Whether deliveries are paused after repeated failures. |
1192
+ | `held_since` | integer | No | Yes | Unix timestamp when the pause began; null while deliveries run. |
1051
1193
  | `url` | string | No | Yes | Webhook endpoint URL. |
1052
1194
  | `email` | string | No | Yes | Contact email for delivery notices. |
1053
1195
  | `updated_at` | string (date-time) | No | Yes | — |
@@ -33,6 +33,8 @@ module Assinafy
33
33
  attr_reader :users
34
34
  # @return [Resources::DocumentResource]
35
35
  attr_reader :documents
36
+ # @return [Resources::ContractResource]
37
+ attr_reader :contracts
36
38
  # @return [Resources::SignerResource]
37
39
  attr_reader :signers
38
40
  # @return [Resources::SignerDocumentResource]
@@ -84,6 +86,7 @@ module Assinafy
84
86
  @accounts = Resources::AccountResource.new(@connection, account_id, @logger)
85
87
  @users = Resources::UserResource.new(@connection, nil, @logger)
86
88
  @documents = Resources::DocumentResource.new(@connection, account_id, @logger)
89
+ @contracts = Resources::ContractResource.new(@connection, account_id, @logger)
87
90
  @signers = Resources::SignerResource.new(@connection, account_id, @logger)
88
91
  @signer_documents = Resources::SignerDocumentResource.new(@connection, nil, @logger)
89
92
  @assignments = Resources::AssignmentResource.new(@connection, account_id, @logger)
@@ -190,6 +193,7 @@ module Assinafy
190
193
  # # },
191
194
  # # assignment: {
192
195
  # # "resource" => "assignment", "id" => "assignment-id",
196
+ # # "document_id" => "document-id", "created_at" => "2026-06-05T21:21:19Z",
193
197
  # # "sender_email" => "sender@example.com", "method" => "virtual",
194
198
  # # "expires_at" => nil, "message" => "Please review and sign",
195
199
  # # "signers" => [{ "id" => "signer-id", "full_name" => "Example Signer",
@@ -90,6 +90,7 @@ module Assinafy
90
90
  # 'name' => 'MT',
91
91
  # 'primary_color' => nil,
92
92
  # 'secondary_color' => nil,
93
+ # 'limits' => { 'webhook_endpoints' => 1 },
93
94
  # 'created_at' => '2026-05-12T18:05:11Z'
94
95
  # }
95
96
  def get(account_id_override = nil)
@@ -11,6 +11,7 @@ module Assinafy
11
11
  class AssignmentResource < BaseResource
12
12
  OPTIONAL_FIELDS = %i[message expires_at copy_receivers].freeze
13
13
  METHODS = %w[virtual collect].freeze
14
+ DIGITAL_CERTIFICATE_REQUIRES_VIRTUAL = 'digital_certificate_requires_virtual'
14
15
 
15
16
  # How a signer's identity is verified before they may sign, set per signer
16
17
  # on the assignment. When omitted it is inferred from
@@ -24,7 +25,7 @@ module Assinafy
24
25
  # certificate (A1 or A3), producing a qualified PAdES signature.
25
26
  # Requires the Digital Certificate feature, a CPF or CNPJ in the
26
27
  # signer's `government_id`, and that the signer is alone in its signing
27
- # step. Charged 0.5 credits per signer on top of its notification
28
+ # step on a virtual assignment. Charged 0.5 credits per signer on top of its notification
28
29
  # (breakdown code `SignatureDigitalCertificate`). A CPF requires that person's
29
30
  # certificate (an e-CPF, or an e-CNPJ naming them as legal
30
31
  # representative); a CNPJ requires an e-CNPJ for that company, from any
@@ -124,6 +125,7 @@ module Assinafy
124
125
 
125
126
  result = { method: method }
126
127
  normalised = signers.map { |ref| normalise_signer_ref(ref, options) }
128
+ validate_certificate_method!(method, normalised)
127
129
  validate_signing_steps!(normalised)
128
130
  result[:signers] = normalised unless normalised.empty?
129
131
  OPTIONAL_FIELDS.each { |key| result[key] = p[key] if p[key] }
@@ -254,6 +256,16 @@ module Assinafy
254
256
  raise ValidationError.new('notification_methods must match the verification_method')
255
257
  end
256
258
 
259
+ def validate_certificate_method!(method, signers)
260
+ return if method == 'virtual'
261
+ return unless signers.any? { |signer| signer[:verification_method] == 'DigitalCertificate' }
262
+
263
+ raise ValidationError.new(
264
+ 'DigitalCertificate signers require method: virtual',
265
+ { code: DIGITAL_CERTIFICATE_REQUIRES_VIRTUAL, method: method }
266
+ )
267
+ end
268
+
257
269
  def validate_signing_steps!(signers)
258
270
  steps = signers.map { |signer| signer[:step] }
259
271
  contiguous = steps.compact.uniq.sort.each_with_index.all? { |step, index| step == index + 1 }
@@ -291,6 +303,8 @@ module Assinafy
291
303
  # data: [
292
304
  # {
293
305
  # 'id' => 'assignment-id',
306
+ # 'document_id' => 'document-id',
307
+ # 'created_at' => '2026-06-05T21:21:19Z',
294
308
  # 'sender_email' => 'sender@example.com',
295
309
  # 'method' => 'virtual',
296
310
  # 'expires_at' => nil,
@@ -330,6 +344,7 @@ module Assinafy
330
344
  # # "message" => "Please sign" }
331
345
  # # => {
332
346
  # # "resource" => "assignment", "id" => "assignment-id",
347
+ # # "document_id" => "document-id", "created_at" => "2026-06-05T21:21:19Z",
333
348
  # # "sender_email" => "sender@example.com", "method" => "virtual",
334
349
  # # "expires_at" => nil, "message" => "Please sign",
335
350
  # # "signers" => [{ "id" => "signer-id", "full_name" => "Example Signer",
@@ -463,9 +478,7 @@ module Assinafy
463
478
  # @param document_id [String]
464
479
  # @param assignment_id [String]
465
480
  # @param signer_id [String]
466
- # @return [Hash] cost breakdown (documents, credits, needs_extra_document, extra_document_cost,
467
- # total_credits, breakdown[], document_balance, credit_balance, has_sufficient_resources,
468
- # blocking_reason, message)
481
+ # @return [Hash] resend cost (`total`, `breakdown`, `credit_balance`, `has_sufficient_credits`)
469
482
  # @raise [Assinafy::ApiError] on an unsuccessful API response
470
483
  # @raise [Assinafy::NetworkError] on transport or TLS failure
471
484
  # @raise [Assinafy::ValidationError] on invalid required input
@@ -474,13 +487,11 @@ module Assinafy
474
487
  # resource.estimate_resend_cost("document-id", "assignment-id", "signer-id")
475
488
  # # (no request body)
476
489
  # # => {
477
- # # "documents" => 1, "credits" => 0.45, "needs_extra_document" => false,
478
- # # "extra_document_cost" => 0, "total_credits" => 0.45,
479
- # # "breakdown" => [{ "code" => "NotificationWhatsapp",
480
- # # "name" => "Whatsapp Notification", "cost" => 0.45,
481
- # # "quantity" => 1, "unit_cost" => 0.45 }],
482
- # # "document_balance" => 10, "credit_balance" => 100,
483
- # # "has_sufficient_resources" => true, "blocking_reason" => nil, "message" => nil
490
+ # # "total" => 0.45,
491
+ # # "breakdown" => [{ "code" => "NotificationWhatsappResend",
492
+ # # "name" => "Whatsapp Notification Resend", "cost" => 0.45 }],
493
+ # # "credit_balance" => 100,
494
+ # # "has_sufficient_credits" => true
484
495
  # # }
485
496
  def estimate_resend_cost(document_id, assignment_id, signer_id)
486
497
  doc_id = require_id(document_id, 'Document ID')