assinafy 1.11.0 → 2.0.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 +4 -4
- data/CHANGELOG.md +39 -0
- data/README.en.md +51 -3
- data/README.md +59 -1
- data/docs/API_REFERENCE.md +96 -12
- data/lib/assinafy/errors.rb +26 -0
- data/lib/assinafy/resources/assignment_resource.rb +115 -12
- data/lib/assinafy/resources/base_resource.rb +20 -6
- data/lib/assinafy/resources/document_resource.rb +28 -54
- data/lib/assinafy/resources/field_resource.rb +31 -16
- data/lib/assinafy/resources/signer_document_resource.rb +75 -21
- data/lib/assinafy/resources/signer_resource.rb +55 -1
- data/lib/assinafy/resources/webhook_resource.rb +83 -55
- data/lib/assinafy/support/webhook_verifier.rb +1 -1
- data/lib/assinafy/version.rb +1 -1
- data/sig/assinafy.rbs +25 -7
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d2a0b97db3a9ad302307e31bccf8f06751fe310dd253417bb129ad921b0a4b3f
|
|
4
|
+
data.tar.gz: 94eb893e22a78777a0b4102d572e274e0f22c5b50cd3768d348f2deac69541ab
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 43838b1c27e22fc5210a8e877a4e1897f50030a6e32801cf490f101de6d18805ed79bff013120801ce90ab53369531142bf678cbe0e04f543139aa72790bab11
|
|
7
|
+
data.tar.gz: f0e442b567d84643b2237ec21edf5b59de9155268451986dcdcc19f841ccf1514e1fd11e112fedc0ff1b8c04439feb411fec7ec37bc06128a86409996745798e
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the `assinafy` Ruby gem are documented here.
|
|
4
4
|
|
|
5
|
+
## [2.0.0] - 2026-10-16
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `IdDocumentFaceMatchBasic` biometric verification in assignments, cost estimates and template generation,
|
|
10
|
+
paired with either Email or Whatsapp notifications.
|
|
11
|
+
- `SignerResource#identity_verification` and `#start_identity_verification` for hosted identity verification
|
|
12
|
+
with explicit biometric-processing consent and signer-only authentication.
|
|
13
|
+
- `AssignmentResource#identity_review`, `#identity_review_image` and `#decide_identity_review` for account
|
|
14
|
+
reviewers to inspect evidence and approve or reject a biometric identity review.
|
|
15
|
+
- `WebhookResource#list_endpoints(url:)` filters by exact URL, and `#delete_endpoint_by_url` removes the endpoint
|
|
16
|
+
registered with that URL.
|
|
17
|
+
|
|
18
|
+
## [1.11.1] - 2026-10-12
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- `Assinafy::ApiError#rate_limit` returns `{ limit:, remaining:, reset: }` from the `X-Rate-Limit-*` headers, and
|
|
23
|
+
`#retry_after` returns the `Retry-After` seconds of a `429 Too Many Requests`.
|
|
24
|
+
- `SignerDocumentResource#list_as_workspace` and `#search_as_workspace` list and search a signer's documents with
|
|
25
|
+
the workspace API key or token.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- `DocumentResource#send_token` always sends `{ "channel": "email", "recipient": "<signer email>" }`, as the API
|
|
30
|
+
requires, and returns `{ document, channel, recipient }`. `recipient:` (or the earlier `email:`) must be an email
|
|
31
|
+
address; calls without a recipient, with a `whatsapp` channel, or with a phone number now raise
|
|
32
|
+
`ValidationError`.
|
|
33
|
+
- `SignerDocumentResource#list` and `#search` require the signer access code and never send workspace
|
|
34
|
+
credentials. Calls that relied on omitting the code to use the API key now raise `ValidationError`; switch them
|
|
35
|
+
to `#list_as_workspace` / `#search_as_workspace`.
|
|
36
|
+
- `FieldResource#validate` and `#validate_multiple` raise `ValidationError` for an explicit `signer_access_code:
|
|
37
|
+
nil` instead of falling back to workspace credentials. Omitting the keyword still uses workspace credentials.
|
|
38
|
+
- `AssignmentResource#estimate_cost` accepts payloads without `signers` or `entries`, which the API treats as
|
|
39
|
+
optional.
|
|
40
|
+
- `DocumentResource#search` and `SignerDocumentResource#search` accept a `nil` search term and omit `search`.
|
|
41
|
+
- `FieldResource#create` sends an explicit `regex: nil` as JSON `null`.
|
|
42
|
+
- `DocumentResource#rename` rejects names longer than 255 characters before sending.
|
|
43
|
+
|
|
5
44
|
## 1.11.0
|
|
6
45
|
|
|
7
46
|
### Added
|
data/README.en.md
CHANGED
|
@@ -388,6 +388,7 @@ the missing side is inferred from the other, and when both are omitted both defa
|
|
|
388
388
|
| `Email` *(default)* | One-time code (OTP) by email before signing | `Email` | 0 credits |
|
|
389
389
|
| `Whatsapp` | One-time code (OTP) over WhatsApp; needs `whatsapp_phone_number` and a paid plan | `Whatsapp` | 0.45 credits (the WhatsApp notification) |
|
|
390
390
|
| `DigitalCertificate` | Signs with their own ICP-Brasil certificate (A1/A3), producing a qualified PAdES signature | `Email` **or** `Whatsapp` | 0.5 credits + its notification (0 or 0.45) |
|
|
391
|
+
| `IdDocumentFaceMatchBasic` | Government ID photos, a photo holding the ID and liveness; requires its account feature and a valid CPF in `government_id` | `Email` **or** `Whatsapp` | Per-signer fee + notification; request an estimate |
|
|
391
392
|
|
|
392
393
|
- Send exactly one channel in `notification_methods` (a one-element array).
|
|
393
394
|
- The certificate charge appears in the cost breakdown under the code `SignatureDigitalCertificate`. Resending a
|
|
@@ -517,6 +518,48 @@ signing.assignments.decline(
|
|
|
517
518
|
)
|
|
518
519
|
```
|
|
519
520
|
|
|
521
|
+
**Biometric signers** use `verification_method: 'IdDocumentFaceMatchBasic'` with exactly one Email or Whatsapp
|
|
522
|
+
notification in virtual/collect assignments or template generation. Store a valid CPF in the signer's
|
|
523
|
+
`government_id` first and call `estimate_cost` or `estimate_cost_from_template` for the current price.
|
|
524
|
+
The OTP example above applies to Email/Whatsapp. For biometrics, read the verification state and collect explicit
|
|
525
|
+
biometric-processing consent before starting:
|
|
526
|
+
|
|
527
|
+
```ruby
|
|
528
|
+
state = signing.signers.identity_verification(signer_access_code: access_code)
|
|
529
|
+
# => { 'can_start' => true, 'status' => nil, 'url' => nil, 'expires_at' => nil, 'completed_at' => nil }
|
|
530
|
+
|
|
531
|
+
# After the signer explicitly consents to biometric processing:
|
|
532
|
+
session = signing.signers.start_identity_verification(consent: true, signer_access_code: access_code)
|
|
533
|
+
# => { 'id' => 'verification-id', 'url' => 'https://verify.assinafy.com.br/c/session#t=token-placeholder',
|
|
534
|
+
# 'expires_at' => '2026-10-16T13:00:00Z' }
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
Open the capture URL unchanged, including its `#t=` fragment. It is a bearer link: keep it out of logs and browser
|
|
538
|
+
storage. For an existing Pending session, use the URL from `identity_verification`. `can_start` is true only
|
|
539
|
+
before any session or after Expired; restarting an Expired session costs nothing extra. Declined or Abandoned
|
|
540
|
+
requires a new document and assignment.
|
|
541
|
+
|
|
542
|
+
InReview lets the signer view, confirm data and sign while account review holds certification. After all signers
|
|
543
|
+
sign, the document can be `pending_identity_review`. The account reviewer uses workspace credentials:
|
|
544
|
+
|
|
545
|
+
```ruby
|
|
546
|
+
review = client.assignments.identity_review(document.fetch('id'), assignment.fetch('id'), signer.fetch('id'))
|
|
547
|
+
# => SignerIdentityReview: status, reason_codes, evidence_available, expected, document, matches,
|
|
548
|
+
# checks, images, ruleset_version, evidence_sha256 and decision metadata
|
|
549
|
+
image = client.assignments.identity_review_image(document.fetch('id'), assignment.fetch('id'), signer.fetch('id'), 'face')
|
|
550
|
+
# => JPEG, PNG or PDF bytes, depending on the name listed in review['images']; do not cache
|
|
551
|
+
|
|
552
|
+
# After the reviewer checks the evidence:
|
|
553
|
+
client.assignments.decide_identity_review(
|
|
554
|
+
document.fetch('id'), assignment.fetch('id'), signer.fetch('id'), decision: 'Approved', note: 'Identity checked'
|
|
555
|
+
)
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
`decision: 'Rejected'` cancels the document and notifies signers and copy receivers. The optional note accepts at
|
|
559
|
+
most 2000 characters, goes to activities and the provider, and is never shown to the signer. Images exist only
|
|
560
|
+
during InReview. Observe signer_identity_review_required, signer_identity_review_approved,
|
|
561
|
+
signer_identity_review_rejected, signer_identity_verified and signer_identity_verification_failed events.
|
|
562
|
+
|
|
520
563
|
**A1/A3 certificate signers** complete their signature in Assinafy's hosted signing flow, opened from
|
|
521
564
|
`assignment['signing_urls']`: they accept the terms, confirm their data, and sign with their A1 or A3 certificate
|
|
522
565
|
through the Web PKI browser extension. The ordinary signing endpoint rejects certificate signers, and the SDK does
|
|
@@ -538,7 +581,7 @@ endpoint = client.webhooks.create_endpoint(
|
|
|
538
581
|
name: 'ERP',
|
|
539
582
|
signing_enabled: true
|
|
540
583
|
)
|
|
541
|
-
secret =
|
|
584
|
+
secret = endpoint.fetch('secret') # returned once on creation; store it immediately, including for OAuth apps
|
|
542
585
|
```
|
|
543
586
|
|
|
544
587
|
- `create_endpoint` requires `url`, `email`, and `events`; `name`, `is_active` (default `true`), and
|
|
@@ -548,7 +591,9 @@ secret = client.webhooks.endpoint_secret(endpoint.fetch('id'))['secret'] # => "
|
|
|
548
591
|
endpoint has none and keeps the current one otherwise; `false` discards it.
|
|
549
592
|
- `rotate_endpoint_secret(id)` returns a new secret; the old one stops working immediately.
|
|
550
593
|
- Reading and rotating secrets is not available to OAuth applications — use an API key or a user session token.
|
|
551
|
-
Both answer `400` when signing is disabled.
|
|
594
|
+
Both answer `400` when signing is disabled. An OAuth app that loses its secret must delete and recreate the endpoint.
|
|
595
|
+
- `list_endpoints(url: registered_url)` filters by exact URL; `delete_endpoint_by_url(registered_url)` removes
|
|
596
|
+
that endpoint and returns `nil` (`404` when no endpoint matches).
|
|
552
597
|
|
|
553
598
|
**Delivery contract.** Each delivery is a `POST` with an `application/json` body and the headers `webhook-id`
|
|
554
599
|
(identical on every attempt of the same event to the same endpoint — deduplicate on it), `webhook-timestamp`
|
|
@@ -633,6 +678,7 @@ rescue Assinafy::ValidationError => e
|
|
|
633
678
|
warn e.errors.inspect
|
|
634
679
|
rescue Assinafy::ApiError => e
|
|
635
680
|
warn "Assinafy returned #{e.status_code}: #{e.message}"
|
|
681
|
+
sleep(e.retry_after) if e.status_code == 429 && e.retry_after # also e.rate_limit => { limit:, remaining:, reset: }
|
|
636
682
|
rescue Assinafy::NetworkError => e
|
|
637
683
|
warn "Network failure: #{e.message}"
|
|
638
684
|
end
|
|
@@ -712,7 +758,7 @@ client.documents.download_page('document-id', 'page-id')
|
|
|
712
758
|
client.documents.delete('document-id')
|
|
713
759
|
client.documents.verify('signature-hash')
|
|
714
760
|
client.documents.public_info('document-id')
|
|
715
|
-
client.documents.send_token('document-id',
|
|
761
|
+
client.documents.send_token('document-id', recipient: 'signer@example.com')
|
|
716
762
|
client.documents.list_tags('document-id')
|
|
717
763
|
client.documents.replace_tags('document-id', ['tag-id-1', 'tag-id-2'])
|
|
718
764
|
client.documents.append_tags('document-id', ['tag-id-3'])
|
|
@@ -806,6 +852,8 @@ query parameter.
|
|
|
806
852
|
client.signer_documents.current('signer-id', signer_access_code: 'code')
|
|
807
853
|
client.signer_documents.list('signer-id', { status: 'pending_signature' }, signer_access_code: 'code')
|
|
808
854
|
client.signer_documents.search('signer-id', 'contract', signer_access_code: 'code')
|
|
855
|
+
client.signer_documents.list_as_workspace('signer-id') # same route with your API key
|
|
856
|
+
client.signer_documents.search_as_workspace('signer-id', 'contract')
|
|
809
857
|
client.signer_documents.sign_multiple(%w[document-id-1 document-id-2], signer_access_code: 'code')
|
|
810
858
|
client.signer_documents.decline_multiple(%w[document-id-1 document-id-2], decline_reason: 'No', signer_access_code: 'code')
|
|
811
859
|
client.signer_documents.download('signer-id', 'document-id', 'pades') # public: no access code needed
|
data/README.md
CHANGED
|
@@ -519,6 +519,7 @@ dois, ambos assumem `Email`.
|
|
|
519
519
|
| `Email` *(padrão)* | código de uso único (OTP) por e-mail, exigido antes de assinar | `Email` | 0 crédito |
|
|
520
520
|
| `Whatsapp` | código de uso único (OTP) por WhatsApp; exige `whatsapp_phone_number` e plano pago | `Whatsapp` | 0,45 crédito (a notificação por WhatsApp) |
|
|
521
521
|
| `DigitalCertificate` | o signatário assina com o **próprio certificado ICP-Brasil (A1/A3)**, gerando uma assinatura **PAdES qualificada** | `Email` **ou** `Whatsapp` | 0,5 crédito + a notificação (0 ou 0,45) |
|
|
522
|
+
| `IdDocumentFaceMatchBasic` | documento de identidade, foto segurando o documento e prova de vida; exige o recurso na conta e CPF válido em `government_id` | `Email` **ou** `Whatsapp` | tarifa por signatário + notificação; consulte a estimativa |
|
|
522
523
|
|
|
523
524
|
- Informe **exatamente um** canal em `notification_methods` (um array de um elemento).
|
|
524
525
|
- O custo do certificado digital aparece na estimativa com o código `SignatureDigitalCertificate`.
|
|
@@ -717,6 +718,55 @@ signatario_client.assignments.decline(
|
|
|
717
718
|
signatario_client.signer_documents.decline_multiple([doc['id']], decline_reason: 'Não', signer_access_code: codigo)
|
|
718
719
|
```
|
|
719
720
|
|
|
721
|
+
### Signatários com verificação biométrica
|
|
722
|
+
|
|
723
|
+
Na criação do assignment (virtual ou collect), ou em `create_from_template`, envie
|
|
724
|
+
`verification_method: 'IdDocumentFaceMatchBasic'` e exatamente um canal de notificação.
|
|
725
|
+
Cadastre o CPF válido do signatário em `government_id` antes de criar o assignment. Use
|
|
726
|
+
`estimate_cost` ou `estimate_cost_from_template` para obter o custo antes de enviar.
|
|
727
|
+
|
|
728
|
+
O fluxo de OTP acima se aplica a `Email` e `Whatsapp`. Para biometria, consulte o estado com o
|
|
729
|
+
código de acesso do signatário e abra uma sessão somente após obter o consentimento explícito:
|
|
730
|
+
|
|
731
|
+
```ruby
|
|
732
|
+
estado = signatario_client.signers.identity_verification(signer_access_code: codigo)
|
|
733
|
+
# => { 'can_start' => true, 'status' => nil, 'url' => nil, 'expires_at' => nil, 'completed_at' => nil }
|
|
734
|
+
|
|
735
|
+
# Após o consentimento explícito do signatário para o processamento biométrico:
|
|
736
|
+
sessao = signatario_client.signers.start_identity_verification(consent: true, signer_access_code: codigo)
|
|
737
|
+
# => { 'id' => 'verification-id', 'url' => 'https://verify.assinafy.com.br/c/session#t=token-placeholder',
|
|
738
|
+
# 'expires_at' => '2026-10-16T13:00:00Z' }
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
Abra `sessao['url']` sem alterar a URL nem o fragmento `#t=`; ela concede acesso à captura e não
|
|
742
|
+
deve aparecer em logs ou armazenamento do navegador. Se já houver uma sessão `Pending`, use a
|
|
743
|
+
URL retornada por `identity_verification`. `can_start` só é verdadeiro antes da primeira sessão
|
|
744
|
+
ou após `Expired`; reiniciar uma sessão expirada não cobra novamente. `Declined` e `Abandoned`
|
|
745
|
+
exigem um novo documento e assignment.
|
|
746
|
+
|
|
747
|
+
Em `InReview`, o signatário continua visualizando, confirmando dados e assinando, mas a certificação
|
|
748
|
+
aguarda a revisão da conta. Depois de todas as assinaturas, o documento pode ficar em
|
|
749
|
+
`pending_identity_review`. Com as credenciais do workspace, o revisor consulta e decide:
|
|
750
|
+
|
|
751
|
+
```ruby
|
|
752
|
+
revisao = client.assignments.identity_review(doc['id'], assignment['id'], signatario['id'])
|
|
753
|
+
# => SignerIdentityReview: status, reason_codes, evidence_available, expected, document,
|
|
754
|
+
# matches, checks, images, ruleset_version, evidence_sha256 e dados da decisão
|
|
755
|
+
imagem = client.assignments.identity_review_image(doc['id'], assignment['id'], signatario['id'], 'face')
|
|
756
|
+
# => bytes JPEG, PNG ou PDF, conforme o nome retornado em revisao['images']; não armazenar em cache
|
|
757
|
+
|
|
758
|
+
# Após o revisor conferir as evidências:
|
|
759
|
+
client.assignments.decide_identity_review(
|
|
760
|
+
doc['id'], assignment['id'], signatario['id'], decision: 'Approved', note: 'Identidade conferida'
|
|
761
|
+
)
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
`decision: 'Rejected'` cancela o documento e notifica signatários e destinatários de cópia.
|
|
765
|
+
A nota opcional aceita até 2000 caracteres, fica na atividade e vai ao provedor; não é mostrada
|
|
766
|
+
ao signatário. As imagens só ficam disponíveis durante `InReview`. Acompanhe também os eventos
|
|
767
|
+
`signer_identity_review_required`, `signer_identity_review_approved`, `signer_identity_review_rejected`,
|
|
768
|
+
`signer_identity_verified` e `signer_identity_verification_failed`.
|
|
769
|
+
|
|
720
770
|
### Signatários com certificado A1/A3
|
|
721
771
|
|
|
722
772
|
O signatário por certificado digital conclui a assinatura no fluxo hospedado da Assinafy, aberto
|
|
@@ -749,9 +799,11 @@ endpoint = client.webhooks.create_endpoint(
|
|
|
749
799
|
endpoint['id'] # => "webhook-endpoint-id"
|
|
750
800
|
|
|
751
801
|
client.webhooks.list_endpoints # do mais antigo ao mais novo
|
|
802
|
+
client.webhooks.list_endpoints(url: 'https://app.example.com/webhooks/assinafy') # URL exata
|
|
752
803
|
client.webhooks.get_endpoint('webhook-endpoint-id')
|
|
753
804
|
client.webhooks.update_endpoint('webhook-endpoint-id', is_active: false) # só os campos enviados
|
|
754
805
|
client.webhooks.delete_endpoint('webhook-endpoint-id') # => nil, libera a vaga
|
|
806
|
+
client.webhooks.delete_endpoint_by_url('https://app.example.com/webhooks/assinafy') # => nil
|
|
755
807
|
```
|
|
756
808
|
|
|
757
809
|
- `create_endpoint` exige `url`, `email` e `events`; `name`, `is_active` (padrão `true`) e
|
|
@@ -763,6 +815,10 @@ client.webhooks.delete_endpoint('webhook-endpoint-id') # =>
|
|
|
763
815
|
|
|
764
816
|
### Segredo de assinatura
|
|
765
817
|
|
|
818
|
+
Com `signing_enabled: true`, `create_endpoint` devolve o segredo uma única vez em
|
|
819
|
+
`endpoint['secret']`. Guarde esse valor na criação, inclusive em aplicativos OAuth. Se o segredo
|
|
820
|
+
de um aplicativo OAuth for perdido, exclua e recrie o endpoint.
|
|
821
|
+
|
|
766
822
|
```ruby
|
|
767
823
|
segredo = client.webhooks.endpoint_secret('webhook-endpoint-id')
|
|
768
824
|
segredo['secret'] # => "whsec_..."
|
|
@@ -964,7 +1020,9 @@ Toda exceção do SDK deriva de `Assinafy::Error` e carrega um `#context` com de
|
|
|
964
1020
|
depuração. `ApiError` trata as duas formas de corpo de erro da API — o erro de framework
|
|
965
1021
|
(`{"name":..., "code":..., "status":...}`) e o envelope de aplicação
|
|
966
1022
|
(`{"status":..., "data":null, "message":...}`) — inclusive quando um `200` traz um `status`
|
|
967
|
-
interno de falha. Num `403`, `e.context[:www_authenticate]` traz o desafio OAuth, quando houver.
|
|
1023
|
+
interno de falha. Num `403`, `e.context[:www_authenticate]` traz o desafio OAuth, quando houver. Num `429`,
|
|
1024
|
+
`e.retry_after` traz os segundos de `Retry-After` e `e.rate_limit` traz `{ limit:, remaining:, reset: }` dos
|
|
1025
|
+
cabeçalhos `X-Rate-Limit-*`.
|
|
968
1026
|
|
|
969
1027
|
---
|
|
970
1028
|
|
data/docs/API_REFERENCE.md
CHANGED
|
@@ -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-
|
|
4
|
-
> `https://api.assinafy.com.br/v1/docs/openapi.json` (
|
|
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).
|
|
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
|
|
@@ -82,8 +82,9 @@ Required parameters and object properties are marked with `*`.
|
|
|
82
82
|
| POST | `/v1/accounts/{accountId}/templates/{templateId}/documents/estimate-cost` | `DocumentResource#estimate_cost_from_template` | Bearer token or `X-Api-Key` | path `accountId*`: string<br>path `templateId*`: string | required; `application/json` object { `signers*`: Array<object { `role_id*`: string; `verification_method`: string; `notification_methods`: Array<string> }> } | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`CostEstimate`](#costestimate) } |
|
|
83
83
|
| GET | `/v1/accounts/{accountId}/theme` | `AccountResource#theme` | Bearer token or `X-Api-Key` | path `accountId*`: string | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`AccountTheme`](#accounttheme) } |
|
|
84
84
|
| GET | `/v1/accounts/{accountId}/webhooks` | `WebhookResource#list_dispatches` | Bearer token or `X-Api-Key` | path `accountId*`: string<br>query `endpoint_id`: string<br>query `event`: string<br>query `delivered`: string<br>query `from`: integer<br>query `to`: integer<br>query `page`: integer<br>query `per-page`: integer | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: Array<[`WebhookDispatch`](#webhookdispatch)> } |
|
|
85
|
-
| GET | `/v1/accounts/{accountId}/webhooks/endpoints` | `WebhookResource#list_endpoints` | Bearer token or `X-Api-Key`; OAuth `account:read` | path `accountId*`: string | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: Array<[`WebhookEndpoint`](#webhookendpoint)> } |
|
|
86
|
-
| POST | `/v1/accounts/{accountId}/webhooks/endpoints` | `WebhookResource#create_endpoint` | Bearer token or `X-Api-Key`; OAuth `webhooks:write` | path `accountId*`: string | required; `application/json` object { `url*`: string (uri); `email*`: string (email); `events*`: Array<string>; `name`: string; `is_active`: boolean; `signing_enabled`: boolean }; unknown keys raise `ValidationError` locally | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`WebhookEndpoint`](#webhookendpoint) }; `400` duplicate URL, `403` past the plan limit (1 endpoint, 3 on paid plans) |
|
|
85
|
+
| GET | `/v1/accounts/{accountId}/webhooks/endpoints` | `WebhookResource#list_endpoints` | Bearer token or `X-Api-Key`; OAuth `account:read` | path `accountId*`: string; query `url`: exact registered URL | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: Array<[`WebhookEndpoint`](#webhookendpoint)> } |
|
|
86
|
+
| POST | `/v1/accounts/{accountId}/webhooks/endpoints` | `WebhookResource#create_endpoint` | Bearer token or `X-Api-Key`; OAuth `webhooks:write` | path `accountId*`: string | required; `application/json` object { `url*`: string (uri); `email*`: string (email); `events*`: Array<string>; `name`: string; `is_active`: boolean; `signing_enabled`: boolean }; unknown keys raise `ValidationError` locally | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`WebhookEndpoint`](#webhookendpoint) with `secret`: string when signing_enabled is true (returned once) }; `400` duplicate URL, `403` past the plan limit (1 endpoint, 3 on paid plans) |
|
|
87
|
+
| DELETE | `/v1/accounts/{accountId}/webhooks/endpoints` | `WebhookResource#delete_endpoint_by_url` | Bearer token or `X-Api-Key`; OAuth `webhooks:write` | path `accountId*`: string; query `url*`: exact registered URL | None | `200` envelope data `[]`; SDK returns `nil`; `404` when no endpoint matches |
|
|
87
88
|
| GET | `/v1/accounts/{accountId}/webhooks/endpoints/{endpointId}` | `WebhookResource#get_endpoint` | Bearer token or `X-Api-Key`; OAuth `account:read` | path `accountId*`: string<br>path `endpointId*`: string | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`WebhookEndpoint`](#webhookendpoint) } |
|
|
88
89
|
| PUT | `/v1/accounts/{accountId}/webhooks/endpoints/{endpointId}` | `WebhookResource#update_endpoint` | Bearer token or `X-Api-Key`; OAuth `webhooks:write` | path `accountId*`: string<br>path `endpointId*`: string | required; `application/json` object { `url`: string (uri); `email`: string (email); `events`: Array<string>; `name`: string; `is_active`: boolean; `signing_enabled`: boolean } — partial; at least one field | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`WebhookEndpoint`](#webhookendpoint) } |
|
|
89
90
|
| DELETE | `/v1/accounts/{accountId}/webhooks/endpoints/{endpointId}` | `WebhookResource#delete_endpoint` | Bearer token or `X-Api-Key`; OAuth `webhooks:write` | path `accountId*`: string<br>path `endpointId*`: string | None | `200` `application/json` [`Envelope`](#envelope); SDK returns `nil` |
|
|
@@ -109,8 +110,11 @@ Required parameters and object properties are marked with `*`.
|
|
|
109
110
|
| POST | `/v1/documents/{documentId}/assignments/estimate-cost` | `AssignmentResource#estimate_cost` | Bearer token or `X-Api-Key` | path `documentId*`: string | required; `application/json` object { `method`: string; `signers`: Array<object { `verification_method`: string; `notification_methods`: Array<string> }>; `entries`: Array<object> } | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`CostEstimate`](#costestimate) } |
|
|
110
111
|
| 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 } |
|
|
111
112
|
| 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> } |
|
|
112
|
-
| 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
|
|
113
|
+
| 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) } |
|
|
113
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) } |
|
|
115
|
+
| 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
|
+
| 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
|
+
| 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 |
|
|
114
118
|
| PUT | `/v1/documents/{documentId}/assignments/{assignmentId}/signers/{signerId}/resend` | `AssignmentResource#resend_notification` | 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`: object { `is_sent`: boolean; `document_id`: string; `signer_id`: string } } |
|
|
115
119
|
| GET | `/v1/documents/{documentId}/assignments/{assignmentId}/whatsapp-notifications` | `AssignmentResource#whatsapp_notifications` | Bearer token or `X-Api-Key` | path `documentId*`: string<br>path `assignmentId*`: string | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: Array<[`WhatsappNotification`](#whatsappnotification)> } |
|
|
116
120
|
| GET | `/v1/documents/{documentId}/download/{artifactName}` | `DocumentResource#download` | Bearer token or `X-Api-Key` | path `documentId*`: string<br>path `artifactName*`: string | None | `200` `application/pdf` string (binary) |
|
|
@@ -124,7 +128,7 @@ Required parameters and object properties are marked with `*`.
|
|
|
124
128
|
| POST | `/v1/oauth/revoke` | `OAuthResource#revoke` | Public (client authenticates with `client_id` in the body) | None | required; `application/x-www-form-urlencoded` (RFC 7009; also accepts `application/json`) object { `token*`: string; `client_id*`: string; `token_type_hint`: string enum `access_token`, `refresh_token`; `client_secret`: string } | `200` empty body — every token outcome reports success |
|
|
125
129
|
| GET | `/v1/oauth/userinfo` | `OAuthResource#userinfo` | Bearer token or `X-Api-Key`; requires the `openid` scope | None | None | `200` `application/json` **flat** object { `sub`: string; `name`: string (nullable); `email`: string (email, nullable); `email_verified`: boolean (nullable) } — not enveloped |
|
|
126
130
|
| GET | `/v1/public/documents/{documentId}` | `DocumentResource#public_info` | Public | path `documentId*`: string | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`Document`](#document) } |
|
|
127
|
-
| PUT | `/v1/public/documents/{documentId}/send-token` | `DocumentResource#send_token` | Public | path `documentId*`: string |
|
|
131
|
+
| PUT | `/v1/public/documents/{documentId}/send-token` | `DocumentResource#send_token` | Public | path `documentId*`: string | required; `application/json` object { `channel*`: `"email"`; `recipient*`: string (signer email) } | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: object { `document`: object { `resource`, `id`, `name`, `page_count`, `created_by` }; `channel`: string; `recipient`: string } } |
|
|
128
132
|
| GET | `/v1/sign` | `AssignmentResource#signer_document` | `signer-access-code` query parameter | query `has_accepted_terms`: boolean | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`Document`](#document) } |
|
|
129
133
|
| POST | `/v1/signature` | `SignerResource#upload_signature` | `signer-access-code` query parameter | query `type`: string<br>query `reuse`: boolean | required; `image/png` string (binary) | `200` `application/json` [`Envelope`](#envelope) |
|
|
130
134
|
| GET | `/v1/signature/{signatureType}` | `SignerResource#download_signature` | `signer-access-code` query parameter | path `signatureType*`: string | None | `200` `image/*` string (binary) |
|
|
@@ -132,9 +136,11 @@ Required parameters and object properties are marked with `*`.
|
|
|
132
136
|
| PUT | `/v1/signers/documents/decline-multiple` | `SignerDocumentResource#decline_multiple` | `signer-access-code` query parameter | None | required; `application/json` object { `document_ids*`: Array<string>; `decline_reason*`: string } | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: Array<any> } |
|
|
133
137
|
| PUT | `/v1/signers/documents/sign-multiple` | `SignerDocumentResource#sign_multiple` | `signer-access-code` query parameter | None | required; `application/json` object { `document_ids*`: Array<string> } | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: Array<any> } |
|
|
134
138
|
| GET | `/v1/signers/self` | `SignerResource#self_data` | `signer-access-code` query parameter | None | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`SignerSelf`](#signerself) } |
|
|
139
|
+
| GET | `/v1/signers/identity-verification` | `SignerResource#identity_verification` | `signer-access-code` query parameter | None | None | `200` envelope data { `can_start`: boolean; `status`: string or null; `url`: string or null (bearer capture URL, preserve fragment); `expires_at`: date-time or null; `completed_at`: date-time or null } |
|
|
140
|
+
| POST | `/v1/signers/identity-verification` | `SignerResource#start_identity_verification` | `signer-access-code` query parameter | None | required; `application/json` { `consent*`: true } | `200` envelope data { `id`: string; `url`: string (bearer capture URL, preserve fragment); `expires_at`: date-time or null }; `400` unless no session exists or the last session Expired |
|
|
135
141
|
| GET | `/v1/signers/{signerId}/document` | `SignerDocumentResource#current` | `signer-access-code` query parameter | path `signerId*`: string | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`Document`](#document) } |
|
|
136
|
-
| GET | `/v1/signers/{signerId}/documents` | `SignerDocumentResource#list` | `signer-access-code` query parameter | path `signerId*`: string<br>query `page`: integer<br>query `per-page`: integer | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: Array<[`Document`](#document)> } |
|
|
137
|
-
| GET | `/v1/signers/{signerId}/documents/search` | `SignerDocumentResource#search` | `signer-access-code` query parameter | path `signerId*`: string<br>query `search`: string | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: Array<[`Document`](#document)> } |
|
|
142
|
+
| GET | `/v1/signers/{signerId}/documents` | `SignerDocumentResource#list` (signer code); `#list_as_workspace` (Bearer token or `X-Api-Key`) | `signer-access-code` query parameter, or workspace credentials | path `signerId*`: string<br>query `page`: integer<br>query `per-page`: integer | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: Array<[`Document`](#document)> } |
|
|
143
|
+
| GET | `/v1/signers/{signerId}/documents/search` | `SignerDocumentResource#search` (signer code); `#search_as_workspace` (Bearer token or `X-Api-Key`) | `signer-access-code` query parameter, or workspace credentials | path `signerId*`: string<br>query `search`: string | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: Array<[`Document`](#document)> } |
|
|
138
144
|
| GET | `/v1/signers/{signerId}/documents/{documentId}/download/{artifactName}` | `SignerDocumentResource#download` | Public | path `signerId*`: string<br>path `documentId*`: string<br>path `artifactName*`: string | None | `200` `application/pdf` string (binary) |
|
|
139
145
|
| GET | `/v1/users/api-keys` | `AuthResource#get_api_key` | Bearer token or `X-Api-Key` | None | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`ApiKey`](#apikey) } |
|
|
140
146
|
| POST | `/v1/users/api-keys` | `AuthResource#create_api_key` | Bearer token or `X-Api-Key` | None | required; `application/json` object { `password*`: string (password) } | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`ApiKey`](#apikey) } |
|
|
@@ -191,6 +197,10 @@ raise `Assinafy::ApiError`:
|
|
|
191
197
|
- `error_name` and `error_code` expose framework `name` and `code` values when present.
|
|
192
198
|
- `context` contains `status_code` and `response_data` for structured logging or support diagnostics, plus
|
|
193
199
|
`www_authenticate` when the response carries that header.
|
|
200
|
+
- `rate_limit` returns `{ limit:, remaining:, reset: }` from the `X-Rate-Limit-Limit`, `X-Rate-Limit-Remaining`
|
|
201
|
+
and `X-Rate-Limit-Reset` headers (reset in seconds), and `retry_after` returns the `Retry-After` seconds of a
|
|
202
|
+
`429 Too Many Requests`; both are `nil` when the headers are absent. Wait at least `retry_after` seconds before
|
|
203
|
+
retrying. Requests are limited per minute, per user when authenticated and per IP address otherwise.
|
|
194
204
|
|
|
195
205
|
Transport, timeout, and TLS failures raise `Assinafy::NetworkError`; caller-side validation failures raise
|
|
196
206
|
`Assinafy::ValidationError` before a request is sent.
|
|
@@ -290,7 +300,7 @@ look successful while the connection stays active.
|
|
|
290
300
|
|
|
291
301
|
Verification and notification methods are coupled per signer: the omitted side is inferred from the other, and
|
|
292
302
|
when both are omitted both are `Email`. Exactly one notification method is allowed. Allowed pairs: `Email` with
|
|
293
|
-
`Email`, `Whatsapp` with `Whatsapp`, and `DigitalCertificate` with either. `Email` costs 0 credits; `Whatsapp`
|
|
303
|
+
`Email`, `Whatsapp` with `Whatsapp`, and `DigitalCertificate` or `IdDocumentFaceMatchBasic` with either. `Email` costs 0 credits; `Whatsapp`
|
|
294
304
|
costs 0.45 credits (its WhatsApp notification, paid plans only); `DigitalCertificate` costs 0.5 credits per signer
|
|
295
305
|
on top of its notification, shown in cost estimates under the breakdown code `SignatureDigitalCertificate`.
|
|
296
306
|
|
|
@@ -307,10 +317,38 @@ Certificate signers complete the signature in Assinafy's hosted signing flow, re
|
|
|
307
317
|
`/signers/certificate/complete`), whose authentication and request/response schemas are not part of the OpenAPI
|
|
308
318
|
contract. Once the flow completes, the `pades` artifact returns the qualified signature.
|
|
309
319
|
|
|
320
|
+
### Biometric identity verification
|
|
321
|
+
|
|
322
|
+
`IdDocumentFaceMatchBasic` requires its account feature and a valid CPF in the signer's `government_id`.
|
|
323
|
+
It uses government ID photos, a photo holding the ID and liveness, and adds a per-signer fee to the chosen
|
|
324
|
+
Email or Whatsapp notification. Call `estimate_cost` or `estimate_cost_from_template` for the current total.
|
|
325
|
+
Both assignment methods and template generation accept this verification method.
|
|
326
|
+
|
|
327
|
+
With the signer access code, read `signers.identity_verification`; after the signer explicitly consents to
|
|
328
|
+
biometric processing, call `signers.start_identity_verification(consent: true, signer_access_code: code)`.
|
|
329
|
+
Open the returned capture URL unchanged, including its fragment; it is a bearer link, so keep it out of logs
|
|
330
|
+
and browser storage. The status response backfills pending provider decisions. `can_start` is true only before
|
|
331
|
+
any session or after Expired; Pending, InReview, Approved, Declined and Abandoned cannot restart. A restart
|
|
332
|
+
replaces an Expired session without another charge. Declined or Abandoned requires a new document and assignment.
|
|
333
|
+
|
|
334
|
+
During InReview the signer may view, confirm data and sign. Certification waits for account approval; once all
|
|
335
|
+
signers sign, the document can be `pending_identity_review`. Read `assignments.identity_review`, download only
|
|
336
|
+
names from its `images` with `identity_review_image`, and submit `decide_identity_review(decision: 'Approved')`
|
|
337
|
+
or `decision: 'Rejected'` while the document is pending_signature or pending_identity_review. Rejection cancels
|
|
338
|
+
the document and notifies signers and copy receivers. Optional `note:` (at most 2000 characters) is recorded in
|
|
339
|
+
activities and sent to the provider; it is never shown to the signer. Images are fetched from the provider,
|
|
340
|
+
available only during InReview, and must not be cached. The review retains summaries, with `evidence_available`
|
|
341
|
+
false when none is retained or fetched. Direct approvals expose evidence through the signer_identity_verified
|
|
342
|
+
activity instead. Observe signer_identity_review_required, signer_identity_review_approved,
|
|
343
|
+
signer_identity_review_rejected, signer_identity_verified and signer_identity_verification_failed events.
|
|
344
|
+
|
|
310
345
|
### Webhook endpoints and deliveries
|
|
311
346
|
|
|
312
347
|
An account can have 1 webhook endpoint, or up to 3 on paid plans; each has a distinct URL, its own events, and its
|
|
313
|
-
own signing setting, and every active endpoint subscribed to an event receives it.
|
|
348
|
+
own signing setting, and every active endpoint subscribed to an event receives it. Creation with
|
|
349
|
+
`signing_enabled: true` returns a `secret` once: store it immediately. OAuth apps cannot read or rotate it later;
|
|
350
|
+
if lost, delete and recreate the endpoint. `#list_endpoints(url: registered_url)` finds an exact URL match and
|
|
351
|
+
`#delete_endpoint_by_url(registered_url)` deletes that endpoint, returning `nil`. `WebhookResource#create_endpoint`
|
|
314
352
|
and `#update_endpoint` accept only `url`, `email`, `events`, `name`, `is_active`, and `signing_enabled`.
|
|
315
353
|
`#register`, `#get`, and `#inactivate` (`/webhooks/subscriptions`, `/webhooks/inactivate`) act on the account's
|
|
316
354
|
oldest endpoint; `#register` accepts only `url`, `email`, `events`, and `is_active`.
|
|
@@ -351,6 +389,7 @@ return-value, and usage details.
|
|
|
351
389
|
| `SignerResource#validate_create!` | Validate and normalize a signer-create body without a network request. | [`signer_resource.rb`](../lib/assinafy/resources/signer_resource.rb) |
|
|
352
390
|
| `SignerResource#find_by_email` | Page through a successful search and return a case-insensitive match or `nil`. | [`signer_resource.rb`](../lib/assinafy/resources/signer_resource.rb) |
|
|
353
391
|
| `SignerDocumentResource#document` | Alias for `#current`. | [`signer_document_resource.rb`](../lib/assinafy/resources/signer_document_resource.rb) |
|
|
392
|
+
| `SignerDocumentResource#list_as_workspace`, `#search_as_workspace` | List/search a signer's documents with workspace credentials; `#list`/`#search` require the signer access code and send only it. | [`signer_document_resource.rb`](../lib/assinafy/resources/signer_document_resource.rb) |
|
|
354
393
|
| `AuthResource#api_key` | Alias for `#get_api_key`. | [`auth_resource.rb`](../lib/assinafy/resources/auth_resource.rb) |
|
|
355
394
|
| `WebhookResource#update` | Alias for `#register` (acts on the oldest endpoint). | [`webhook_resource.rb`](../lib/assinafy/resources/webhook_resource.rb) |
|
|
356
395
|
| `AssignmentResource.build_payload` | Validate and normalize virtual or collect assignment bodies locally. | [`assignment_resource.rb`](../lib/assinafy/resources/assignment_resource.rb) |
|
|
@@ -359,12 +398,12 @@ return-value, and usage details.
|
|
|
359
398
|
| `WebhookVerifier::SECRET_PREFIX`, `::DEFAULT_TOLERANCE` | The `whsec_` secret prefix and the default 300-second timestamp tolerance. | [`webhook_verifier.rb`](../lib/assinafy/support/webhook_verifier.rb) |
|
|
360
399
|
| `WebhookVerifier#extract_event`, `#event_type`, `#event_payload`, `#event_object`, `#event_subject`, `#event_data` | Parse and access webhook envelope fields; `event_data` is retained as a compatibility helper. | [`webhook_verifier.rb`](../lib/assinafy/support/webhook_verifier.rb) |
|
|
361
400
|
| `Assinafy::Error.new`, `#context` | Construct/read the SDK base error and its structured context. | [`errors.rb`](../lib/assinafy/errors.rb) |
|
|
362
|
-
| `Assinafy::ApiError.new`, `.from_response`, `#status_code`, `#response_data`, `#error_name`, `#error_code` | Construct/read an API response error. | [`errors.rb`](../lib/assinafy/errors.rb) |
|
|
401
|
+
| `Assinafy::ApiError.new`, `.from_response`, `#status_code`, `#response_data`, `#error_name`, `#error_code`, `#rate_limit`, `#retry_after` | Construct/read an API response error, including its rate-limit headers. | [`errors.rb`](../lib/assinafy/errors.rb) |
|
|
363
402
|
| `Assinafy::ValidationError.new`, `#errors`; `Assinafy::NetworkError` | Represent caller-side validation and transport failures. | [`errors.rb`](../lib/assinafy/errors.rb) |
|
|
364
403
|
| `Assinafy::OAuthError.new`, `.from_response`, `#error`, `#error_description` | Represent an OAuth endpoint failure; subclasses `ApiError`, so existing rescue clauses keep working. | [`errors.rb`](../lib/assinafy/errors.rb) |
|
|
365
404
|
| `Assinafy::OAuth.generate_code_verifier`, `.code_challenge`, `.generate_state`, `.normalize_scope`, `.validate_code_verifier!`, `.authorization_url` | Build the PKCE pair, CSRF state, and authorization URL for the browser half of the OAuth flow. No network request. | [`oauth.rb`](../lib/assinafy/oauth.rb) |
|
|
366
405
|
| `Assinafy::OAuth::AUTHORIZATION_SERVER`, `::AUTHORIZATION_SERVER_METADATA_URL`, `::AUTHORIZATION_ENDPOINT`, `::CODE_CHALLENGE_METHOD`, `::SCOPES` | Published authorization-server endpoints and scopes. | [`oauth.rb`](../lib/assinafy/oauth.rb) |
|
|
367
|
-
| `AssignmentResource::VERIFICATION_METHODS`, `::NOTIFICATION_METHODS` | The signer verification (`Email`, `Whatsapp`, `DigitalCertificate`) and notification (`Email`, `Whatsapp`) enums, validated locally by `build_payload`. | [`assignment_resource.rb`](../lib/assinafy/resources/assignment_resource.rb) |
|
|
406
|
+
| `AssignmentResource::VERIFICATION_METHODS`, `::NOTIFICATION_METHODS` | The signer verification (`Email`, `Whatsapp`, `DigitalCertificate`, `IdDocumentFaceMatchBasic`) and notification (`Email`, `Whatsapp`) enums, validated locally by `build_payload`. | [`assignment_resource.rb`](../lib/assinafy/resources/assignment_resource.rb) |
|
|
368
407
|
| `Assinafy::Utils.handle_assinafy_response`, `.clean_params`, `.query_params`, `.body_params`, `.require_email`, `.require_expiration` | Internal public helpers used by resources for envelope and parameter normalization; applications should prefer resource methods. | [`utils.rb`](../lib/assinafy/utils.rb) |
|
|
369
408
|
| `Assinafy::NullLogger#debug`, `#info`, `#warn`, `#error`, `#fatal`, `#unknown` | Internal no-op logger methods used when no logger is configured. | [`null_logger.rb`](../lib/assinafy/null_logger.rb) |
|
|
370
409
|
| `Assinafy::VERSION` | Published SDK version constant. | [`version.rb`](../lib/assinafy/version.rb) |
|
|
@@ -461,6 +500,8 @@ A signer within an assignment: the base Signer plus per-assignment verification/
|
|
|
461
500
|
| `notified` | boolean | No | Yes | — |
|
|
462
501
|
| `completed` | boolean | No | Yes | Only present in account-owner contexts. |
|
|
463
502
|
| `notification_history` | Array<[`NotificationHistoryEntry`](#notificationhistoryentry)> | No | Yes | Per-channel delivery history for this signer (email + WhatsApp), most-recent send order. |
|
|
503
|
+
| `government_id` | string | No | Yes | Signer's normalized CPF (11 digits) or CNPJ (14 characters; may be alphanumeric). |
|
|
504
|
+
| `identity_verification_status` | string | No | Yes | Latest identity-document verification status; `InReview` means the account must decide it before the document can be certificated. enum: `["Pending", "InReview", "Approved", "Declined", "Expired", "Abandoned"]` |
|
|
464
505
|
|
|
465
506
|
### AssignmentSummary
|
|
466
507
|
|
|
@@ -790,6 +831,47 @@ A signing party belonging to a workspace account.
|
|
|
790
831
|
| `email` | string (email) | No | Yes | — |
|
|
791
832
|
| `whatsapp_phone_number` | string | No | Yes | E.164 format; normalized on save. |
|
|
792
833
|
| `has_accepted_terms` | boolean | No | No | — |
|
|
834
|
+
| `government_id` | string | No | Yes | Signer's normalized CPF (11 digits) or CNPJ (14 characters; may be alphanumeric). |
|
|
835
|
+
|
|
836
|
+
### SignerIdentityReview
|
|
837
|
+
|
|
838
|
+
Account review of a biometric identity verification. Images are available only during InReview.
|
|
839
|
+
|
|
840
|
+
| Property | Type | Required | Nullable | Constraints / description |
|
|
841
|
+
| --- | --- | --- | --- | --- |
|
|
842
|
+
| `id` | string | No | No | — |
|
|
843
|
+
| `status` | string | No | No | — enum: `["Pending", "InReview", "Approved", "Declined", "Expired", "Abandoned"]` |
|
|
844
|
+
| `provider` | string | No | No | — enum: `["AssinafyVerify"]` |
|
|
845
|
+
| `verification_method` | string | No | No | — enum: `["IdDocumentFaceMatchBasic"]` |
|
|
846
|
+
| `reason_codes` | Array<object> | No | No | Why the provider sent it to review, in its order. Unknown codes have the code as description. |
|
|
847
|
+
| `reason_codes[].code` | string | No | No | — |
|
|
848
|
+
| `reason_codes[].description` | string | No | No | — |
|
|
849
|
+
| `evidence_available` | boolean | No | No | — |
|
|
850
|
+
| `expected` | object | No | Yes | What the signer was registered with. |
|
|
851
|
+
| `expected.name` | string | No | Yes | — |
|
|
852
|
+
| `expected.cpf` | string | No | Yes | — |
|
|
853
|
+
| `document` | object | No | Yes | What was read from the document. |
|
|
854
|
+
| `document.type` | string | No | Yes | — |
|
|
855
|
+
| `document.name` | string | No | Yes | — |
|
|
856
|
+
| `document.cpf` | string | No | Yes | — |
|
|
857
|
+
| `matches` | object | No | Yes | — |
|
|
858
|
+
| `matches.cpf` | boolean | No | Yes | — |
|
|
859
|
+
| `matches.name` | number | No | Yes | Name similarity, 0-1. |
|
|
860
|
+
| `matches.birth_date` | boolean | No | Yes | — |
|
|
861
|
+
| `checks` | Array<object> | No | No | — |
|
|
862
|
+
| `checks[].name` | string | No | No | — |
|
|
863
|
+
| `checks[].result` | string | No | No | pass, review, skipped, or a failure result. |
|
|
864
|
+
| `checks[].score` | number | No | Yes | — |
|
|
865
|
+
| `checks[].reasons` | Array<string> | No | No | — |
|
|
866
|
+
| `images` | Array<string> | No | No | The evidence images the reviewer can open with **Signer identity review image**, in display order: `document_front`, `document_back`, `document_pdf`, `face`. Only while `status` is `InReview`; empty once decided. |
|
|
867
|
+
| `ruleset_version` | string | No | Yes | — |
|
|
868
|
+
| `evidence_sha256` | string | No | Yes | — |
|
|
869
|
+
| `reviewed_by` | object | No | Yes | — |
|
|
870
|
+
| `reviewed_by.id` | string | No | No | — |
|
|
871
|
+
| `reviewed_by.name` | string | No | No | — |
|
|
872
|
+
| `reviewed_at` | string (date-time) | No | Yes | — |
|
|
873
|
+
| `review_note` | string | No | Yes | — |
|
|
874
|
+
| `completed_at` | string (date-time) | No | Yes | — |
|
|
793
875
|
|
|
794
876
|
### SignerSelf
|
|
795
877
|
|
|
@@ -806,6 +888,8 @@ The current signer, as returned by `GET /v1/signers/self`. Extends Signer with t
|
|
|
806
888
|
| `has_signature` | boolean | No | No | Whether the signer has a saved signature image stored. |
|
|
807
889
|
| `has_initial` | boolean | No | No | Whether the signer has a saved initials image stored. |
|
|
808
890
|
| `is_signature_reusable` | boolean | No | No | Whether the signer opted to reuse their saved signature/initials in future processes. When false, clients should not pre-render the saved image even if `has_signature`/`has_initial` is true. |
|
|
891
|
+
| `government_id` | string | No | Yes | Signer's normalized CPF (11 digits) or CNPJ (14 characters; may be alphanumeric). |
|
|
892
|
+
| `verification_method` | string | No | No | How this signature request verifies the signer. `Email`/`Whatsapp` ask for the 6-digit code; `IdDocumentFaceMatchBasic` runs the identity document check instead (see **Identity document verification status**). enum: `["Email", "Whatsapp", "DigitalCertificate", "IdDocumentFaceMatchBasic"]` |
|
|
809
893
|
|
|
810
894
|
### SigningUrl
|
|
811
895
|
|
data/lib/assinafy/errors.rb
CHANGED
|
@@ -52,6 +52,32 @@ module Assinafy
|
|
|
52
52
|
@error_code = response_data['code']
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
+
# Rate-limit window reported by the `X-Rate-Limit-Limit`, `X-Rate-Limit-Remaining` and
|
|
56
|
+
# `X-Rate-Limit-Reset` response headers. Only the headers present are included.
|
|
57
|
+
#
|
|
58
|
+
# @return [Hash{Symbol=>Integer}, nil] `{ limit:, remaining:, reset: }` (reset in seconds), or nil
|
|
59
|
+
# @example
|
|
60
|
+
# e.rate_limit # => { limit: 60, remaining: 0, reset: 42 }
|
|
61
|
+
def rate_limit
|
|
62
|
+
context[:rate_limit]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Seconds to wait before retrying, from the `Retry-After` header of a `429 Too Many Requests`.
|
|
66
|
+
#
|
|
67
|
+
# @return [Integer, nil]
|
|
68
|
+
# @example Back off on a rate-limited request
|
|
69
|
+
# begin
|
|
70
|
+
# client.documents.list
|
|
71
|
+
# rescue Assinafy::ApiError => e
|
|
72
|
+
# raise unless e.status_code == 429
|
|
73
|
+
#
|
|
74
|
+
# sleep(e.retry_after || 1)
|
|
75
|
+
# retry
|
|
76
|
+
# end
|
|
77
|
+
def retry_after
|
|
78
|
+
context[:retry_after]
|
|
79
|
+
end
|
|
80
|
+
|
|
55
81
|
# Build an {ApiError} from an HTTP status and parsed body. Reads the human
|
|
56
82
|
# message from `message`, `error`, or `name` (in that order).
|
|
57
83
|
#
|