assinafy 1.11.0 → 1.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -0
- data/README.en.md +4 -1
- data/README.md +3 -1
- data/docs/API_REFERENCE.md +11 -6
- data/lib/assinafy/errors.rb +26 -0
- data/lib/assinafy/resources/assignment_resource.rb +13 -10
- 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/version.rb +1 -1
- data/sig/assinafy.rbs +11 -6
- 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: 4c9d44ba9996ee18372b0c965c691f9a74695b98ffd85ebe5e43ae29a4f3b662
|
|
4
|
+
data.tar.gz: a0313ec17a476c5439eab8fd004936c337b4b99e14323b8dfd864553b68f22d9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4a3e1ef28628add0a0e5abfc6f59f09f5ea0fe09c067a6fbb8cb3b767771590479bb8493b525ce389d2a572f15eea8257ed9a1ba47e68e7be1297603777febbd
|
|
7
|
+
data.tar.gz: 159e300170cd02a74d7668084362b5c80f5e99eea2653e2fd5f9faa0a838446b3987f9d5cd9df131cf0f2feba6a0dd50fdae0de4351ebba213c92e942ea3d529
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the `assinafy` Ruby gem are documented here.
|
|
4
4
|
|
|
5
|
+
## [1.11.1] - 2026-10-12
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `Assinafy::ApiError#rate_limit` returns `{ limit:, remaining:, reset: }` from the `X-Rate-Limit-*` headers, and
|
|
10
|
+
`#retry_after` returns the `Retry-After` seconds of a `429 Too Many Requests`.
|
|
11
|
+
- `SignerDocumentResource#list_as_workspace` and `#search_as_workspace` list and search a signer's documents with
|
|
12
|
+
the workspace API key or token.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- `DocumentResource#send_token` always sends `{ "channel": "email", "recipient": "<signer email>" }`, as the API
|
|
17
|
+
requires, and returns `{ document, channel, recipient }`. `recipient:` (or the earlier `email:`) must be an email
|
|
18
|
+
address; calls without a recipient, with a `whatsapp` channel, or with a phone number now raise
|
|
19
|
+
`ValidationError`.
|
|
20
|
+
- `SignerDocumentResource#list` and `#search` require the signer access code and never send workspace
|
|
21
|
+
credentials. Calls that relied on omitting the code to use the API key now raise `ValidationError`; switch them
|
|
22
|
+
to `#list_as_workspace` / `#search_as_workspace`.
|
|
23
|
+
- `FieldResource#validate` and `#validate_multiple` raise `ValidationError` for an explicit `signer_access_code:
|
|
24
|
+
nil` instead of falling back to workspace credentials. Omitting the keyword still uses workspace credentials.
|
|
25
|
+
- `AssignmentResource#estimate_cost` accepts payloads without `signers` or `entries`, which the API treats as
|
|
26
|
+
optional.
|
|
27
|
+
- `DocumentResource#search` and `SignerDocumentResource#search` accept a `nil` search term and omit `search`.
|
|
28
|
+
- `FieldResource#create` sends an explicit `regex: nil` as JSON `null`.
|
|
29
|
+
- `DocumentResource#rename` rejects names longer than 255 characters before sending.
|
|
30
|
+
|
|
5
31
|
## 1.11.0
|
|
6
32
|
|
|
7
33
|
### Added
|
data/README.en.md
CHANGED
|
@@ -633,6 +633,7 @@ rescue Assinafy::ValidationError => e
|
|
|
633
633
|
warn e.errors.inspect
|
|
634
634
|
rescue Assinafy::ApiError => e
|
|
635
635
|
warn "Assinafy returned #{e.status_code}: #{e.message}"
|
|
636
|
+
sleep(e.retry_after) if e.status_code == 429 && e.retry_after # also e.rate_limit => { limit:, remaining:, reset: }
|
|
636
637
|
rescue Assinafy::NetworkError => e
|
|
637
638
|
warn "Network failure: #{e.message}"
|
|
638
639
|
end
|
|
@@ -712,7 +713,7 @@ client.documents.download_page('document-id', 'page-id')
|
|
|
712
713
|
client.documents.delete('document-id')
|
|
713
714
|
client.documents.verify('signature-hash')
|
|
714
715
|
client.documents.public_info('document-id')
|
|
715
|
-
client.documents.send_token('document-id',
|
|
716
|
+
client.documents.send_token('document-id', recipient: 'signer@example.com')
|
|
716
717
|
client.documents.list_tags('document-id')
|
|
717
718
|
client.documents.replace_tags('document-id', ['tag-id-1', 'tag-id-2'])
|
|
718
719
|
client.documents.append_tags('document-id', ['tag-id-3'])
|
|
@@ -806,6 +807,8 @@ query parameter.
|
|
|
806
807
|
client.signer_documents.current('signer-id', signer_access_code: 'code')
|
|
807
808
|
client.signer_documents.list('signer-id', { status: 'pending_signature' }, signer_access_code: 'code')
|
|
808
809
|
client.signer_documents.search('signer-id', 'contract', signer_access_code: 'code')
|
|
810
|
+
client.signer_documents.list_as_workspace('signer-id') # same route with your API key
|
|
811
|
+
client.signer_documents.search_as_workspace('signer-id', 'contract')
|
|
809
812
|
client.signer_documents.sign_multiple(%w[document-id-1 document-id-2], signer_access_code: 'code')
|
|
810
813
|
client.signer_documents.decline_multiple(%w[document-id-1 document-id-2], decline_reason: 'No', signer_access_code: 'code')
|
|
811
814
|
client.signer_documents.download('signer-id', 'document-id', 'pades') # public: no access code needed
|
data/README.md
CHANGED
|
@@ -964,7 +964,9 @@ Toda exceção do SDK deriva de `Assinafy::Error` e carrega um `#context` com de
|
|
|
964
964
|
depuração. `ApiError` trata as duas formas de corpo de erro da API — o erro de framework
|
|
965
965
|
(`{"name":..., "code":..., "status":...}`) e o envelope de aplicação
|
|
966
966
|
(`{"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.
|
|
967
|
+
interno de falha. Num `403`, `e.context[:www_authenticate]` traz o desafio OAuth, quando houver. Num `429`,
|
|
968
|
+
`e.retry_after` traz os segundos de `Retry-After` e `e.rate_limit` traz `{ limit:, remaining:, reset: }` dos
|
|
969
|
+
cabeçalhos `X-Rate-Limit-*`.
|
|
968
970
|
|
|
969
971
|
---
|
|
970
972
|
|
data/docs/API_REFERENCE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Assinafy Ruby SDK API Reference
|
|
2
2
|
|
|
3
|
-
> Contract source: Assinafy API v1 OpenAPI 3.1.0, retrieved 2026-10-
|
|
3
|
+
> Contract source: Assinafy API v1 OpenAPI 3.1.0, retrieved 2026-10-12 from
|
|
4
4
|
> `https://api.assinafy.com.br/v1/docs/openapi.json` (81 paths, 106 operations, 44 schemas).
|
|
5
5
|
> `scripts/check_api_contract.rb` validates contract compatibility weekly.
|
|
6
6
|
|
|
@@ -109,7 +109,7 @@ Required parameters and object properties are marked with `*`.
|
|
|
109
109
|
| 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
110
|
| 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
111
|
| 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
|
|
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*`: string (date-time) or `null` to clear } | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`Assignment`](#assignment) } |
|
|
113
113
|
| 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) } |
|
|
114
114
|
| 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
115
|
| 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)> } |
|
|
@@ -124,7 +124,7 @@ Required parameters and object properties are marked with `*`.
|
|
|
124
124
|
| 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
125
|
| 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
126
|
| 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 |
|
|
127
|
+
| 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
128
|
| 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
129
|
| 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
130
|
| GET | `/v1/signature/{signatureType}` | `SignerResource#download_signature` | `signer-access-code` query parameter | path `signatureType*`: string | None | `200` `image/*` string (binary) |
|
|
@@ -133,8 +133,8 @@ Required parameters and object properties are marked with `*`.
|
|
|
133
133
|
| 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
134
|
| GET | `/v1/signers/self` | `SignerResource#self_data` | `signer-access-code` query parameter | None | None | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`SignerSelf`](#signerself) } |
|
|
135
135
|
| 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)> } |
|
|
136
|
+
| 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)> } |
|
|
137
|
+
| 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
138
|
| 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
139
|
| 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
140
|
| 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 +191,10 @@ raise `Assinafy::ApiError`:
|
|
|
191
191
|
- `error_name` and `error_code` expose framework `name` and `code` values when present.
|
|
192
192
|
- `context` contains `status_code` and `response_data` for structured logging or support diagnostics, plus
|
|
193
193
|
`www_authenticate` when the response carries that header.
|
|
194
|
+
- `rate_limit` returns `{ limit:, remaining:, reset: }` from the `X-Rate-Limit-Limit`, `X-Rate-Limit-Remaining`
|
|
195
|
+
and `X-Rate-Limit-Reset` headers (reset in seconds), and `retry_after` returns the `Retry-After` seconds of a
|
|
196
|
+
`429 Too Many Requests`; both are `nil` when the headers are absent. Wait at least `retry_after` seconds before
|
|
197
|
+
retrying. Requests are limited per minute, per user when authenticated and per IP address otherwise.
|
|
194
198
|
|
|
195
199
|
Transport, timeout, and TLS failures raise `Assinafy::NetworkError`; caller-side validation failures raise
|
|
196
200
|
`Assinafy::ValidationError` before a request is sent.
|
|
@@ -351,6 +355,7 @@ return-value, and usage details.
|
|
|
351
355
|
| `SignerResource#validate_create!` | Validate and normalize a signer-create body without a network request. | [`signer_resource.rb`](../lib/assinafy/resources/signer_resource.rb) |
|
|
352
356
|
| `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
357
|
| `SignerDocumentResource#document` | Alias for `#current`. | [`signer_document_resource.rb`](../lib/assinafy/resources/signer_document_resource.rb) |
|
|
358
|
+
| `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
359
|
| `AuthResource#api_key` | Alias for `#get_api_key`. | [`auth_resource.rb`](../lib/assinafy/resources/auth_resource.rb) |
|
|
355
360
|
| `WebhookResource#update` | Alias for `#register` (acts on the oldest endpoint). | [`webhook_resource.rb`](../lib/assinafy/resources/webhook_resource.rb) |
|
|
356
361
|
| `AssignmentResource.build_payload` | Validate and normalize virtual or collect assignment bodies locally. | [`assignment_resource.rb`](../lib/assinafy/resources/assignment_resource.rb) |
|
|
@@ -359,7 +364,7 @@ return-value, and usage details.
|
|
|
359
364
|
| `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
365
|
| `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
366
|
| `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) |
|
|
367
|
+
| `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
368
|
| `Assinafy::ValidationError.new`, `#errors`; `Assinafy::NetworkError` | Represent caller-side validation and transport failures. | [`errors.rb`](../lib/assinafy/errors.rb) |
|
|
364
369
|
| `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
370
|
| `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) |
|
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
|
#
|
|
@@ -52,12 +52,14 @@ module Assinafy
|
|
|
52
52
|
# - `signers: [{ id:, verification_method:, notification_methods:, step: }]`
|
|
53
53
|
# - Legacy `signer_ids:`/`signerIds:` arrays of IDs
|
|
54
54
|
#
|
|
55
|
-
# @note
|
|
56
|
-
# requires positioned `entries
|
|
55
|
+
# @note Creation requires top-level `signers` for both methods, and `collect` additionally
|
|
56
|
+
# requires positioned `entries`; both are optional in estimate-cost payloads.
|
|
57
57
|
# @param payload [Hash]
|
|
58
58
|
# @param options [Hash]
|
|
59
59
|
# @option options [Boolean] :allow_signers_without_id allow estimate-cost
|
|
60
60
|
# payloads where method-only signer descriptors carry no id
|
|
61
|
+
# @option options [Boolean] :require_signers (true) false for estimate-cost payloads, where
|
|
62
|
+
# `signers` and `entries` are optional; an empty `signers` list is then omitted
|
|
61
63
|
# @return [Hash] string-keyed body suitable for {#create} / {#estimate_cost}
|
|
62
64
|
# @raise [Assinafy::ValidationError] on missing required fields
|
|
63
65
|
# @example Bare signer IDs are normalised into { id: } hashes (virtual method)
|
|
@@ -114,12 +116,13 @@ module Assinafy
|
|
|
114
116
|
entries = p[:entries]
|
|
115
117
|
method = (p[:method] || 'virtual').to_s
|
|
116
118
|
|
|
117
|
-
validate_method!(method, signers, entries, p)
|
|
119
|
+
validate_method!(method, signers, entries, p, options.fetch(:require_signers, true))
|
|
118
120
|
validate_optional_fields!(p)
|
|
119
121
|
|
|
120
122
|
result = { method: method }
|
|
121
|
-
|
|
122
|
-
validate_signing_steps!(
|
|
123
|
+
normalised = signers.map { |ref| normalise_signer_ref(ref, options) }
|
|
124
|
+
validate_signing_steps!(normalised)
|
|
125
|
+
result[:signers] = normalised unless normalised.empty?
|
|
123
126
|
OPTIONAL_FIELDS.each { |key| result[key] = p[key] if p[key] }
|
|
124
127
|
result[:entries] = entries if entries
|
|
125
128
|
Utils.body_params(result)
|
|
@@ -127,13 +130,13 @@ module Assinafy
|
|
|
127
130
|
|
|
128
131
|
private
|
|
129
132
|
|
|
130
|
-
def validate_method!(method, signers, entries, payload)
|
|
133
|
+
def validate_method!(method, signers, entries, payload, require_signers)
|
|
131
134
|
unless METHODS.include?(method)
|
|
132
135
|
raise ValidationError.new("Assignment method must be one of: #{METHODS.join(', ')}")
|
|
133
136
|
end
|
|
137
|
+
return unless require_signers
|
|
134
138
|
|
|
135
|
-
# `signers`
|
|
136
|
-
# estimate is priced per signer. Positioned fields do not replace this list.
|
|
139
|
+
# Creation needs `signers` for both methods; positioned fields do not replace this list.
|
|
137
140
|
if signers.empty?
|
|
138
141
|
raise ValidationError.new(
|
|
139
142
|
'At least one signer is required',
|
|
@@ -356,7 +359,7 @@ module Assinafy
|
|
|
356
359
|
# `id`. An empty descriptor (`{}`) defaults both methods to `Email`.
|
|
357
360
|
#
|
|
358
361
|
# @param document_id [String]
|
|
359
|
-
# @param payload [Hash]
|
|
362
|
+
# @param payload [Hash] `method`, and optional `signers` (method-only descriptors) and `entries`
|
|
360
363
|
# @return [Hash] cost breakdown (documents, credits, needs_extra_document, extra_document_cost,
|
|
361
364
|
# total_credits, breakdown[], document_balance, credit_balance, has_sufficient_resources,
|
|
362
365
|
# blocking_reason, message)
|
|
@@ -379,7 +382,7 @@ module Assinafy
|
|
|
379
382
|
# # }
|
|
380
383
|
def estimate_cost(document_id, payload)
|
|
381
384
|
doc_id = require_id(document_id, 'Document ID')
|
|
382
|
-
body = self.class.build_payload(payload, allow_signers_without_id: true)
|
|
385
|
+
body = self.class.build_payload(payload, allow_signers_without_id: true, require_signers: false)
|
|
383
386
|
|
|
384
387
|
call('Failed to estimate assignment cost') do
|
|
385
388
|
http_post("documents/#{doc_id}/assignments/estimate-cost", body)
|
|
@@ -17,6 +17,11 @@ module Assinafy
|
|
|
17
17
|
total: 'x-pagination-total-count',
|
|
18
18
|
last_page: 'x-pagination-page-count'
|
|
19
19
|
}.freeze
|
|
20
|
+
RATE_LIMIT_HEADERS = {
|
|
21
|
+
limit: 'x-rate-limit-limit',
|
|
22
|
+
remaining: 'x-rate-limit-remaining',
|
|
23
|
+
reset: 'x-rate-limit-reset'
|
|
24
|
+
}.freeze
|
|
20
25
|
|
|
21
26
|
# @param connection [Faraday::Connection]
|
|
22
27
|
# @param default_account_id [String, nil] used by `account_id` when no override is passed
|
|
@@ -297,23 +302,32 @@ module Assinafy
|
|
|
297
302
|
|
|
298
303
|
# On a `403` the `WWW-Authenticate` challenge names the scope an OAuth
|
|
299
304
|
# token is missing, which tells "reconnect with more scope" apart from
|
|
300
|
-
# "wrong workspace or role", so it travels with the error
|
|
305
|
+
# "wrong workspace or role", so it travels with the error, as do the
|
|
306
|
+
# rate-limit headers a `429` needs for backing off.
|
|
301
307
|
def check_status!(response, _label)
|
|
302
308
|
return if (200..299).cover?(response.status)
|
|
303
309
|
|
|
304
|
-
raise
|
|
310
|
+
raise with_response_headers(error_class.from_response(response.status, response.body), response)
|
|
305
311
|
end
|
|
306
312
|
|
|
307
|
-
# A 2xx envelope can still carry an error status, with the same
|
|
313
|
+
# A 2xx envelope can still carry an error status, with the same headers.
|
|
308
314
|
def unwrap(response)
|
|
309
315
|
Utils.handle_assinafy_response(response.body)
|
|
310
316
|
rescue ApiError => e
|
|
311
|
-
raise
|
|
317
|
+
raise with_response_headers(e, response)
|
|
312
318
|
end
|
|
313
319
|
|
|
314
|
-
def
|
|
315
|
-
|
|
320
|
+
def with_response_headers(error, response)
|
|
321
|
+
headers = response.headers || {}
|
|
322
|
+
challenge = headers['www-authenticate']
|
|
316
323
|
error.context[:www_authenticate] = challenge if challenge
|
|
324
|
+
rate_limit = RATE_LIMIT_HEADERS.filter_map do |key, header|
|
|
325
|
+
value = to_int(headers[header])
|
|
326
|
+
[key, value] if value
|
|
327
|
+
end.to_h
|
|
328
|
+
error.context[:rate_limit] = rate_limit unless rate_limit.empty?
|
|
329
|
+
retry_after = to_int(headers['retry-after'])
|
|
330
|
+
error.context[:retry_after] = retry_after if retry_after
|
|
317
331
|
error
|
|
318
332
|
end
|
|
319
333
|
|
|
@@ -131,7 +131,7 @@ module Assinafy
|
|
|
131
131
|
# Lightweight search over an account's documents (id/name/status/artifacts),
|
|
132
132
|
# without the heavier per-document detail returned by {#list}.
|
|
133
133
|
#
|
|
134
|
-
# @param query [String] free-text search term
|
|
134
|
+
# @param query [String, nil] optional free-text search term; nil omits `search`
|
|
135
135
|
# @param params [Hash] extra query parameters (`status`, `page`, `per_page`, ...)
|
|
136
136
|
# @param account_id_override [String, nil]
|
|
137
137
|
# @return [Hash{Symbol=>Array,Hash}] `{ data: [...], meta: {..} | nil }`
|
|
@@ -162,10 +162,10 @@ module Assinafy
|
|
|
162
162
|
# # ... (one Hash per matching document)
|
|
163
163
|
# ]
|
|
164
164
|
# }
|
|
165
|
-
def search(query, params = {}, account_id_override = nil)
|
|
165
|
+
def search(query = nil, params = {}, account_id_override = nil)
|
|
166
166
|
acc_id = account_id(account_id_override)
|
|
167
167
|
filters = require_payload(params, 'Document search parameters')
|
|
168
|
-
require_string(query, 'Search query')
|
|
168
|
+
require_string(query, 'Search query') unless query.nil?
|
|
169
169
|
|
|
170
170
|
call_list('Failed to search documents') do
|
|
171
171
|
http_get("accounts/#{acc_id}/documents/search", filters.merge(search: query))
|
|
@@ -253,7 +253,7 @@ module Assinafy
|
|
|
253
253
|
# Rename a document.
|
|
254
254
|
#
|
|
255
255
|
# @param document_id [String]
|
|
256
|
-
# @param name [String] the new display name
|
|
256
|
+
# @param name [String] the new display name (at most 255 characters)
|
|
257
257
|
# @return [Hash] the updated document object (envelope `data` unwrapped)
|
|
258
258
|
# @raise [Assinafy::ApiError] on an unsuccessful API response
|
|
259
259
|
# @raise [Assinafy::NetworkError] on transport or TLS failure
|
|
@@ -281,7 +281,7 @@ module Assinafy
|
|
|
281
281
|
# }
|
|
282
282
|
def rename(document_id, name)
|
|
283
283
|
doc_id = require_id(document_id, 'Document ID')
|
|
284
|
-
new_name = require_string(name, 'Name')
|
|
284
|
+
new_name = require_string(name, 'Name', max_length: 255)
|
|
285
285
|
|
|
286
286
|
call('Failed to rename document') do
|
|
287
287
|
http_patch("documents/#{doc_id}", body_params(name: new_name))
|
|
@@ -664,67 +664,41 @@ module Assinafy
|
|
|
664
664
|
end
|
|
665
665
|
end
|
|
666
666
|
|
|
667
|
-
#
|
|
668
|
-
#
|
|
669
|
-
# deployed API requires `{ recipient:, channel: }` when a recipient is supplied.
|
|
667
|
+
# Email a 6-digit access token for the document to its signer (public endpoint;
|
|
668
|
+
# no workspace credentials are sent).
|
|
670
669
|
#
|
|
671
670
|
# @param document_id [String]
|
|
672
|
-
# @param recipient [String
|
|
673
|
-
# @param channel [String
|
|
674
|
-
# @param email [String, nil]
|
|
675
|
-
# @return [
|
|
676
|
-
# API returns `{ 'document' => {..}, 'channel' => String, 'recipient' => String }`
|
|
671
|
+
# @param recipient [String] the signer's email address (required)
|
|
672
|
+
# @param channel [String] delivery channel; only `"email"` is accepted
|
|
673
|
+
# @param email [String, nil] earlier name for `recipient:`; sent as `recipient`
|
|
674
|
+
# @return [Hash] `{ 'document' => Hash, 'channel' => String, 'recipient' => String }`
|
|
677
675
|
# @raise [Assinafy::ApiError] on an unsuccessful API response
|
|
678
676
|
# @raise [Assinafy::NetworkError] on transport or TLS failure
|
|
679
|
-
# @raise [Assinafy::ValidationError]
|
|
677
|
+
# @raise [Assinafy::ValidationError] when the recipient is missing or not an email address,
|
|
678
|
+
# when the channel is not `email`, or when both `recipient:` and `email:` are given
|
|
680
679
|
# @see PUT /public/documents/{document_id}/send-token
|
|
681
|
-
# @example
|
|
682
|
-
# client.documents.send_token('document-id')
|
|
680
|
+
# @example Email the access token to the signer (no auth required)
|
|
681
|
+
# client.documents.send_token('document-id', recipient: 'signer@example.com')
|
|
683
682
|
#
|
|
684
|
-
# @example Email a signer using the current OpenAPI request (no auth required)
|
|
685
683
|
# # Request: PUT /public/documents/{document_id}/send-token
|
|
686
|
-
# # Body: { "email": "signer@example.com" }
|
|
687
|
-
# client.documents.send_token('document-id', email: 'signer@example.com')
|
|
688
|
-
#
|
|
689
|
-
# @example Use the deployed-API request shape
|
|
690
|
-
# # Body: { "recipient": "signer@example.com", "channel": "email" }
|
|
691
|
-
# client.documents.send_token('document-id', recipient: 'signer@example.com', channel: 'email')
|
|
684
|
+
# # Body: { "channel": "email", "recipient": "signer@example.com" }
|
|
692
685
|
#
|
|
693
|
-
# #
|
|
694
|
-
# {
|
|
695
|
-
#
|
|
696
|
-
#
|
|
697
|
-
#
|
|
698
|
-
#
|
|
699
|
-
|
|
700
|
-
# 'created_by' => 'John Smith'
|
|
701
|
-
# },
|
|
702
|
-
# 'channel' => 'email',
|
|
703
|
-
# 'recipient' => 'signer@example.com'
|
|
704
|
-
# }
|
|
705
|
-
# # => nil when the API returns the documented no-data envelope
|
|
706
|
-
def send_token(document_id, recipient: nil, channel: nil, email: nil)
|
|
686
|
+
# # => {
|
|
687
|
+
# # 'document' => { 'resource' => 'document', 'id' => 'doc1', 'name' => '1.pdf',
|
|
688
|
+
# # 'page_count' => 1, 'created_by' => 'John Smith' },
|
|
689
|
+
# # 'channel' => 'email',
|
|
690
|
+
# # 'recipient' => 'signer@example.com'
|
|
691
|
+
# # }
|
|
692
|
+
def send_token(document_id, recipient: nil, channel: 'email', email: nil)
|
|
707
693
|
doc_id = require_id(document_id, 'Document ID')
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
elsif !email.nil?
|
|
711
|
-
if recipient || channel
|
|
712
|
-
raise ValidationError.new('Use either email or recipient/channel, not both')
|
|
713
|
-
end
|
|
694
|
+
raise ValidationError.new('Use either recipient or email, not both') if recipient && email
|
|
695
|
+
raise ValidationError.new('Channel must be email') unless channel == 'email'
|
|
714
696
|
|
|
715
|
-
|
|
716
|
-
else
|
|
717
|
-
require_string(recipient, 'Recipient')
|
|
718
|
-
delivery_channel = require_present(channel, 'Channel').to_s
|
|
719
|
-
unless %w[email whatsapp].include?(delivery_channel)
|
|
720
|
-
raise ValidationError.new('Channel must be email or whatsapp')
|
|
721
|
-
end
|
|
722
|
-
|
|
723
|
-
payload = { recipient: recipient, channel: delivery_channel }
|
|
724
|
-
end
|
|
697
|
+
address = Utils.require_email(recipient || email)
|
|
725
698
|
|
|
726
699
|
call('Failed to send signer token') do
|
|
727
|
-
http_put("public/documents/#{doc_id}/send-token",
|
|
700
|
+
http_put("public/documents/#{doc_id}/send-token", { 'channel' => 'email', 'recipient' => address },
|
|
701
|
+
workspace_auth: false)
|
|
728
702
|
end
|
|
729
703
|
end
|
|
730
704
|
|
|
@@ -7,12 +7,17 @@ module Assinafy
|
|
|
7
7
|
# See https://api.assinafy.com.br/v1/docs#field-definition for the
|
|
8
8
|
# documentation of these endpoints.
|
|
9
9
|
class FieldResource < BaseResource
|
|
10
|
+
# Default for `signer_access_code:`, telling an omitted keyword (workspace credentials)
|
|
11
|
+
# apart from an explicit nil (rejected).
|
|
12
|
+
WORKSPACE = Object.new.freeze
|
|
13
|
+
private_constant :WORKSPACE
|
|
14
|
+
|
|
10
15
|
# Create a field definition.
|
|
11
16
|
#
|
|
12
17
|
# @param payload [Hash]
|
|
13
18
|
# @option payload [String] :type required — e.g. `text`, `cpf`, `email` — see {#types}
|
|
14
19
|
# @option payload [String] :name required — display label
|
|
15
|
-
# @option payload [String]
|
|
20
|
+
# @option payload [String, nil] :regex optional validation regex (text fields); nil is sent as null
|
|
16
21
|
# @option payload [Boolean] :is_required default `true`
|
|
17
22
|
# @param account_id_override [String, nil]
|
|
18
23
|
# @return [Hash] the created field definition (envelope `data` unwrapped)
|
|
@@ -41,7 +46,8 @@ module Assinafy
|
|
|
41
46
|
# # }
|
|
42
47
|
def create(payload, account_id_override = nil)
|
|
43
48
|
acc_id = account_id(account_id_override)
|
|
44
|
-
|
|
49
|
+
raw = require_payload(payload)
|
|
50
|
+
body = with_explicit_regex(raw, body_params(raw))
|
|
45
51
|
require_string(body['name'], 'Field name')
|
|
46
52
|
require_string(body['type'], 'Field type')
|
|
47
53
|
require_boolean(body['is_required'], 'is_required') if body.key?('is_required')
|
|
@@ -163,9 +169,7 @@ module Assinafy
|
|
|
163
169
|
acc_id = account_id(account_id_override)
|
|
164
170
|
fid = require_id(field_id, 'Field ID')
|
|
165
171
|
raw = require_payload(payload)
|
|
166
|
-
body = body_params(raw)
|
|
167
|
-
body['regex'] = nil if (raw.key?(:regex) && raw[:regex].nil?) ||
|
|
168
|
-
(raw.key?('regex') && raw['regex'].nil?)
|
|
172
|
+
body = with_explicit_regex(raw, body_params(raw))
|
|
169
173
|
require_string(body['name'], 'Field name') if body.key?('name')
|
|
170
174
|
require_boolean(body['is_active'], 'is_active') if body.key?('is_active')
|
|
171
175
|
|
|
@@ -197,14 +201,15 @@ module Assinafy
|
|
|
197
201
|
|
|
198
202
|
# Validate a single value against a field definition.
|
|
199
203
|
#
|
|
200
|
-
# The OpenAPI declares workspace Authorization
|
|
201
|
-
#
|
|
202
|
-
#
|
|
204
|
+
# The OpenAPI declares workspace Authorization, which is used when
|
|
205
|
+
# `signer_access_code:` is omitted. The deployed API also accepts the signer
|
|
206
|
+
# access code: when the keyword is given it must be a non-blank code, and
|
|
207
|
+
# only the code is sent (never the workspace credentials).
|
|
203
208
|
#
|
|
204
209
|
# @param field_id [String]
|
|
205
210
|
# @param value [Object]
|
|
206
211
|
# @param account_id_override [String, nil]
|
|
207
|
-
# @param signer_access_code [String
|
|
212
|
+
# @param signer_access_code [String] optional keyword; nil or blank raises
|
|
208
213
|
# @return [Hash{String=>Object}] `{ "type" =>, "success" =>, "error_message" => }`
|
|
209
214
|
# @raise [Assinafy::ApiError] on an unsuccessful API response
|
|
210
215
|
# @raise [Assinafy::NetworkError] on transport or TLS failure
|
|
@@ -221,12 +226,12 @@ module Assinafy
|
|
|
221
226
|
# @example Validate as a signer (signer-access-code auth, sent as a query param)
|
|
222
227
|
# client.fields.validate('field-id', 'Some text', signer_access_code: 'signer-access-code')
|
|
223
228
|
# # => { "type" => "text", "success" => true, "error_message" => "" }
|
|
224
|
-
def validate(field_id, value, account_id_override = nil, signer_access_code:
|
|
229
|
+
def validate(field_id, value, account_id_override = nil, signer_access_code: WORKSPACE)
|
|
225
230
|
acc_id = account_id(account_id_override)
|
|
226
231
|
fid = require_id(field_id, 'Field ID')
|
|
227
232
|
raise ValidationError.new('Field value is required') if value.nil?
|
|
228
233
|
|
|
229
|
-
access_code =
|
|
234
|
+
access_code = field_access_code(signer_access_code)
|
|
230
235
|
|
|
231
236
|
call('Failed to validate field value') do
|
|
232
237
|
http_post("accounts/#{acc_id}/fields/#{fid}/validate", body_params(value: value),
|
|
@@ -236,12 +241,12 @@ module Assinafy
|
|
|
236
241
|
|
|
237
242
|
# Validate many `{ field_id:, value: }` pairs in a single call.
|
|
238
243
|
#
|
|
239
|
-
# As with {#validate},
|
|
240
|
-
#
|
|
244
|
+
# As with {#validate}, omitting `signer_access_code:` uses the workspace
|
|
245
|
+
# credentials; giving it requires a non-blank code and sends only the code.
|
|
241
246
|
#
|
|
242
247
|
# @param values [Array<Hash>]
|
|
243
248
|
# @param account_id_override [String, nil]
|
|
244
|
-
# @param signer_access_code [String
|
|
249
|
+
# @param signer_access_code [String] optional keyword; nil or blank raises
|
|
245
250
|
# @return [Array<Hash>] one validation Hash per input, each carrying its `field_id`
|
|
246
251
|
# @raise [Assinafy::ApiError] on an unsuccessful API response
|
|
247
252
|
# @raise [Assinafy::NetworkError] on transport or TLS failure
|
|
@@ -265,10 +270,10 @@ module Assinafy
|
|
|
265
270
|
# # { "field_id" => "field-id-2", "type" => "email",
|
|
266
271
|
# # "success" => true, "error_message" => "" }
|
|
267
272
|
# # ]
|
|
268
|
-
def validate_multiple(values, account_id_override = nil, signer_access_code:
|
|
273
|
+
def validate_multiple(values, account_id_override = nil, signer_access_code: WORKSPACE)
|
|
269
274
|
acc_id = account_id(account_id_override)
|
|
270
275
|
list = require_array(values, 'Field values')
|
|
271
|
-
access_code =
|
|
276
|
+
access_code = field_access_code(signer_access_code)
|
|
272
277
|
|
|
273
278
|
call_array('Failed to validate field values') do
|
|
274
279
|
http_post("accounts/#{acc_id}/fields/validate-multiple",
|
|
@@ -308,6 +313,16 @@ module Assinafy
|
|
|
308
313
|
|
|
309
314
|
private
|
|
310
315
|
|
|
316
|
+
def field_access_code(signer_access_code)
|
|
317
|
+
signer_access_code.equal?(WORKSPACE) ? nil : require_signer_access_code(signer_access_code)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# `regex` is nullable: an explicit nil is sent as JSON null to clear it.
|
|
321
|
+
def with_explicit_regex(raw, body)
|
|
322
|
+
body['regex'] = nil if [:regex, 'regex'].any? { |key| raw.key?(key) && raw[key].nil? }
|
|
323
|
+
body
|
|
324
|
+
end
|
|
325
|
+
|
|
311
326
|
def field_value_payload(item)
|
|
312
327
|
payload = require_payload(item, 'Field value')
|
|
313
328
|
field_id = payload.key?(:field_id) ? payload[:field_id] : payload['field_id']
|
|
@@ -8,8 +8,9 @@ module Assinafy
|
|
|
8
8
|
#
|
|
9
9
|
# - {#current}/{#document}, {#sign_multiple}, {#decline_multiple} require the
|
|
10
10
|
# `signer-access-code` URL parameter.
|
|
11
|
-
# - {#list}
|
|
12
|
-
# code
|
|
11
|
+
# - {#list} and {#search} authenticate as the signer: they require the access
|
|
12
|
+
# code and never send workspace credentials. The same routes also accept
|
|
13
|
+
# workspace credentials; use {#list_as_workspace} / {#search_as_workspace} for that.
|
|
13
14
|
# - {#download} only needs the document/artifact IDs.
|
|
14
15
|
#
|
|
15
16
|
# See https://api.assinafy.com.br/v1/docs#signer for the full
|
|
@@ -61,18 +62,16 @@ module Assinafy
|
|
|
61
62
|
|
|
62
63
|
alias document current
|
|
63
64
|
|
|
64
|
-
# List all documents the signer has access to, with pagination metadata
|
|
65
|
-
#
|
|
66
|
-
# signer access code, so `signer_access_code:` is optional here; when nil
|
|
67
|
-
# it is omitted from the query and the header auth is used.
|
|
65
|
+
# List all documents the signer has access to, with pagination metadata,
|
|
66
|
+
# authenticated with the signer access code only (no workspace credentials).
|
|
68
67
|
#
|
|
69
68
|
# @param signer_id [String]
|
|
70
69
|
# @param params [Hash] documented `page` and `per_page` query parameters
|
|
71
|
-
# @param signer_access_code [String
|
|
70
|
+
# @param signer_access_code [String] required; may also be given as `params[:signer_access_code]`
|
|
72
71
|
# @return [Hash{Symbol=>Array,Hash}] `{ data: [...], meta: { current_page:, per_page:, total:, last_page: } }`
|
|
73
72
|
# @raise [Assinafy::ApiError] on an unsuccessful API response
|
|
74
73
|
# @raise [Assinafy::NetworkError] on transport or TLS failure
|
|
75
|
-
# @raise [Assinafy::ValidationError] on invalid required input
|
|
74
|
+
# @raise [Assinafy::ValidationError] on invalid required input, including a nil or blank access code
|
|
76
75
|
# @see GET /signers/{signer_id}/documents
|
|
77
76
|
# @example List the signer's documents with pagination
|
|
78
77
|
# page = client.signer_documents.list('signer-id',
|
|
@@ -103,27 +102,49 @@ module Assinafy
|
|
|
103
102
|
|
|
104
103
|
call_list('Failed to list signer documents') do
|
|
105
104
|
http_get("signers/#{sid}/documents", query.merge(signer_access_code: access_code),
|
|
106
|
-
workspace_auth:
|
|
105
|
+
workspace_auth: false)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# List a signer's documents with the workspace credentials (API key or Bearer token)
|
|
110
|
+
# instead of the signer access code. Same response as {#list}.
|
|
111
|
+
#
|
|
112
|
+
# @param signer_id [String]
|
|
113
|
+
# @param params [Hash] documented `page` and `per_page` query parameters
|
|
114
|
+
# @return [Hash{Symbol=>Array,Hash}] `{ data: [...], meta: { current_page:, per_page:, total:, last_page: } }`
|
|
115
|
+
# @raise [Assinafy::ApiError] on an unsuccessful API response
|
|
116
|
+
# @raise [Assinafy::NetworkError] on transport or TLS failure
|
|
117
|
+
# @raise [Assinafy::ValidationError] on invalid input, or when `params` carries a signer access code
|
|
118
|
+
# @see GET /signers/{signer_id}/documents
|
|
119
|
+
# @example
|
|
120
|
+
# client.signer_documents.list_as_workspace('signer-id', { page: 1, per_page: 15 })
|
|
121
|
+
# # Request: GET /signers/{signer_id}/documents?page=1&per-page=15 (X-Api-Key header)
|
|
122
|
+
# # => { data: [{ "id" => "document-id", "name" => "contract.pdf", ... }],
|
|
123
|
+
# # meta: { current_page: 1, per_page: 15, total: 1, last_page: 1 } }
|
|
124
|
+
def list_as_workspace(signer_id, params = {})
|
|
125
|
+
sid = require_id(signer_id, 'Signer ID')
|
|
126
|
+
|
|
127
|
+
call_list('Failed to list signer documents') do
|
|
128
|
+
http_get("signers/#{sid}/documents", workspace_query(params))
|
|
107
129
|
end
|
|
108
130
|
end
|
|
109
131
|
|
|
110
|
-
# Lightweight search over the signer's documents
|
|
111
|
-
#
|
|
112
|
-
# code, so `signer_access_code:` is optional.
|
|
132
|
+
# Lightweight search over the signer's documents, authenticated with the
|
|
133
|
+
# signer access code only (no workspace credentials).
|
|
113
134
|
#
|
|
114
135
|
# @param signer_id [String]
|
|
115
|
-
# @param query [String] free-text search term
|
|
136
|
+
# @param query [String, nil] optional free-text search term; nil omits `search`
|
|
116
137
|
# @param params [Hash] optional deployment-specific query parameters
|
|
117
|
-
# @param signer_access_code [String
|
|
138
|
+
# @param signer_access_code [String] required; may also be given as `params[:signer_access_code]`
|
|
118
139
|
# @return [Hash{Symbol=>Array,Hash}] `{ data: [...], meta: {..} | nil }`
|
|
119
140
|
# @raise [Assinafy::ApiError] on an unsuccessful API response
|
|
120
141
|
# @raise [Assinafy::NetworkError] on transport or TLS failure
|
|
121
|
-
# @raise [Assinafy::ValidationError] on invalid required input
|
|
142
|
+
# @raise [Assinafy::ValidationError] on invalid required input, including a nil or blank access code
|
|
122
143
|
# @see GET /signers/{signer_id}/documents/search
|
|
123
144
|
# @example Search the signer's documents
|
|
124
|
-
# page = client.signer_documents.search('signer-id', 'contract')
|
|
145
|
+
# page = client.signer_documents.search('signer-id', 'contract', signer_access_code: 'signer-access-code')
|
|
125
146
|
#
|
|
126
|
-
# # Request: GET /signers/{signer_id}/documents/search?search=contract
|
|
147
|
+
# # Request: GET /signers/{signer_id}/documents/search?search=contract&signer-access-code=...
|
|
127
148
|
# # => {
|
|
128
149
|
# # data: [
|
|
129
150
|
# # { "id" => "document-id", "account_id" => "account-id", "name" => "contract.pdf",
|
|
@@ -134,15 +155,39 @@ module Assinafy
|
|
|
134
155
|
# # }
|
|
135
156
|
# # ]
|
|
136
157
|
# # }
|
|
137
|
-
def search(signer_id, query, params = {}, signer_access_code: nil)
|
|
158
|
+
def search(signer_id, query = nil, params = {}, signer_access_code: nil)
|
|
138
159
|
sid = require_id(signer_id, 'Signer ID')
|
|
139
160
|
filters, access_code = signer_query(params, signer_access_code)
|
|
140
|
-
require_string(query, 'Search query')
|
|
161
|
+
require_string(query, 'Search query') unless query.nil?
|
|
141
162
|
|
|
142
163
|
call_list('Failed to search signer documents') do
|
|
143
164
|
http_get("signers/#{sid}/documents/search",
|
|
144
165
|
filters.merge(search: query, signer_access_code: access_code),
|
|
145
|
-
workspace_auth:
|
|
166
|
+
workspace_auth: false)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Search a signer's documents with the workspace credentials instead of the
|
|
171
|
+
# signer access code. Same response as {#search}.
|
|
172
|
+
#
|
|
173
|
+
# @param signer_id [String]
|
|
174
|
+
# @param query [String, nil] optional free-text search term; nil omits `search`
|
|
175
|
+
# @param params [Hash] optional deployment-specific query parameters
|
|
176
|
+
# @return [Hash{Symbol=>Array,Hash}] `{ data: [...], meta: {..} | nil }`
|
|
177
|
+
# @raise [Assinafy::ApiError] on an unsuccessful API response
|
|
178
|
+
# @raise [Assinafy::NetworkError] on transport or TLS failure
|
|
179
|
+
# @raise [Assinafy::ValidationError] on invalid input, or when `params` carries a signer access code
|
|
180
|
+
# @see GET /signers/{signer_id}/documents/search
|
|
181
|
+
# @example
|
|
182
|
+
# client.signer_documents.search_as_workspace('signer-id', 'contract')
|
|
183
|
+
# # Request: GET /signers/{signer_id}/documents/search?search=contract (X-Api-Key header)
|
|
184
|
+
# # => { data: [{ "id" => "document-id", "name" => "contract.pdf", ... }] }
|
|
185
|
+
def search_as_workspace(signer_id, query = nil, params = {})
|
|
186
|
+
sid = require_id(signer_id, 'Signer ID')
|
|
187
|
+
require_string(query, 'Search query') unless query.nil?
|
|
188
|
+
|
|
189
|
+
call_list('Failed to search signer documents') do
|
|
190
|
+
http_get("signers/#{sid}/documents/search", workspace_query(params).merge(search: query))
|
|
146
191
|
end
|
|
147
192
|
end
|
|
148
193
|
|
|
@@ -256,7 +301,16 @@ module Assinafy
|
|
|
256
301
|
].compact
|
|
257
302
|
raw_code = explicit_code.nil? ? embedded_codes.first : explicit_code
|
|
258
303
|
|
|
259
|
-
[query,
|
|
304
|
+
[query, require_signer_access_code(raw_code)]
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def workspace_query(params)
|
|
308
|
+
query = require_payload(params, 'Signer document query parameters')
|
|
309
|
+
if query.keys.any? { |key| %w[signer_access_code signer-access-code].include?(key.to_s) }
|
|
310
|
+
raise ValidationError.new('Workspace requests must not carry a signer access code')
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
query
|
|
260
314
|
end
|
|
261
315
|
end
|
|
262
316
|
end
|
data/lib/assinafy/version.rb
CHANGED
data/sig/assinafy.rbs
CHANGED
|
@@ -50,6 +50,8 @@ module Assinafy
|
|
|
50
50
|
|
|
51
51
|
def initialize: (String message, Integer status_code, ?untyped response_data) -> void
|
|
52
52
|
def self.from_response: (Integer status_code, untyped response_data) -> ApiError
|
|
53
|
+
def rate_limit: () -> Hash[Symbol, Integer]?
|
|
54
|
+
def retry_after: () -> Integer?
|
|
53
55
|
end
|
|
54
56
|
|
|
55
57
|
class ValidationError < Error
|
|
@@ -150,6 +152,7 @@ module Assinafy
|
|
|
150
152
|
STATS_GRANULARITIES: Array[String]
|
|
151
153
|
AUTH_HEADERS: Array[String]
|
|
152
154
|
PAGINATION_HEADERS: Hash[Symbol, String]
|
|
155
|
+
RATE_LIMIT_HEADERS: Hash[Symbol, String]
|
|
153
156
|
|
|
154
157
|
def initialize: (untyped connection, ?String? default_account_id, ?_Logger? logger) -> void
|
|
155
158
|
end
|
|
@@ -238,7 +241,7 @@ module Assinafy
|
|
|
238
241
|
|
|
239
242
|
def upload: (upload_source source, ?params options) -> api_object
|
|
240
243
|
def list: (?params params, ?String? account_id_override) -> list_result
|
|
241
|
-
def search: (String query, ?params params, ?String? account_id_override) -> list_result
|
|
244
|
+
def search: (?String? query, ?params params, ?String? account_id_override) -> list_result
|
|
242
245
|
def statuses: () -> Array[api_object]
|
|
243
246
|
def details: (String document_id) -> api_object
|
|
244
247
|
def get: (String document_id) -> api_object
|
|
@@ -269,9 +272,9 @@ module Assinafy
|
|
|
269
272
|
def send_token: (
|
|
270
273
|
String document_id,
|
|
271
274
|
?recipient: String?,
|
|
272
|
-
?channel: String
|
|
275
|
+
?channel: String,
|
|
273
276
|
?email: String?
|
|
274
|
-
) -> api_object
|
|
277
|
+
) -> api_object
|
|
275
278
|
def list_tags: (String document_id, ?String? account_id_override) -> Array[api_object]
|
|
276
279
|
def replace_tags: (String document_id, Array[String] tags, ?String? account_id_override) -> Array[api_object]
|
|
277
280
|
def append_tags: (String document_id, Array[String] tags, ?String? account_id_override) -> Array[api_object]
|
|
@@ -309,7 +312,9 @@ module Assinafy
|
|
|
309
312
|
def current: (String signer_id, signer_access_code: String) -> api_object
|
|
310
313
|
def document: (String signer_id, signer_access_code: String) -> api_object
|
|
311
314
|
def list: (String signer_id, ?params params, ?signer_access_code: String?) -> list_result
|
|
312
|
-
def
|
|
315
|
+
def list_as_workspace: (String signer_id, ?params params) -> list_result
|
|
316
|
+
def search: (String signer_id, ?String? query, ?params params, ?signer_access_code: String?) -> list_result
|
|
317
|
+
def search_as_workspace: (String signer_id, ?String? query, ?params params) -> list_result
|
|
313
318
|
def sign_multiple: (Array[String] document_ids, signer_access_code: String) -> Array[api_object]
|
|
314
319
|
def decline_multiple: (
|
|
315
320
|
Array[String] document_ids,
|
|
@@ -385,12 +390,12 @@ module Assinafy
|
|
|
385
390
|
String field_id,
|
|
386
391
|
untyped value,
|
|
387
392
|
?String? account_id_override,
|
|
388
|
-
?signer_access_code: String
|
|
393
|
+
?signer_access_code: String
|
|
389
394
|
) -> api_object
|
|
390
395
|
def validate_multiple: (
|
|
391
396
|
Array[params] values,
|
|
392
397
|
?String? account_id_override,
|
|
393
|
-
?signer_access_code: String
|
|
398
|
+
?signer_access_code: String
|
|
394
399
|
) -> Array[api_object]
|
|
395
400
|
def types: () -> Array[api_object]
|
|
396
401
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: assinafy
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.11.
|
|
4
|
+
version: 1.11.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Assinafy SDK Contributors
|
|
@@ -170,7 +170,7 @@ files:
|
|
|
170
170
|
- lib/assinafy/utils.rb
|
|
171
171
|
- lib/assinafy/version.rb
|
|
172
172
|
- sig/assinafy.rbs
|
|
173
|
-
homepage: https://
|
|
173
|
+
homepage: https://www.assinafy.com.br
|
|
174
174
|
licenses:
|
|
175
175
|
- MIT
|
|
176
176
|
metadata:
|