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
89
# 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)
  validate_optional_fields!(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


241
242
243
244
245
246
247
248
249
250
# File 'lib/assinafy/resources/assignment_resource.rb', line 241

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


455
456
457
458
459
460
461
462
463
464
465
466
# File 'lib/assinafy/resources/assignment_resource.rb', line 455

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


275
276
277
278
279
280
281
282
# File 'lib/assinafy/resources/assignment_resource.rb', line 275

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


362
363
364
365
366
367
368
369
370
# File 'lib/assinafy/resources/assignment_resource.rb', line 362

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


199
200
201
202
203
204
205
206
# File 'lib/assinafy/resources/assignment_resource.rb', line 199

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


331
332
333
334
335
336
337
338
339
# File 'lib/assinafy/resources/assignment_resource.rb', line 331

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


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

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}


429
430
431
432
433
434
435
436
437
438
439
# File 'lib/assinafy/resources/assignment_resource.rb', line 429

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


395
396
397
398
399
400
401
402
403
404
# File 'lib/assinafy/resources/assignment_resource.rb', line 395

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


487
488
489
490
491
492
493
494
# File 'lib/assinafy/resources/assignment_resource.rb', line 487

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