assinafy 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +168 -0
- data/LICENSE +19 -0
- data/README.md +463 -0
- data/lib/assinafy/client.rb +245 -0
- data/lib/assinafy/configuration.rb +157 -0
- data/lib/assinafy/errors.rb +83 -0
- data/lib/assinafy/null_logger.rb +9 -0
- data/lib/assinafy/resources/account_resource.rb +267 -0
- data/lib/assinafy/resources/assignment_resource.rb +476 -0
- data/lib/assinafy/resources/auth_resource.rb +251 -0
- data/lib/assinafy/resources/base_resource.rb +250 -0
- data/lib/assinafy/resources/document_resource.rb +912 -0
- data/lib/assinafy/resources/field_resource.rb +274 -0
- data/lib/assinafy/resources/signer_document_resource.rb +222 -0
- data/lib/assinafy/resources/signer_resource.rb +422 -0
- data/lib/assinafy/resources/tag_resource.rb +183 -0
- data/lib/assinafy/resources/template_resource.rb +215 -0
- data/lib/assinafy/resources/user_resource.rb +182 -0
- data/lib/assinafy/resources/webhook_resource.rb +222 -0
- data/lib/assinafy/support/webhook_verifier.rb +129 -0
- data/lib/assinafy/utils.rb +122 -0
- data/lib/assinafy/version.rb +5 -0
- data/lib/assinafy.rb +25 -0
- metadata +195 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f8b8c2701ec7f853e14b297d675829d16fef94b35f87d46cf372ef24d2d55482
|
|
4
|
+
data.tar.gz: d6cd90c3ad7304648f397544a118b9cd57dfd80db2a6a84ddea116878b515da8
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 823b760bc2d019f2e6f77de43b6e199067b1030e5e1f2a75fde65ad87fa7cec51ae8dba59f357e388a27db945a814cc6ff0dd2be4be92e4d9c41a6b5cf5c272c
|
|
7
|
+
data.tar.gz: 5007b5ec7928af7dd1cc736cba63f9fc8efb72aa1478aedfad08bcaa28af2eec0c248e7903732b6e4b4c76fd73cd17f47a64535a266e33370e8d56b2b424e615
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the `assinafy` Ruby gem are documented here.
|
|
4
|
+
|
|
5
|
+
## 1.5.0
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`Resources::AccountResource`** (`client.accounts`): `list`, `create`, `get`, `update`,
|
|
10
|
+
`delete` (with `force:`), `theme`, `stats`, and brand-logo `upload_logo`/`download_logo`/`delete_logo`.
|
|
11
|
+
- **`Resources::UserResource`** (`client.users`): `me`, `stats`, and notification-preference
|
|
12
|
+
`GET`/`PUT` wrappers.
|
|
13
|
+
- `DocumentResource#search` (`GET /accounts/{account_id}/documents/search`) and
|
|
14
|
+
`#rename` (`PATCH /documents/{document_id}`).
|
|
15
|
+
- `AssignmentResource#list` (`GET /assignments`; the account context is sent as the
|
|
16
|
+
camelCase `accountId` query parameter, verified live).
|
|
17
|
+
- `SignerDocumentResource#search` (`GET /signers/{signer_id}/documents/search`).
|
|
18
|
+
- `AuthResource#link_social_login` (`POST /auth/link-social-login`).
|
|
19
|
+
- `SignerResource#upload_signature` accepts an optional `reuse:` flag.
|
|
20
|
+
- An opt-in live integration suite (`spec/integration/`, gated by `ASSINAFY_LIVE=1`)
|
|
21
|
+
that drives representative workflows against the sandbox and self-cleans.
|
|
22
|
+
- A `.github/dependabot.yml` (bundler + github-actions) so dependency advisories are
|
|
23
|
+
caught automatically.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- `SignerResource#accept_terms` and `#verify_email` now send `signer-access-code` as
|
|
28
|
+
the documented query parameter (the `signerAccessCode` security scheme is `in: query`),
|
|
29
|
+
consistent with every other signer-authenticated endpoint. Previously it was sent
|
|
30
|
+
only in the request body.
|
|
31
|
+
- `TemplateResource#create` now performs the required `multipart/form-data` file
|
|
32
|
+
upload. The previous JSON body was rejected by the API (HTTP 400) and never worked.
|
|
33
|
+
Its signature is now `create(source, options = {}, account_id_override = nil)`.
|
|
34
|
+
- `SignerDocumentResource#download` makes `signer_access_code:` optional — the endpoint
|
|
35
|
+
is public (`security: []`), so only the document/artifact IDs are required.
|
|
36
|
+
- Document search wrappers send the documented `search` query key; downloads accept the
|
|
37
|
+
documented `pades` artifact; signer updates preserve `government_id`.
|
|
38
|
+
- `faraday` dependency floor raised to `>= 2.14.3` (CVE-2026-54297, DoS via deeply
|
|
39
|
+
nested query params).
|
|
40
|
+
|
|
41
|
+
### Removed
|
|
42
|
+
|
|
43
|
+
- `WebhookResource#delete`. There is no `DELETE /webhooks/subscriptions` route — it
|
|
44
|
+
returns HTTP 404 (verified live). Use `#inactivate` to stop deliveries.
|
|
45
|
+
|
|
46
|
+
### Documentation
|
|
47
|
+
|
|
48
|
+
- Corrected YARD payloads verified against live responses: `confirm_data` (body is
|
|
49
|
+
`full_name`/`email`/`government_id`; returns the signer object), `upload_signature`
|
|
50
|
+
(handles documented no-data responses), `FieldResource#update` (accepts only `name`/`regex`/`is_active`),
|
|
51
|
+
webhook dispatch shape (ISO-8601 `created_at` + `updated_at`), and template `create`.
|
|
52
|
+
- README updated for the new resources and behaviors, plus live-test instructions.
|
|
53
|
+
|
|
54
|
+
## 1.4.0
|
|
55
|
+
|
|
56
|
+
This release is the result of a full audit of the SDK against the live Assinafy
|
|
57
|
+
v1 API (verified end-to-end against the sandbox) and the published documentation
|
|
58
|
+
at <https://api.assinafy.com.br/v1/docs>.
|
|
59
|
+
|
|
60
|
+
### Added
|
|
61
|
+
|
|
62
|
+
- `Resources::TemplateResource#delete` (`DELETE /accounts/{account_id}/templates/{template_id}`)
|
|
63
|
+
and `#download_page` (`GET /accounts/{account_id}/templates/{template_id}/pages/{page_id}/download`)
|
|
64
|
+
— both verified against the live API.
|
|
65
|
+
- `Support::WebhookVerifier#event_payload`, `#event_object`, and `#event_subject`,
|
|
66
|
+
matching the real delivery envelope (top-level `payload`/`object`/`subject` keys).
|
|
67
|
+
- `ApiError#error_name` and `ApiError#error_code` expose the API's `name`/`code`
|
|
68
|
+
error fields; `ApiError.from_response` now also falls back to `name` for the message.
|
|
69
|
+
- YARD `@example` blocks with full request **and** response payloads on every public
|
|
70
|
+
method, sourced from the live API and the docs.
|
|
71
|
+
- Behavioral coverage: `spec/api_coverage_spec.rb` now also fails CI when a public
|
|
72
|
+
endpoint wrapper is missing a matrix row, and asserts documented aliases still
|
|
73
|
+
resolve to their canonical methods. Behavioral WebMock specs were added for the
|
|
74
|
+
previously untested methods and for `Client#upload_and_request_signatures`.
|
|
75
|
+
|
|
76
|
+
### Fixed
|
|
77
|
+
|
|
78
|
+
- `AssignmentResource#reset_expiration` now sends `expires_at` verbatim, so an
|
|
79
|
+
explicit `nil` is serialized as JSON `null` ("no expiration") instead of being
|
|
80
|
+
dropped from the body (the field is required by the API). Verified live.
|
|
81
|
+
- `Support::WebhookVerifier#event_data`/`#event_type` corrected to the real envelope:
|
|
82
|
+
`event_type` reads `event` (the fabricated `type` fallback was removed), and
|
|
83
|
+
`event_data` is deprecated in favor of `#event_payload`/`#event_object`.
|
|
84
|
+
- `DocumentResource#send_token` validates `recipient`/`channel` before the request.
|
|
85
|
+
- `SignerResource#find_by_email` now paginates through all result pages instead of
|
|
86
|
+
relying on a single oversized page (the API clamps `per-page` to its own maximum —
|
|
87
|
+
observed as 50 on the sandbox).
|
|
88
|
+
- `Client#upload_and_request_signatures` raises a clear `ApiError` if a created
|
|
89
|
+
signer comes back without an ID, rather than building an assignment with `nil` IDs.
|
|
90
|
+
- `TagResource#update` rejects an empty payload or a blank name before issuing a no-op PUT.
|
|
91
|
+
|
|
92
|
+
### Changed
|
|
93
|
+
|
|
94
|
+
- **Minimum Ruby is now 3.2** (3.0 and 3.1 are end-of-life). CI tests 3.2, 3.3, 3.4,
|
|
95
|
+
4.0, and head. `.ruby-version` is committed (3.4.8) and drives the lint/audit/release jobs.
|
|
96
|
+
- `SignerDocumentResource#list` accepts an optional `signer_access_code:` (the endpoint
|
|
97
|
+
also supports workspace `X-Api-Key` auth); the class auth documentation was corrected.
|
|
98
|
+
- CI gained a `concurrency` group to cancel superseded runs.
|
|
99
|
+
|
|
100
|
+
## 1.3.1
|
|
101
|
+
|
|
102
|
+
### Added
|
|
103
|
+
|
|
104
|
+
- `Client#tags` and `Resources::TagResource` for the documented workspace tag
|
|
105
|
+
endpoints.
|
|
106
|
+
- Document tag helpers on `Resources::DocumentResource`: `list_tags`,
|
|
107
|
+
`replace_tags`, `append_tags`, and `detach_tag`.
|
|
108
|
+
|
|
109
|
+
### Fixed
|
|
110
|
+
|
|
111
|
+
- Assignment signer payloads now preserve the documented `step` field for
|
|
112
|
+
sequential signing.
|
|
113
|
+
|
|
114
|
+
## 1.3.0
|
|
115
|
+
|
|
116
|
+
### Added
|
|
117
|
+
|
|
118
|
+
- YARD documentation for every public method on `Client`, `Configuration`,
|
|
119
|
+
every `Resources::*` class, `Support::WebhookVerifier`, and the SDK's
|
|
120
|
+
error hierarchy.
|
|
121
|
+
- `spec/api_coverage_spec.rb` — an explicit, version-controlled matrix that
|
|
122
|
+
asserts every documented endpoint at
|
|
123
|
+
https://api.assinafy.com.br/v1/docs has a corresponding SDK method.
|
|
124
|
+
|
|
125
|
+
### Fixed
|
|
126
|
+
|
|
127
|
+
- `AssignmentResource#sign` now translates snake_case keys (`item_id`,
|
|
128
|
+
`field_id`, `page_id`, `value`) to the camelCase keys (`itemId`, `fieldId`,
|
|
129
|
+
`pageId`, `value`) that the `POST /documents/{documentId}/assignments/{assignmentId}`
|
|
130
|
+
endpoint expects — the only place the API deviates from snake_case.
|
|
131
|
+
Already-camelCase input is passed through unchanged.
|
|
132
|
+
|
|
133
|
+
### Changed
|
|
134
|
+
|
|
135
|
+
- README expanded with one runnable example per resource matching the
|
|
136
|
+
documented Assinafy v1 surface area.
|
|
137
|
+
|
|
138
|
+
## 1.2.0
|
|
139
|
+
|
|
140
|
+
### Changed
|
|
141
|
+
|
|
142
|
+
- Consolidated HTTP error handling in `BaseResource` behind a single private
|
|
143
|
+
`request` helper used by `call`, `call_void`, `call_binary`, `call_list`,
|
|
144
|
+
and `call_optional`.
|
|
145
|
+
- Pagination metadata extraction in `BaseResource` is now driven by a
|
|
146
|
+
declarative header → key mapping.
|
|
147
|
+
- `Client.from_config` now delegates to `Configuration.from_hash`. A new
|
|
148
|
+
`Client.from_hash` alias is exposed for symmetry with `Configuration`.
|
|
149
|
+
- `Client#upload_and_request_signatures` no longer duplicates the signer
|
|
150
|
+
payload normalization logic — it relies on `SignerResource#create`.
|
|
151
|
+
- `NullLogger` now responds to the full Ruby `Logger` severity surface.
|
|
152
|
+
- Minimum Ruby is now 3.0 (matching the CI matrix).
|
|
153
|
+
|
|
154
|
+
### Removed
|
|
155
|
+
|
|
156
|
+
- Undocumented `expiration` field from `AssignmentResource.build_payload`
|
|
157
|
+
— only `expires_at` is documented in the public API.
|
|
158
|
+
|
|
159
|
+
### Fixed
|
|
160
|
+
|
|
161
|
+
- Documented `signer-access-code` and pagination handling are now consistent
|
|
162
|
+
across every resource.
|
|
163
|
+
|
|
164
|
+
### Release
|
|
165
|
+
|
|
166
|
+
- Added a tag-triggered release workflow that publishes to both GitHub
|
|
167
|
+
Packages (`rubygems.pkg.github.com/assinafy`) and RubyGems.org. The verify
|
|
168
|
+
job blocks publishing when the git tag does not match `Assinafy::VERSION`.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2026 Assinafy
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
# Assinafy Ruby SDK
|
|
2
|
+
|
|
3
|
+
[](https://github.com/assinafy/ruby-sdk/actions/workflows/ci.yml)
|
|
4
|
+
[](https://rubygems.org/gems/assinafy)
|
|
5
|
+
|
|
6
|
+
Ruby SDK for the [Assinafy API v1](https://api.assinafy.com.br/v1/docs).
|
|
7
|
+
|
|
8
|
+
The SDK includes a public wrapper for every operation in the current Assinafy v1 [OpenAPI specification](https://api.assinafy.com.br/v1/docs/openapi.json), plus sandbox-live template routes that are not yet listed there. The checked-in [`spec/api_coverage_spec.rb`](spec/api_coverage_spec.rb) verifies that its static route-to-method inventory is unique and points to public SDK methods.
|
|
9
|
+
|
|
10
|
+
- **Source:** <https://github.com/assinafy/ruby-sdk>
|
|
11
|
+
- **Issues:** <https://github.com/assinafy/ruby-sdk/issues>
|
|
12
|
+
- **API docs:** <https://api.assinafy.com.br/v1/docs>
|
|
13
|
+
|
|
14
|
+
## Requirements
|
|
15
|
+
|
|
16
|
+
- Ruby 3.2+ (maintained support: 3.3+; 3.2 is legacy/EOL compatibility)
|
|
17
|
+
- Bundler
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
From RubyGems.org:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
gem 'assinafy'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
bundle install
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
From GitHub Packages (mirror):
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
source 'https://rubygems.pkg.github.com/assinafy' do
|
|
35
|
+
gem 'assinafy'
|
|
36
|
+
end
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
You'll need a personal access token with `read:packages` scope, configured via:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
bundle config https://rubygems.pkg.github.com/assinafy USERNAME:TOKEN
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
require 'assinafy'
|
|
49
|
+
|
|
50
|
+
client = Assinafy::Client.new(
|
|
51
|
+
api_key: ENV.fetch('ASSINAFY_API_KEY'),
|
|
52
|
+
account_id: ENV.fetch('ASSINAFY_ACCOUNT_ID')
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
document = client.documents.upload({ file_path: './contract.pdf' })
|
|
56
|
+
signer = client.signers.create(full_name: 'Alice Silva', email: 'alice@example.com')
|
|
57
|
+
|
|
58
|
+
assignment = client.assignments.create(
|
|
59
|
+
document['id'],
|
|
60
|
+
method: 'virtual',
|
|
61
|
+
signers: [{ id: signer['id'] }],
|
|
62
|
+
message: 'Please sign the attached contract.'
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
puts assignment['id']
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Configuration
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
client = Assinafy::Client.new(
|
|
72
|
+
api_key: 'your-api-key',
|
|
73
|
+
token: nil,
|
|
74
|
+
account_id: 'your-account-id',
|
|
75
|
+
base_url: 'https://api.assinafy.com.br/v1',
|
|
76
|
+
webhook_secret: nil,
|
|
77
|
+
timeout: 30,
|
|
78
|
+
logger: Logger.new($stdout)
|
|
79
|
+
)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- `api_key:` sends `X-Api-Key` (preferred).
|
|
83
|
+
- `token:` sends `Authorization: Bearer ...` (legacy session token).
|
|
84
|
+
- A client can also be created with no credentials for authentication and public/signer endpoints.
|
|
85
|
+
- Account-scoped methods document a per-call account override for multi-workspace tenants.
|
|
86
|
+
- Provide a `Logger`-compatible `logger:` to observe upload/assignment/webhook lifecycle messages.
|
|
87
|
+
|
|
88
|
+
`Client.from_config(hash)` accepts string- or symbol-keyed hashes (e.g. parsed YAML).
|
|
89
|
+
|
|
90
|
+
## Resources
|
|
91
|
+
|
|
92
|
+
`Assinafy::Client` exposes twelve accessors — eleven API resources for the current
|
|
93
|
+
OpenAPI operations and sandbox-live template routes, plus the local `webhook_verifier` helper:
|
|
94
|
+
|
|
95
|
+
| Accessor | What it covers |
|
|
96
|
+
| --------------------------- | -------------------------------------------------------------- |
|
|
97
|
+
| `client.auth` | Login, social login, password reset, API keys |
|
|
98
|
+
| `client.accounts` | Account CRUD, theme, KPI stats, brand logo |
|
|
99
|
+
| `client.users` | User profile, notification preferences, cross-account KPIs |
|
|
100
|
+
| `client.documents` | Upload, list, search, rename, download, delete, verify, tags |
|
|
101
|
+
| `client.signers` | Workspace signer CRUD + signer self-service endpoints |
|
|
102
|
+
| `client.signer_documents` | Signer-authenticated multi-document operations + search |
|
|
103
|
+
| `client.assignments` | List/create/sign/decline/resend/estimate assignments |
|
|
104
|
+
| `client.templates` | Template creation (file upload), get, list, update, delete |
|
|
105
|
+
| `client.tags` | Workspace tags |
|
|
106
|
+
| `client.fields` | Field definitions + validation + type catalog |
|
|
107
|
+
| `client.webhooks` | Subscription, event-type catalog, dispatch history, retries |
|
|
108
|
+
| `client.webhook_verifier` | Optional HMAC-SHA256 verifier for signed deliveries |
|
|
109
|
+
|
|
110
|
+
### Authentication
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
client.auth.login(email: 'user@example.com', password: 'secret')
|
|
114
|
+
client.auth.social_login(provider: 'google', token: 'id-token', has_accepted_terms: true)
|
|
115
|
+
client.auth.link_social_login(provider: 'google', token: 'id-token')
|
|
116
|
+
client.auth.create_api_key(password: 'secret')
|
|
117
|
+
client.auth.get_api_key
|
|
118
|
+
client.auth.delete_api_key
|
|
119
|
+
client.auth.change_password(email: 'user@example.com', password: 'old', new_password: 'new')
|
|
120
|
+
client.auth.request_password_reset(email: 'user@example.com')
|
|
121
|
+
client.auth.reset_password(email: 'user@example.com', new_password: 'new', token: 'reset-token')
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Accounts
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
client.accounts.list # accounts the user can access
|
|
128
|
+
client.accounts.get # the current account (or pass an id)
|
|
129
|
+
client.accounts.create(name: 'Acme Inc.')
|
|
130
|
+
client.accounts.update({ name: 'Acme Renamed' })
|
|
131
|
+
client.accounts.delete(force: true, account_id_override: 'account-id')
|
|
132
|
+
client.accounts.theme # { account_name, primary_color, secondary_color, logo }
|
|
133
|
+
client.accounts.stats(granularity: 'monthly', month: '2026-06') # account KPI rows
|
|
134
|
+
client.accounts.upload_logo({ file_path: './logo.png' })
|
|
135
|
+
client.accounts.download_logo # raw bytes; raises ApiError on HTTP 404 when unset
|
|
136
|
+
client.accounts.delete_logo
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Users
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
client.users.me # OpenAPI: AuthUser; some sandboxes: { user:, accounts: }; data is passed through
|
|
143
|
+
client.users.stats(granularity: 'monthly') # cross-account KPI rows
|
|
144
|
+
client.users.notification_preferences # returns all nine owner-email preferences
|
|
145
|
+
client.users.update_notification_preferences(SignerDeclined: false) # partial request; returns all nine
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Both stats methods return rows with `period`, `documents_uploaded`, `documents_sent`,
|
|
149
|
+
`signature_requests`, `signature_requests_email`, `signature_requests_whatsapp`,
|
|
150
|
+
`signature_requests_viewed`, `signature_requests_completed`, and `documents_certified`.
|
|
151
|
+
|
|
152
|
+
### Documents
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
client.documents.statuses # GET /documents/statuses
|
|
156
|
+
client.documents.list(page: 1, per_page: 20, status: 'pending_signature')
|
|
157
|
+
client.documents.search('contract') # lightweight GET .../documents/search
|
|
158
|
+
client.documents.upload({ file_path: './contract.pdf' }, name: 'Contract v1')
|
|
159
|
+
client.documents.upload({ buffer: pdf_bytes, file_name: 'contract.pdf' })
|
|
160
|
+
client.documents.rename('document-id', 'renamed.pdf') # PATCH /documents/{id}
|
|
161
|
+
client.documents.get('document-id') # alias of .details
|
|
162
|
+
client.documents.wait_until_ready('document-id', max_wait_seconds: 60)
|
|
163
|
+
client.documents.activities('document-id')
|
|
164
|
+
client.documents.thumbnail('document-id') # binary PNG/JPEG
|
|
165
|
+
client.documents.download('document-id', 'certificated') # binary PDF
|
|
166
|
+
client.documents.download('document-id', 'pades') # signed PAdES artifact
|
|
167
|
+
client.documents.download_page('document-id', 'page-id')
|
|
168
|
+
client.documents.delete('document-id')
|
|
169
|
+
client.documents.verify('signature-hash')
|
|
170
|
+
client.documents.public_info('document-id')
|
|
171
|
+
client.documents.send_token('document-id') # current OpenAPI also permits no body
|
|
172
|
+
client.documents.send_token('document-id', email: 'alice@example.com') # current OpenAPI body
|
|
173
|
+
# Current sandbox deployment: recipient: 'alice@example.com', channel: 'email'
|
|
174
|
+
client.documents.list_tags('document-id')
|
|
175
|
+
client.documents.replace_tags('document-id', ['tag-id-1', 'tag-id-2'])
|
|
176
|
+
client.documents.append_tags('document-id', ['tag-id-3'])
|
|
177
|
+
# The current sandbox also accepts existing tag names in these arrays.
|
|
178
|
+
client.documents.detach_tag('document-id', 'tag-id')
|
|
179
|
+
|
|
180
|
+
# Template-driven creation
|
|
181
|
+
client.documents.create_from_template(
|
|
182
|
+
'template-id',
|
|
183
|
+
[{ role_id: 'role', id: 'signer-id', verification_method: 'Email', notification_methods: ['Email'] }],
|
|
184
|
+
{ name: 'Contract', message: 'Please sign', expires_at: '2026-12-31T23:59:00Z' }
|
|
185
|
+
)
|
|
186
|
+
client.documents.estimate_cost_from_template(
|
|
187
|
+
'template-id',
|
|
188
|
+
[{ role_id: 'role', id: 'signer-id', verification_method: 'Whatsapp' }]
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Convenience: signing progress derived from the embedded assignment summary
|
|
192
|
+
client.documents.fully_signed?('document-id')
|
|
193
|
+
client.documents.signing_progress('document-id')
|
|
194
|
+
# => { signed: 1, total: 2, pending: 1, percentage: 50.0 }
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Signers (workspace CRUD)
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
client.signers.create(full_name: 'Alice Silva', email: 'alice@example.com')
|
|
201
|
+
client.signers.create(full_name: 'Bob Costa', phone: '+5548999990000') # phone -> whatsapp_phone_number
|
|
202
|
+
client.signers.get('signer-id')
|
|
203
|
+
client.signers.list(search: 'alice', per_page: 50) # returns { data:, meta: }
|
|
204
|
+
client.signers.update('signer-id', full_name: 'Alice S.', government_id: '00000000000')
|
|
205
|
+
client.signers.delete('signer-id')
|
|
206
|
+
|
|
207
|
+
# Convenience: case-insensitive lookup with built-in 404 handling
|
|
208
|
+
client.signers.find_by_email('alice@example.com')
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Signers (self-service, signer-access-code)
|
|
212
|
+
|
|
213
|
+
```ruby
|
|
214
|
+
client.signers.self_data(signer_access_code: 'code') # includes has_signature, has_initial, is_signature_reusable
|
|
215
|
+
client.signers.accept_terms(signer_access_code: 'code')
|
|
216
|
+
client.signers.verify_email(verification_code: '123456', signer_access_code: 'code')
|
|
217
|
+
client.signers.confirm_data('document-id', { full_name: 'Alice Silva', email: 'alice@example.com', government_id: '00000000000' }, signer_access_code: 'code')
|
|
218
|
+
client.signers.upload_signature(png_bytes, signer_access_code: 'code', type: 'signature', content_type: 'image/png')
|
|
219
|
+
# => nil for the documented no-data envelope; some deployments return []
|
|
220
|
+
client.signers.download_signature(signer_access_code: 'code', type: 'signature')
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Assignments
|
|
224
|
+
|
|
225
|
+
```ruby
|
|
226
|
+
# Virtual (no positioned fields)
|
|
227
|
+
client.assignments.create(
|
|
228
|
+
'document-id',
|
|
229
|
+
method: 'virtual',
|
|
230
|
+
signers: [{ id: 'signer-1', verification_method: 'Email', notification_methods: ['Email'], step: 1 }],
|
|
231
|
+
message: 'Please sign',
|
|
232
|
+
expires_at: '2026-12-31T23:59:00Z',
|
|
233
|
+
copy_receivers: ['cc-signer-id']
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# Collect (positioned fields)
|
|
237
|
+
client.assignments.create(
|
|
238
|
+
'document-id',
|
|
239
|
+
method: 'collect',
|
|
240
|
+
signers: [{ id: 'signer-1' }],
|
|
241
|
+
entries: [{ page_id: 'page-id', fields: [{ signer_id: 'signer-1', field_id: 'field-id',
|
|
242
|
+
display_settings: { left: 100, top: 100, width: 240,
|
|
243
|
+
height: 48, fontSize: 16 } }] }]
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
client.assignments.list # GET /assignments (scoped to the account)
|
|
247
|
+
client.assignments.estimate_cost('document-id', signers: [{ verification_method: 'Whatsapp' }])
|
|
248
|
+
client.assignments.reset_expiration('document-id', 'assignment-id', '2026-12-31T23:59:00Z')
|
|
249
|
+
client.assignments.reset_expiration('document-id', 'assignment-id', nil) # clears the expiry
|
|
250
|
+
client.assignments.resend_notification('document-id', 'assignment-id', 'signer-id')
|
|
251
|
+
client.assignments.estimate_resend_cost('document-id', 'assignment-id', 'signer-id')
|
|
252
|
+
client.assignments.whatsapp_notifications('document-id', 'assignment-id')
|
|
253
|
+
|
|
254
|
+
# Signer perspective (signer-access-code authentication)
|
|
255
|
+
client.assignments.signer_document(signer_access_code: 'code', has_accepted_terms: true)
|
|
256
|
+
client.assignments.sign(
|
|
257
|
+
'document-id',
|
|
258
|
+
'assignment-id',
|
|
259
|
+
[{ item_id: 'i1', field_id: 'f1', page_id: 'p1', value: 'Alice' }],
|
|
260
|
+
signer_access_code: 'code'
|
|
261
|
+
)
|
|
262
|
+
client.assignments.decline('document-id', 'assignment-id', decline_reason: 'Clause 2', signer_access_code: 'code')
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
> The `sign` request body is the API's camelCase body-key exception. This SDK accepts the snake_case keys (`item_id`, `field_id`, `page_id`, `value`) shown above and maps them to `itemId/fieldId/pageId/value` automatically. CamelCase input is also passed through unchanged. Assignment listing separately uses the live-required `accountId` query parameter.
|
|
266
|
+
|
|
267
|
+
### Signer documents (multi-document workflows)
|
|
268
|
+
|
|
269
|
+
```ruby
|
|
270
|
+
client.signer_documents.current('signer-id', signer_access_code: 'code')
|
|
271
|
+
client.signer_documents.list('signer-id', { status: 'pending_signature' }, signer_access_code: 'code')
|
|
272
|
+
client.signer_documents.search('signer-id', 'contract', signer_access_code: 'code')
|
|
273
|
+
client.signer_documents.sign_multiple(%w[doc-1 doc-2], signer_access_code: 'code')
|
|
274
|
+
client.signer_documents.decline_multiple(%w[doc-1 doc-2], decline_reason: 'No', signer_access_code: 'code')
|
|
275
|
+
client.signer_documents.download('signer-id', 'document-id', 'pades') # public: no access code needed
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Templates
|
|
279
|
+
|
|
280
|
+
```ruby
|
|
281
|
+
client.templates.list(search: 'contract', per_page: 25)
|
|
282
|
+
client.templates.get('template-id')
|
|
283
|
+
client.templates.create({ file_path: './contract.pdf' }) # multipart file upload
|
|
284
|
+
client.templates.create({ buffer: pdf_bytes, file_name: 'contract.pdf' })
|
|
285
|
+
client.templates.update('template-id', name: 'Renamed template')
|
|
286
|
+
client.templates.delete('template-id')
|
|
287
|
+
client.templates.download_page('template-id', 'page-id') # binary image bytes
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
> Template endpoints (`get`/`create`/`update`/`delete`/`download_page`) are live-verified against the sandbox but are not part of the current OpenAPI document. `create` requires a source file (`multipart/form-data`); the template name defaults to the uploaded file's name.
|
|
291
|
+
|
|
292
|
+
### Tags
|
|
293
|
+
|
|
294
|
+
```ruby
|
|
295
|
+
client.tags.list(search: 'contract')
|
|
296
|
+
client.tags.create(name: 'Contracts', color: 'ff8800')
|
|
297
|
+
client.tags.update('tag-id', name: 'Sales Contracts', color: nil)
|
|
298
|
+
client.tags.delete('tag-id') # fails with 409 if the tag is in use
|
|
299
|
+
client.tags.delete('tag-id', force: true) # detaches from documents/templates first
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Fields
|
|
303
|
+
|
|
304
|
+
```ruby
|
|
305
|
+
client.fields.types # GET /field-types
|
|
306
|
+
client.fields.list(include_inactive: true, include_standard: false)
|
|
307
|
+
client.fields.create(type: 'text', name: 'Internal code', regex: '/[A-Z]{3}-[0-9]{4}/')
|
|
308
|
+
client.fields.get('field-id')
|
|
309
|
+
client.fields.update('field-id', name: 'Renamed')
|
|
310
|
+
client.fields.delete('field-id')
|
|
311
|
+
|
|
312
|
+
# Authenticated as a workspace user
|
|
313
|
+
client.fields.validate('field-id', 'ABC-1234')
|
|
314
|
+
# Or authenticated via signer-access-code
|
|
315
|
+
client.fields.validate('field-id', 'ABC-1234', signer_access_code: 'code')
|
|
316
|
+
client.fields.validate_multiple(
|
|
317
|
+
[{ field_id: 'a', value: '1' }, { field_id: 'b', value: 'value@example.com' }],
|
|
318
|
+
signer_access_code: 'code'
|
|
319
|
+
)
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Webhooks
|
|
323
|
+
|
|
324
|
+
```ruby
|
|
325
|
+
client.webhooks.list_event_types # GET /webhooks/event-types
|
|
326
|
+
client.webhooks.get # current subscription (nil on 404)
|
|
327
|
+
client.webhooks.register(
|
|
328
|
+
url: 'https://example.com/webhooks/assinafy',
|
|
329
|
+
email: 'ops@example.com',
|
|
330
|
+
events: %w[document_ready document_prepared signer_signed_document]
|
|
331
|
+
)
|
|
332
|
+
client.webhooks.inactivate # stop deliveries, keep the event set
|
|
333
|
+
|
|
334
|
+
client.webhooks.list_dispatches(delivered: false, per_page: 50)
|
|
335
|
+
client.webhooks.retry_dispatch('dispatch-id')
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
> To stop deliveries, use `inactivate` (the API has no delete-subscription route).
|
|
339
|
+
|
|
340
|
+
#### Webhook signature verification
|
|
341
|
+
|
|
342
|
+
The Assinafy API does not currently document a body-signing scheme for outbound webhook deliveries. The SDK ships an opt-in HMAC-SHA256 verifier you can use when a gateway in front of your receiver is configured to sign payloads with a shared secret:
|
|
343
|
+
|
|
344
|
+
```ruby
|
|
345
|
+
verifier = Assinafy::Support::WebhookVerifier.new(ENV.fetch('ASSINAFY_WEBHOOK_SECRET'))
|
|
346
|
+
raw_body = request.body.read
|
|
347
|
+
|
|
348
|
+
# The signature header is one your gateway injects (e.g. Cloudflare / API Gateway).
|
|
349
|
+
# Assinafy v1 does not send a signature header itself.
|
|
350
|
+
if verifier.verify(raw_body, request.headers['X-Webhook-Signature'])
|
|
351
|
+
event = verifier.extract_event(raw_body)
|
|
352
|
+
verifier.event_type(event) # => "assignment_created" (the top-level `event`)
|
|
353
|
+
verifier.event_payload(event) # => event-specific params, or nil
|
|
354
|
+
verifier.event_object(event) # => the entity acted on, e.g. the document
|
|
355
|
+
verifier.event_subject(event) # => the actor, e.g. the user
|
|
356
|
+
end
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
If no `webhook_secret` is configured, `verify` always returns `false` — safe-by-default.
|
|
360
|
+
|
|
361
|
+
## Responses
|
|
362
|
+
|
|
363
|
+
Most JSON successes use a `{ "status": ..., "message": ..., "data": ... }` envelope. The SDK
|
|
364
|
+
returns the `data` payload (a Hash for single resources, an Array for collection bodies). For
|
|
365
|
+
documented no-data envelopes containing only `status`/`message`, it returns `nil`; deployed API
|
|
366
|
+
versions that add `data` are passed through. Binary endpoints (`download`, `thumbnail`,
|
|
367
|
+
`download_page`, `download_signature`) return raw bytes as an ASCII-8BIT `String`, and
|
|
368
|
+
delete-style endpoints return `nil`.
|
|
369
|
+
|
|
370
|
+
Errors surface the envelope/framework error body through `Assinafy::ApiError`:
|
|
371
|
+
|
|
372
|
+
```ruby
|
|
373
|
+
begin
|
|
374
|
+
client.documents.details('missing-id')
|
|
375
|
+
rescue Assinafy::ApiError => e
|
|
376
|
+
e.status_code # => 404
|
|
377
|
+
e.message # => "Documento não encontrado."
|
|
378
|
+
e.error_name # => nil (or "Not Found" for framework errors)
|
|
379
|
+
e.error_code # => nil (or an integer code)
|
|
380
|
+
e.response_data # => the raw parsed body
|
|
381
|
+
end
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Resource YARD documentation includes request/response examples and calls out known differences
|
|
385
|
+
between the current OpenAPI document and the deployed sandbox.
|
|
386
|
+
|
|
387
|
+
## Pagination
|
|
388
|
+
|
|
389
|
+
Most `*.list*` methods return `{ data: [...], meta: { ... } }` when the API includes pagination
|
|
390
|
+
headers. Ruby-style `per_page:` is transparently converted to the documented `per-page` query
|
|
391
|
+
parameter (values above the API's maximum are clamped server-side; the sandbox caps it at 50).
|
|
392
|
+
A few endpoints (e.g. `fields.list`) do not paginate and return `meta: nil`.
|
|
393
|
+
|
|
394
|
+
```ruby
|
|
395
|
+
result = client.documents.list(page: 2, per_page: 25)
|
|
396
|
+
result[:data] # => Array<Hash>
|
|
397
|
+
result[:meta] # => { current_page: 2, per_page: 25, total: 138, last_page: 6 }
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## High-level workflow helper
|
|
401
|
+
|
|
402
|
+
`Client#upload_and_request_signatures` bundles upload + signer creation + virtual assignment into a single call:
|
|
403
|
+
|
|
404
|
+
```ruby
|
|
405
|
+
result = client.upload_and_request_signatures(
|
|
406
|
+
source: { file_path: './contract.pdf' },
|
|
407
|
+
signers: [
|
|
408
|
+
{ full_name: 'Alice Silva', email: 'alice@example.com' },
|
|
409
|
+
{ full_name: 'Bob Costa', whatsapp_phone_number: '+5548999990000' }
|
|
410
|
+
],
|
|
411
|
+
message: 'Please sign.',
|
|
412
|
+
expires_at: '2026-12-31T23:59:00Z'
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
puts result[:document]['id']
|
|
416
|
+
puts result[:assignment]['id']
|
|
417
|
+
result[:signer_ids] # => ['<sid-1>', '<sid-2>']
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## Errors
|
|
421
|
+
|
|
422
|
+
The SDK raises one of:
|
|
423
|
+
|
|
424
|
+
- `Assinafy::ValidationError` — caller-side input invalid (missing IDs, bad email, etc.).
|
|
425
|
+
- `Assinafy::ApiError` — the API returned a non-2xx status. Includes `status_code` and `response_data`.
|
|
426
|
+
- `Assinafy::NetworkError` — Faraday connection error or timeout.
|
|
427
|
+
- `Assinafy::Error` — base class; other unexpected errors get wrapped here with the operation label.
|
|
428
|
+
|
|
429
|
+
All inherit a `#context` Hash with debugging metadata.
|
|
430
|
+
|
|
431
|
+
## Tests
|
|
432
|
+
|
|
433
|
+
```bash
|
|
434
|
+
bundle exec rake spec # 200+ RSpec examples, including a coverage matrix
|
|
435
|
+
bundle exec rubocop # Linting
|
|
436
|
+
bundle exec bundler-audit check # Dependency CVEs
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
The coverage spec checks the committed route-to-method matrix for duplicate operations or wrappers, missing public methods, aliases that drift, and unmapped resource methods. It does not download or compare the [OpenAPI document](https://api.assinafy.com.br/v1/docs/openapi.json) during the test run; update the matrix deliberately when the remote contract changes.
|
|
440
|
+
|
|
441
|
+
### Live integration tests
|
|
442
|
+
|
|
443
|
+
The suite in [`spec/integration/`](spec/integration/live_sandbox_spec.rb) exercises representative workflows across every resource against the real sandbox. It is not an exhaustive operation-by-operation contract check: OTP- or feature-gated routes may be skipped, and sandbox rollout can lag the current OpenAPI document. The suite is excluded from the default run and only executes when `ASSINAFY_LIVE=1` is set with credentials:
|
|
444
|
+
|
|
445
|
+
```bash
|
|
446
|
+
ASSINAFY_LIVE=1 \
|
|
447
|
+
ASSINAFY_API_KEY=... \
|
|
448
|
+
ASSINAFY_ACCOUNT_ID=... \
|
|
449
|
+
ASSINAFY_TEST_EMAIL=recipient1@example.com \
|
|
450
|
+
ASSINAFY_TEST_EMAIL2=recipient2@example.com \
|
|
451
|
+
ASSINAFY_BASE_URL=https://sandbox.assinafy.com.br/v1 \
|
|
452
|
+
bundle exec rspec spec/integration
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
> These tests create and clean up real resources and, for the assignment flow, send real signature-request emails to the addresses in `ASSINAFY_TEST_EMAIL` / `ASSINAFY_TEST_EMAIL2`.
|
|
456
|
+
|
|
457
|
+
## Contributing
|
|
458
|
+
|
|
459
|
+
Pull requests and issues are welcome at <https://github.com/assinafy/ruby-sdk>.
|
|
460
|
+
|
|
461
|
+
## License
|
|
462
|
+
|
|
463
|
+
MIT. See [LICENSE](LICENSE).
|