Class: Assinafy::Resources::AssignmentResource

Inherits:
BaseResource
  • Object
show all
Defined in:
lib/assinafy/resources/assignment_resource.rb,
sig/assinafy.rbs

Overview

Assignments — invitations to sign a specific document. Covers virtual (no positioned fields) and collect (positioned fields) methods, cost estimation, signer notification resends, declines, and signing.

See https://api.assinafy.com.br/v1/docs#assignment for the full documentation of these endpoints.

Constant Summary collapse

OPTIONAL_FIELDS =

Returns:

  • (Array[Symbol])
%i[message expires_at copy_receivers].freeze
METHODS =

Returns:

  • (Array[String])
%w[virtual collect].freeze
SIGN_ITEM_KEY_MAP =

Returns:

  • (Hash[String, String])
{
  'item_id'  => 'itemId',
  'field_id' => 'fieldId',
  'page_id'  => 'pageId',
  'value'    => 'value'
}.freeze

Constants inherited from BaseResource

BaseResource::AUTH_HEADERS, BaseResource::PAGINATION_HEADERS, BaseResource::PATH_SEGMENT

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from BaseResource

#initialize

Constructor Details

This class inherits a constructor from Assinafy::Resources::BaseResource

Class Method Details

.build_payload(payload, options = {}) ⇒ Hash

Note:

The OpenAPI marks top-level signers as required, but the sandbox accepts collect payloads that reference signer IDs only in positioned fields. This builder preserves that live-compatible form.

Normalise a flexible Ruby-side assignment payload into the body shape the API expects. Accepts:

  • signers: ['id1', 'id2'] — bare IDs
  • signers: [{ id:, verification_method:, notification_methods:, step: }]
  • Legacy signer_ids:/signerIds: arrays of IDs

Examples:

Bare signer IDs are normalised into { id: } hashes (virtual method)

Assinafy::Resources::AssignmentResource.build_payload(signers: %w[s1 s2])
# => { "method" => "virtual", "signers" => [{ "id" => "s1" }, { "id" => "s2" }] }

Rich signer descriptors with sequential signing steps and optional fields

Assinafy::Resources::AssignmentResource.build_payload(
  signers:        [{ id: "s1", verification_method: "Email", notification_methods: ["Email"], step: 1 }],
  message:        "Please sign",
  expires_at:     "2026-12-31T23:59:00Z",
  copy_receivers: ["copy-signer-id"]
)
# => {
#   "method"     => "virtual",
#   "signers"    => [{ "id" => "s1", "verification_method" => "Email",
#                      "notification_methods" => ["Email"], "step" => 1 }],
#   "message"    => "Please sign", "expires_at" => "2026-12-31T23:59:00Z",
#   "copy_receivers" => ["copy-signer-id"]
# }

Estimate-cost payload — method-only descriptor with no id (allow flag set)

Assinafy::Resources::AssignmentResource.build_payload(
  { signers: [{ verification_method: "Whatsapp" }] }, { allow_signers_without_id: true }
)
# => { "method" => "virtual", "signers" => [{ "verification_method" => "Whatsapp" }] }

Collect method — positioned fields include all required display settings

Assinafy::Resources::AssignmentResource.build_payload(
  method: "collect",
  entries: [{
    page_id: "page-id",
    fields: [{
      signer_id: "signer-id",
      field_id: "field-id",
      display_settings: { left: 100, top: 100, width: 240, height: 48, fontSize: 16 }
    }]
  }]
)
# => { "method" => "collect", "entries" => [{ ... }] }

Parameters:

  • payload (Hash)
  • options (Hash) (defaults to: {})

Options Hash (options):

  • :allow_signers_without_id (Boolean)

    allow estimate-cost payloads where method-only signer descriptors carry no id

Returns:

Raises:



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/assinafy/resources/assignment_resource.rb', line 73

def build_payload(payload, options = {})
  p = Utils.clean_params(payload).transform_keys(&:to_sym) if payload.is_a?(Hash)
  raise ValidationError.new('Assignment payload must be a Hash') unless p

  signers = extract_signer_refs(p)
  entries = p[:entries]
  method  = (p[:method] || 'virtual').to_s

  validate_method!(method, signers, entries, p)

  result = { method: method }
  result[:signers] = signers.map { |ref| normalise_signer_ref(ref, options) } unless signers.empty?
  OPTIONAL_FIELDS.each { |key| result[key] = p[key] if p[key] }
  result[:entries] = entries if entries
  Utils.body_params(result)
