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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 97dcce395cbee3bd4a632650550becfd07d6fa291da434678d1a31de4488d103
4
- data.tar.gz: 4cde25cfc6221e23378892c4fa19d7779046b65642bc33dc734756f712c2aab8
3
+ metadata.gz: 4c9d44ba9996ee18372b0c965c691f9a74695b98ffd85ebe5e43ae29a4f3b662
4
+ data.tar.gz: a0313ec17a476c5439eab8fd004936c337b4b99e14323b8dfd864553b68f22d9
5
5
  SHA512:
6
- metadata.gz: 437fad120c4ec641767a2241f0e03589e14ea31b3407b603c85e7284476526711cd8d8f8732573d2ab4348aa9387cbfa077e43061ff91e1728eeda766b6c51f2
7
- data.tar.gz: 9c2e1b81591e038d449145fa3f97f7b2b90f98fbfede435f01cd14fbfbb6b9d62805df762e39a01c0e9de7a8d9e729371d3b88e63b5049fb7ac29b0c10d01b88
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', email: 'alice@example.com')
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
 
@@ -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-07 from
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`: string (date-time) } | `200` `application/json` [`Envelope`](#envelope) plus object { `data`: [`Assignment`](#assignment) } |
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 | optional; `application/json` object { `email`: string (email) } | `200` `application/json` [`Envelope`](#envelope) |
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) |
@@ -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 Both methods require top-level `signers`; `collect` additionally
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
- result[:signers] = signers.map { |ref| normalise_signer_ref(ref, options) }
122
- validate_signing_steps!(result[:signers])
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` is required for both methods: creation needs to know who signs, and the
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 with_challenge(error_class.from_response(response.status, response.body), response)
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 challenge.
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 with_challenge(e, response)
317
+ raise with_response_headers(e, response)
312
318
  end
313
319
 
314
- def with_challenge(error, response)
315
- challenge = response.headers&.[]('www-authenticate')
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
- # Send a 6-digit access token for the document to a signer (public endpoint).
668
- # The current OpenAPI permits no body or an `{ email: }` body, while the
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, nil] deployed-API email address or WhatsApp phone number
673
- # @param channel [String, nil] deployed-API `email` or `whatsapp` channel
674
- # @param email [String, nil] email address for the current OpenAPI request shape
675
- # @return [nil, Hash] `nil` for the OpenAPI's no-data envelope; the deployed
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] on invalid required input
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 Ask the API to use the document's signer contact (no auth required)
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
- # # Deployed-API response (unwrapped data payload):
694
- # {
695
- # 'document' => {
696
- # 'resource' => 'document',
697
- # 'id' => 'doc1',
698
- # 'name' => '1.pdf',
699
- # 'page_count' => '1',
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
- if email.nil? && recipient.nil? && channel.nil?
709
- payload = nil
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
- payload = { email: Utils.require_email(email) }
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", payload && body_params(payload), workspace_auth: false)
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] :regex optional validation regex (text fields)
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
- body = body_params(require_payload(payload))
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. The deployed API also
201
- # accepts signer-access-code authentication; pass `signer_access_code:`
202
- # to use that compatibility path without sending workspace credentials.
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, nil]
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: nil)
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 = signer_access_code.nil? ? nil : require_signer_access_code(signer_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}, signer-access-code support is a deployed-API
240
- # compatibility extension beyond the current OpenAPI security declaration.
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, nil]
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: nil)
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 = signer_access_code.nil? ? nil : require_signer_access_code(signer_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} accepts EITHER the workspace `X-Api-Key` header OR the access
12
- # code; pass `signer_access_code:` only when authenticating as the signer.
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
- # This endpoint accepts either the workspace `X-Api-Key` header or the
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, nil]
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: access_code.nil?)
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. Like {#list}, this
111
- # accepts either the workspace `X-Api-Key` header or the signer access
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, nil]
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: access_code.nil?)
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, raw_code.nil? ? nil : require_signer_access_code(raw_code)]
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
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Assinafy
4
- VERSION = '1.11.0'
4
+ VERSION = '1.11.1'
5
5
  USER_AGENT = "Assinafy-Ruby-SDK/v#{VERSION}".freeze
6
6
  end
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 search: (String signer_id, String query, ?params params, ?signer_access_code: String?) -> list_result
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.0
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://github.com/assinafy/ruby-sdk
173
+ homepage: https://www.assinafy.com.br
174
174
  licenses:
175
175
  - MIT
176
176
  metadata: