The project is in a healthy, maintained state
Zoho Sign exposes the Zoho Sign API endpoints through Active Call service objects.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies
 Project Readme

Active Call - Zoho Sign

Gem Version

Zoho Sign exposes the Zoho Sign API endpoints through Active Call service objects.

  • Installation
  • Configuration
  • Usage
    • Using call
    • Using call!
    • When to use call or call!
    • Using lists
  • Service Objects
  • Development
  • Contributing
  • License

Installation

Install the gem and add to the application's Gemfile by executing:

bundle add active_call-zoho_sign

If bundler is not being used to manage dependencies, install the gem by executing:

gem install active_call-zoho_sign

Configuration

Create a new Self Client client type from the Zoho Developer Console to retrieve your Client ID and Client Secret.

Choose what you need from the list of Zoho Scopes like ZohoSign.documents.ALL to generate your Grant Token.

Get your Refresh Token by calling ZohoSign::GrantToken::GetService.call(grant_token: '', client_id: '', client_secret: '').refresh_token

Configure your API credentials.

In a Rails application, the standard practice is to place this code in a file named zoho_sign.rb within the config/initializers directory.

require 'active_call-zoho_sign'

ZohoSign::BaseService.configure do |config|
  config.client_id = ''
  config.client_secret = ''
  config.refresh_token = ''

  # Optional configuration.
  config.cache = Rails.cache # Default: ActiveSupport::Cache::MemoryStore.new
  config.logger = Rails.logger # Default: Logger.new($stdout)
  config.logger_level = :debug # Default: :info
  config.log_headers = true # Default: false
  config.log_bodies = true # Default: false
end

While testing, you can set your temporary development OAuth access token in the ZOHO_SIGN_ACCESS_TOKEN environment variable. In your production environment,config.refresh_token will be used.

Usage

Using call

Each service object returned will undergo validation before the call method is invoked to access API endpoints.

After successful validation.

service.success? # => true
service.errors # => #<ActiveModel::Errors []>

After failed validation.

service.success? # => false
service.errors # => #<ActiveModel::Errors [#<ActiveModel::Error attribute=name, type=blank, options={}>]>
service.errors.full_messages # => ["Name can't be blank"]

After a successful call invocation, the response attribute will contain a Faraday::Response object.

service.success? # => true
service.response # => #<Faraday::Response ...>
service.response.success? # => true
service.response.status # => 200
service.response.body # => {"code"=>0, "message"=>"Document has been retrieved", "status"=>"success", "requests"=>{...}}

At this point you will also have a facade object which will hold all the attributes for the specific resource.

service.facade # => #<ZohoSign::Document::Facade @request_status="inprogress" ...>
service.facade.request_status # => 'inprogress'

For convenience, facade attributes can be accessed directly on the service object.

service.request_status # => 'inprogress'

After a failed call invocation, the response attribute will still contain a Faraday::Response object.

service.success? # => false
service.errors # => #<ActiveModel::Errors [#<ActiveModel::Error attribute=base, type=Not found, options={}>]>
service.errors.full_messages # => ["No match found"]
service.response # => #<Faraday::Response ...>
service.response.success? # => false
service.response.status # => 400
service.response.body # => {"code"=>9004, "message"=>"No match found", "status"=>"failure"}

Using call!

Each service object returned will undergo validation before the call! method is invoked to access API endpoints.

After successful validation.

service.success? # => true

After failed validation, a ZohoSign::ValidationError exception will be raised with an errors attribute which will contain an ActiveModel::Errors object.

rescue ZohoSign::ValidationError => exception
  exception.message # => ''
  exception.errors # => #<ActiveModel::Errors [#<ActiveModel::Error attribute=name, type=blank, options={}>]>
  exception.errors.full_messages # => ["Name can't be blank"]

After a successful call! invocation, the response attribute will contain a Faraday::Response object.

service.success? # => true
service.response # => #<Faraday::Response ...>
service.response.success? # => true
service.response.status # => 200
service.response.body # => {"code"=>0, "message"=>"Document has been retrieved", "status"=>"success", "requests"=>{...}}

At this point you will also have a facade object which will hold all the attributes for the specific resource.

service.facade # => #<ZohoSign::Document::Facade @request_status="inprogress" ...>
service.facade.request_status # => 'inprogress'

For convenience, facade attributes can be accessed directly on the service object.

service.request_status # => 'inprogress'

After a failed call! invocation, a ZohoSign::RequestError will be raised with a response attribute which will contain a Faraday::Response object.

rescue ZohoSign::RequestError => exception
  exception.message # => ''
  exception.errors # => #<ActiveModel::Errors [#<ActiveModel::Error attribute=base, type=Not found, options={}>]>
  exception.errors.full_messages # => ["No match found"]
  exception.response # => #<Faraday::Response ...>
  exception.response.status # => 400
  exception.response.body # => {"code"=>9004, "message"=>"No match found", "status"=>"failure"}

When to use call or call!

An example of where to use call would be in a controller doing an inline synchronous request.

class SomeController < ApplicationController
  def update
    @document = ZohoSign::Document::UpdateService.call(**params)

    if @document.success?
      redirect_to [@document], notice: 'Success', status: :see_other
    else
      flash.now[:alert] = @document.errors.full_messages.to_sentence
      render :edit, status: :unprocessable_entity
    end
  end
end

An example of where to use call! would be in a job doing an asynchronous request.

You can use the exceptions to determine which retry strategy to use and which to discard.

class SomeJob < ApplicationJob
  discard_on ZohoSign::NotFoundError

  retry_on ZohoSign::RequestTimeoutError, wait: 5.minutes, attempts: :unlimited
  retry_on ZohoSign::TooManyRequestsError, wait: :polynomially_longer, attempts: 10

  def perform
    ZohoSign::Document::UpdateService.call!(**params)
  end
end

Using lists

If you don't provide the limit argument, multiple API requests will be made untill all records have been returned. You could be rate limited, so use wisely.

Note that the offset argument starts at 1 for the first item.

Service Objects

Documents

Documents

List documents

# https://www.zoho.com/sign/api/document-managment/get-document-list.html

ZohoSign::Document::ListService.call(offset: 1, limit: 10).each do |facade|
  facade.description
end

Sort by column.

ZohoSign::Document::ListService.call(sort_column: 'recipient_email', sort_order: 'ASC').map { _1 }

Filter by column.

ZohoSign::Document::ListService.call(search_columns: { recipient_email: 'eric.cartman@example.com' }).map { _1 }

Columns to sort and filter by are request_name, folder_name, owner_full_name, recipient_email, form_name and created_time.

Get a document

# https://www.zoho.com/sign/api/document-managment/get-details-of-a-particular-document.html

service = ZohoSign::Document::GetService.call(id: '')
service.request_name
service.request_id
service.request_status
service.owner_email
service.owner_first_name
service.owner_last_name
service.attachments
service.sign_percentage
...

Create a document

# https://www.zoho.com/sign/api/document-managment/create-document.html

ZohoSign::Document::CreateService.call(
  file: '/path/to/file.pdf', # or File.open('/path/to/file.pdf')
  file_name: 'file.pdf',
  file_content_type: 'application/pdf',
  data: {
    requests: {
      request_name: 'Name',
      is_sequential: false,
      actions: [{
        action_type: 'SIGN',
        recipient_email: 'eric.cartman@example.com',
        recipient_name: 'Eric Cartman',
        verify_recipient: true,
        verification_type: 'EMAIL'
      }]
    }
  }
)

Update a document

# https://www.zoho.com/sign/api/document-managment/update-document.html

ZohoSign::Document::UpdateService.call(
  id: '',
  data: {
    requests: {
      request_name: 'Name Updated',
      actions: [{
        action_id: '',
        action_type: 'SIGN',
        recipient_email: 'stan.marsh@example.com',
        recipient_name: 'Stan Marsh'
      }]
    }
  }
)

Delete a document

# https://www.zoho.com/sign/api/document-managment/delete-document.html

ZohoSign::Document::DeleteService.call(id: '')

Sign a document

A unique signing URL will be generated, which will be valid for two minutes. If the signing page is not open by then, a new link needs to be generated and it is a one-time usable URL.

# https://www.zoho.com/sign/api/embedded-signing.html

service = ZohoSign::Document::Action::EmbedToken::GetService.call(request_id: '', action_id: '', host: '')
service.sign_url
Folders

Folders

TODO: ...

Field Types

Field Types

TODO: ...

Request Types

Request Types

TODO: ...

Templates

Templates

List templates

# https://www.zoho.com/sign/api/template-managment/get-template-list.html

ZohoSign::Template::ListService.call(offset: 1, limit: 10).each do |facade|
  facade.description
end

Sort by column.

ZohoSign::Template::ListService.call(sort_column: 'template_name', sort_order: 'ASC').map { _1 }

Filter by column.

ZohoSign::Template::ListService.call(search_columns: { template_name: 'Eric Template' }).map { _1 }

Columns to sort and filter by are template_name, owner_first_name and modified_time.

Get a template

# https://www.zoho.com/sign/api/template-managment/get-template-details.html

service = ZohoSign::Template::GetService.call(id: '')
service.description
service.document_fields
service.email_reminders
service.expiration_days
service.folder_name
service.folder_id
service.owner_email
service.template_name
service.request_status
...

Create a template

TODO: ...

Update a template

TODO: ...

Delete a template

TODO: ...

Create document from template

The auto filled fields specified in the field_data object should be marked as Prefill by you when creating the document template.

# https://www.zoho.com/sign/api/template-managment/send-documents-using-template.html

ZohoSign::Template::Document::CreateService.call!(
  id: '',
  is_quicksend: true,
  data: {
    templates: {
      request_name: 'Request Document',
      field_data: {
        field_text_data: {
          'Full name' => 'Eric Cartman',
          'Email' => 'eric.cartman@example.com'
        },
        field_boolean_data: {
          'Agreed to terms' => true
        },
        field_date_data: {
          'Inception date' => '31/01/2025'
        }
      },
      actions: [{
        action_type: 'SIGN',
        recipient_email: 'eric.cartman@example.com',
        recipient_name: 'Eric Cartman',
        verify_recipient: false,
        delivery_mode: 'EMAIL',
        action_id: '',
        role: 'Client'
      }]
    }
  }
)

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/kobusjoubert/zoho_sign.

License

The gem is available as open source under the terms of the MIT License.