end

Instance Method Details

#create(document_id, payload) ⇒ Hash

Create an assignment for a document. See build_payload for the accepted shapes, including the sandbox-compatible collect form without a top-level signers array.

Examples:

Create a virtual assignment for one signer

resource.create("document-id", signers: %w[signer-id], message: "Please sign")
# Request body the SDK sends:
# { "method" => "virtual", "signers" => [{ "id" => "signer-id" }],
#   "message" => "Please sign" }
# => {
#   "resource" => "assignment", "id" => "assignment-id",
#   "sender_email" => "sender@example.com", "method" => "virtual",
#   "expires_at" => nil, "message" => "Please sign",
#   "signers" => [{ "id" => "signer-id", "full_name" => "Example Signer",
#     "email" => "signer@example.com", "whatsapp_phone_number" => nil,
#     "has_accepted_terms" => false, "completed" => false, "notification_history" => [],
#     "verification_method" => "Email", "notification_methods" => ["Email"],
#     "step" => 1, "notified" => true }],
#   "copy_receivers" => [],
#   "items" => [{ "id" => "assignment-item-id", "page" => nil,
#     "signer" => { "id" => "signer-id", ... },
#     "field" => { "id" => "field-id", "name" => "Virtual",
#       "type" => "virtual", "is_pre_defined" => true, ... },
#     "display_settings" => [], "value" => nil, "completed" => false }],
#   "summary" => { "signer_count" => 1, "completed_count" => 0, "signers" => [{ ... }] },
#   "signing_urls" => [{ "signer_id" => "signer-id",
#     "url" => "https://app-sandbox.assinafy.com.br/sign/document-id?email=signer%40example.com" }]
# } # ... (see docs for full shape)

Parameters:

  • document_id (String)
  • payload (Hash)

Returns:

  • (Hash)

    the assignment object (resource, id, method, expires_at, message, signers, items, summarycompleted_count, signers[], signing_urls, copy_receivers)

See Also:

  • /documents/{documentId}/assignments


218
219
220
221
222
223
224
225
226
227
# File 'lib/assinafy/resources/assignment_resource.rb', line 218

def create(document_id, payload)
  doc_id = require_id(document_id, 'Document ID')
  body   = self.class.build_payload(payload)

  @logger.info("Creating assignment for document #{doc_id}")

  call('Failed to create assignment') do
    http_post("documents/#{doc_id}/assignments", body)
  end
end

#decline(document_id, assignment_id, decline_reason:, signer_access_code:) ⇒ Array

Decline an assignment as a signer.

Examples:

Decline an assignment as the signer

resource.decline("document-id", "assignment-id",
                 decline_reason: "I do not agree with clause 2.",
                 signer_access_code: "signer-access-code")
# Request body the SDK sends: { "decline_reason" => "I do not agree with clause 2." }
# => []

Parameters:

  • document_id (String)
  • assignment_id (String)
  • decline_reason (String)
  • signer_access_code (String)

Returns:

  • (Array)

    empty array on success (the API returns no payload)

See Also:

  • /documents/{documentId}/assignments/{assignmentId}/reject


432
433
434
435
436
437
438
439
440
441
442
443
# File 'lib/assinafy/resources/assignment_resource.rb', line 432

def decline(document_id, assignment_id, decline_reason:, signer_access_code:)
  doc_id = require_id(document_id, 'Document ID')
  asg_id = require_id(assignment_id, 'Assignment ID')
  reason = require_string(decline_reason, 'Decline reason')
  access_code = require_signer_access_code(signer_access_code)

  call_array('Failed to decline assignment') do
    http_put("documents/#{doc_id}/assignments/#{asg_id}/reject",
             body_params(decline_reason: reason),
             { signer_access_code: access_code }, workspace_auth: false)
  end
end

#estimate_cost(document_id, payload) ⇒ Hash

Estimate the credit cost of a potential assignment, without creating it. Accepts the same payload as #create, but signer descriptors may omit id. An empty descriptor ({}) defaults both methods to Email.

Examples:

Estimate cost of inviting a WhatsApp signer (no id needed)

resource.estimate_cost("document-id",
                       signers: [{ verification_method: "Whatsapp" }])
# Request body the SDK sends:
# { "method" => "virtual", "signers" => [{ "verification_method" => "Whatsapp" }] }
# => {
#   "documents" => 1, "credits" => 0.45, "needs_extra_document" => false,
#   "extra_document_cost" => 0, "total_credits" => 0.45,
#   "breakdown" => [{ "code" => "NotificationWhatsapp", "cost" => 0.45,
#                     "quantity" => 1, "unit_cost" => 0.45 }],
#   "document_balance" => 62, "credit_balance" => 0,
#   "has_sufficient_resources" => true, "blocking_reason" => nil, "message" => nil
# }

Parameters:

  • document_id (String)
  • payload (Hash)

Returns:

  • (Hash)

    cost breakdown (documents, credits, needs_extra_document, extra_document_cost, total_credits, breakdown, document_balance, credit_balance, has_sufficient_resources, blocking_reason, message)

See Also:

  • /documents/{documentId}/assignments/estimate-cost


252
253
254
255
256
257
258
259
# File 'lib/assinafy/resources/assignment_resource.rb', line 252

def estimate_cost(document_id, payload)
  doc_id = require_id(document_id, 'Document ID')
  body   = self.class.build_payload(payload, allow_signers_without_id: true)

  call('Failed to estimate assignment cost') do
    http_post("documents/#{doc_id}/assignments/estimate-cost", body)
  end
end

#estimate_resend_cost(document_id, assignment_id, signer_id) ⇒ Hash

Estimate the credit cost of resending the notification to a signer.

Examples:

Preview the cost of resending to a WhatsApp signer

resource.estimate_resend_cost("document-id", "assignment-id", "signer-id")
# (no request body)
# => {
#   "documents" => 1, "credits" => 0.45, "needs_extra_document" => false,
#   "extra_document_cost" => 0, "total_credits" => 0.45,
#   "breakdown" => [{ "code" => "NotificationWhatsapp",
#                     "name" => "Whatsapp Notification", "cost" => 0.45,
#                     "quantity" => 1, "unit_cost" => 0.45 }],
#   "document_balance" => 10, "credit_balance" => 100,
#   "has_sufficient_resources" => true, "blocking_reason" => nil, "message" => nil
# }

Parameters:

  • document_id (String)
  • assignment_id (String)
  • signer_id (String)

Returns:

  • (Hash)

    cost breakdown (documents, credits, needs_extra_document, extra_document_cost, total_credits, breakdown, document_balance, credit_balance, has_sufficient_resources, blocking_reason, message)

See Also:

  • /documents/{documentId}/assignments/{assignmentId}/signers/{signerId}/estimate-resend-cost


339
340
341
342
343
344
345
346
347
# File 'lib/assinafy/resources/assignment_resource.rb', line 339

def estimate_resend_cost(document_id, assignment_id, signer_id)
  doc_id = require_id(document_id, 'Document ID')
  asg_id = require_id(assignment_id, 'Assignment ID')
  sid    = require_id(signer_id, 'Signer ID')

  call('Failed to estimate resend cost') do
    http_post("documents/#{doc_id}/assignments/#{asg_id}/signers/#{sid}/estimate-resend-cost")
  end
end

#list(params = {}, account_id_override = nil) ⇒ Hash{Symbol=>Array,Hash}

List assignments for an account. The API requires an account context, supplied as the accountId query parameter — note the camelCase, which is unusual for this otherwise snake_case API (verified live).

Examples:

List assignments for the account

# Request: GET /assignments?accountId={account_id}
client.assignments.list

# Response (unwrapped data payload):
{
  data: [
    {
      'id' => 'assignment-id',
      'sender_email' => 'sender@example.com',
      'method' => 'virtual',
      'expires_at' => nil,
      'message' => 'Please sign this contract',
      'signers' => [
        { 'id' => 'signer-id', 'full_name' => 'Example Signer',
          'email' => 'signer@example.com', 'completed' => false, 'step' => 1 }
      ]
      # ... (see docs for the full assignment shape)
    }
  ],
  meta: nil
}

Parameters:

  • params (Hash) (defaults to: {})

    documented page and per_page query parameters

  • account_id_override (String, nil) (defaults to: nil)

Returns:

  • (Hash{Symbol=>Array,Hash})

    { data: [assignment, ...], meta: {..} | nil }

See Also:

  • /assignments


176
177
178
179
180
181
182
183
# File 'lib/assinafy/resources/assignment_resource.rb', line 176

def list(params = {},  = nil)
  acc_id = ()
  query  = require_payload(params, 'Assignment query parameters')

  call_list('Failed to list assignments') do
    http_get('assignments', query.merge(accountId: acc_id))
  end
end

#resend_notification(document_id, assignment_id, signer_id) ⇒ Hash

Resend the assignment notification (email/WhatsApp) to a signer. May charge credits — use #estimate_resend_cost to preview.

Examples:

Resend the signing notification to a signer

resource.resend_notification("document-id", "assignment-id", "signer-id")
# (no request body)
# => { "is_sent" => true, "document_id" => "document-id", "signer_id" => "signer-id" }

Parameters:

  • document_id (String)
  • assignment_id (String)
  • signer_id (String)

Returns:

  • (Hash)

    delivery confirmation (is_sent, document_id, signer_id)

See Also:

  • /documents/{documentId}/assignments/{assignmentId}/signers/{signerId}/resend


308
309
310
311
312
313
314
315
316
# File 'lib/assinafy/resources/assignment_resource.rb', line 308

def resend_notification(document_id, assignment_id, signer_id)
  doc_id = require_id(document_id, 'Document ID')
  asg_id = require_id(assignment_id, 'Assignment ID')
  sid    = require_id(signer_id, 'Signer ID')

  call('Failed to resend signer notification') do
    http_put("documents/#{doc_id}/assignments/#{asg_id}/signers/#{sid}/resend")
  end
end

#reset_expiration(document_id, assignment_id, expires_at) ⇒ Hash

Update the expiration timestamp of an existing assignment. The expires_at body field is required by the API and accepts an explicit nil (serialized as JSON null) to mean "no expiration". The value is therefore sent verbatim rather than through Utils.body_params, which would drop the nil.

Examples:

Set a new expiration timestamp

resource.reset_expiration("document-id", "assignment-id",
                          "2026-12-31T23:59:00Z")
# Request body the SDK sends: { "expires_at" => "2026-12-31T23:59:00Z" }
# => { "resource" => "assignment", "id" => "assignment-id",
#      "method" => "virtual", "expires_at" => "2026-12-31T23:59:00Z",
#      "signers" => [{ ... }], "items" => [{ ... }], "summary" => { ... },
#      "signing_urls" => [{ ... }], "copy_receivers" => [] } # ... (see docs for full shape)

Clear the expiration (nil is sent verbatim as JSON null)

resource.reset_expiration("document-id", "assignment-id", nil)
# Request body the SDK sends: { "expires_at" => nil }
# => { "resource" => "assignment", "id" => "assignment-id",
#      "method" => "virtual", "expires_at" => nil, ... } # ... (see docs for full shape)

Parameters:

  • document_id (String)
  • assignment_id (String)
  • expires_at (String, nil)

    ISO 8601 timestamp, or nil for no expiry

Returns:

  • (Hash)

    the updated assignment object (same shape as #create; expires_at reflects the new value — nil when cleared)

See Also:

  • /documents/{documentId}/assignments/{assignmentId}/reset-expiration


286
287
288
289
290
291
292
293
294
# File 'lib/assinafy/resources/assignment_resource.rb', line 286

def reset_expiration(document_id, assignment_id, expires_at)
  doc_id = require_id(document_id, 'Document ID')
  asg_id = require_id(assignment_id, 'Assignment ID')

  call('Failed to update assignment expiration') do
    http_put("documents/#{doc_id}/assignments/#{asg_id}/reset-expiration",
             { 'expires_at' => expires_at })
  end
end

#sign(document_id, assignment_id, items, signer_access_code:) ⇒ Hash

Submit signatures for an assignment as a signer.

The API uses camelCase for this body. Callers may pass snake_case (item_id, field_id, page_id, value) — this method maps them to the API's itemId, fieldId, pageId, value.

Examples:

Sign with snake_case keys — mapped to camelCase itemId/fieldId/pageId

resource.sign("document-id", "assignment-id",
  [{ item_id: "assignment-item-id", field_id: "field-id",
     page_id: "page-id", value: "Signed by Example Signer" }],
  signer_access_code: "signer-access-code")
# Request body the SDK sends (snake_case keys mapped to camelCase):
# [{ "itemId" => "assignment-item-id", "fieldId" => "field-id",
#    "pageId" => "page-id", "value" => "Signed by Example Signer" }]
# => {} # per the API reference (unverified via workspace key)

Parameters:

  • document_id (String)
  • assignment_id (String)
  • items (Array<Hash>)
  • signer_access_code (String)
  • signer_access_code: (String)

Returns:

  • (Hash)

    empty Hash {} on success per the API reference. Signing requires an emailed OTP, so this exact shape is not independently verifiable with a workspace API key.

See Also:

  • /documents/{documentId}/assignments/{assignmentId}


406
407
408
409
410
411
412
413
414
415
416
# File 'lib/assinafy/resources/assignment_resource.rb', line 406

def sign(document_id, assignment_id, items, signer_access_code:)
  doc_id = require_id(document_id, 'Document ID')
  asg_id = require_id(assignment_id, 'Assignment ID')
  body   = require_array(items, 'Assignment items').map { |item| normalise_sign_item(item) }
  access_code = require_signer_access_code(signer_access_code)

  call('Failed to sign assignment') do
    http_post("documents/#{doc_id}/assignments/#{asg_id}", body,
              { signer_access_code: access_code }, workspace_auth: false)
  end
end

#signer_document(signer_access_code:, has_accepted_terms: nil) ⇒ Hash

Fetch the document a signer is being asked to sign (signer-access-code auth).

Examples:

Resolve the document a signer was invited to sign

resource.signer_document(signer_access_code: "signer-access-code")
# (no request body — signer-access-code is a query param)
# => {
#   "id" => "document-id", "account_id" => "account-id", "name" => "my_document.pdf",
#   "status" => "metadata_ready",
#   "artifacts" => { "original" => "https://.../download/original",
#                    "thumbnail" => "https://.../thumbnail" },
#   "is_closed" => false, "signing_url" => "%ui_base_url%/sign/doc1",
#   "decline_reason" => nil, "declined_by" => nil,
#   "current_signer" => { "id" => "signer-id", "full_name" => "Signer Name",
#     "email" => "signer@example.com", "has_accepted_terms" => false,
#     "verification_method" => "Email", "notification_methods" => ["Email"] },
#   "assignment" => { "id" => "1", "method" => "virtual", "expires_at" => nil,
#     "items" => [{ "id" => "assignment-item-id", "field" => { "type" => "virtual" }, ... }] }
# } # ... (see docs for full shape)

Parameters:

  • signer_access_code (String)
  • has_accepted_terms (Boolean, nil) (defaults to: nil)
  • signer_access_code: (String)
  • has_accepted_terms: (Boolean, nil) (defaults to: nil)

Returns:

  • (Hash)

    the document (id, account_id, name, status, artifacts, signing_url, ...) with an embedded current_signer and assignment (items filtered to the current signer); no pages array

See Also:

  • /sign


372
373
374
375
376
377
378
379
380
381
# File 'lib/assinafy/resources/assignment_resource.rb', line 372

def signer_document(signer_access_code:, has_accepted_terms: nil)
  access_code = require_signer_access_code(signer_access_code)
  accepted = has_accepted_terms.nil? ? nil : require_boolean(has_accepted_terms, 'has_accepted_terms')

  call('Failed to fetch signer assignment document') do
    http_get('sign',
             { signer_access_code: access_code, has_accepted_terms: accepted },
             workspace_auth: false)
  end
end

#whatsapp_notifications(document_id, assignment_id) ⇒ Array<Hash>

List the WhatsApp notifications that were sent for an assignment, including the rendered template text.

Examples:

List WhatsApp notifications sent for an assignment

resource.whatsapp_notifications("document-id", "assignment-id")
# (no request body)
# => [
#   { "sent_at" => 1710000000,
#     "header" => "Documento para assinatura: Contrato de Servico",
#     "body" => "Oi, Maria.\n\nJoao Silva enviou um documento...",
#     "buttons" => [{ "text" => "Abrir documento" }],
#     "phone_number" => "+15555550100", "signer_id" => "signer-id" }
# ]
# => [] # when no WhatsApp notifications were sent (e.g. email-only assignment)

Parameters:

  • document_id (String)
  • assignment_id (String)

Returns:

  • (Array<Hash>)

    notification objects (sent_at, header, body, buttonstext, phone_number, signer_id); empty array when no WhatsApp notifications were sent

See Also:

  • /documents/{documentId}/assignments/{assignmentId}/whatsapp-notifications


464
465
466
467
468
469
470
471
# File 'lib/assinafy/resources/assignment_resource.rb', line 464

def whatsapp_notifications(document_id, assignment_id)
  doc_id = require_id(document_id, 'Document ID')
  asg_id = require_id(assignment_id, 'Assignment ID')

  call_array('Failed to list WhatsApp notifications') do
    http_get("documents/#{doc_id}/assignments/#{asg_id}/whatsapp-notifications")
  end
end