Client for accessing the 4me REST API
Add this line to your application's Gemfile:
gem '4me-sdk'
And then execute:
$ bundle
Or install it yourself as:
$ gem install 4me-sdk
Sdk4me.configure do |config|
config.access_token = 'd41f5868feb65fc87fa2311a473a8766ea38bc40'
config.account = 'my-sandbox'
config.logger = Rails.logger
...
end
All options available:
- logger: The Ruby Logger instance, default:
Logger.new(STDOUT)
- host: The 4me REST API host, default: 'https://api.4me.com'
- api_version: The 4me REST API version, default: 'v1'
- access_token: (required) The 4me access token
- api_token: (deprecated) The 4me API token
- account: Specify a different account to work with
- source: The source used when creating new records
- user_agent: The User-Agent header of each request. Defaults to 4me-sdk-ruby/(version)
- max_retry_time: maximum nr of seconds to retry a request on a failed response (default = 300 = 5 minutes)
The sleep time between retries starts at 2 seconds and doubles after each retry, i.e. 2, 6, 18, 54, 162, 486, 1458, ... seconds.
Set to 0 to prevent retries. - read_timeout: HTTP read timeout in seconds (default = 25)
- block_at_rate_limit: Set to
true
to block the request until the rate limit is lifted, default:true
TheRetry-After
header is used to compute when the retry should be performed. If that moment is later than the max_throttle_time the request will not be blocked and the throttled response is returned. - max_throttle_time: maximum nr of seconds to retry a request on a rate limiter (default = 3660 = 1 hour and 1 minute)
- proxy_host: Define in case HTTP traffic needs to go through a proxy
- proxy_port: Port of the proxy, defaults to 8080
- proxy_user: Proxy user
- proxy_password: Proxy password
- ca_file: Certificate file (defaults to the provided ca-bundle.crt file from Mozilla)
Each time an 4me SDK Client is instantiated it is possible to override the global configuration like so:
client = Sdk4me::Client.new(account: 'trusted-sandbox', source: 'my special integration')
The proxy settings are limited to basic authentication only. In case ISA-NTLM authentication is required, make sure to setup a local proxy configured to forward the requests. And example local proxy host for Windows is Fiddle.
Minimal example:
require 'sdk4me/client'
client = Sdk4me::Client.new(access_token: '3a4e4590179263839...')
response = client.get('me')
puts response[:primary_email]
The get
method can be used to retrieve a single record from SDK4ME.
response = Sdk4me::Client.new.get('organizations/4321')
puts response[:name]
By default this call will return all fields of the Organization.
The fields can be accessed using symbols and strings, and it is possible chain a number of keys in one go:
response = Sdk4me::Client.new.get('organizations/4321')
puts response[:parent][:name] # this may throw an error when +parent+ is +nil+
puts response[:parent, :name] # using this format you will retrieve +nil+ when +parent+ is +nil+
puts response['parent', 'name'] # strings are also accepted as keys
Although the get
method can be also used to retrieve a collection of records from SDK4ME, the preferred way is to use the each
method.
count = Sdk4me::Client.new.each('organizations') do |organization|
puts organization[:name]
end
puts "Found #{count} organizations"
By default this call will return all collection fields for each Organization. For more fields, check out the field selection documentation.
The fields can be accessed using symbols and strings, and it is possible chain a number of keys in one go:
count = Sdk4me::Client.new.each('organizations', fields: 'parent') do |organization|
puts organization[:parent][:name] # this may throw an error when +parent+ is +nil+
puts organization[:parent, :name] # using this format you will retrieve +nil+ when +parent+ is +nil+
puts organization['parent', 'name'] # strings are also accepted as keys
end
Note that an Sdk4me::Exception
could be thrown in case one of the API requests fails. When using the blocking options the chances of this happening are rather small and you may decide not to explicitly catch the Sdk4me::Exception
here, but leave it up to a generic exception handler.
The each
method described above is the preferred way to work with collections of data.
If you really want to paginate yourself, the get
method is your friend.
@client = Sdk4me::Client.new
response = @client.get('organizations', { per_page: 100 })
puts response.json # all data in an array
puts "showing page #{response.current_page}/#{response.total_pages}, with #{response.per_page} records per page"
puts "total number of records #{response.total_entries}"
# retrieve collection for previous and next pages directly from the response
prev_page = @client.get(response.pagination_relative_link(:prev))
next_page = @client.get(response.pagination_relative_link(:next))
By default this call will return all collection fields for each Organization. For more fields, check out the field selection documentation.
The fields can be accessed using symbols and strings, and it is possible chain a number of keys in one go:
response = Sdk4me::Client.new.get('organizations', { per_page: 100, fields: 'parent' })
puts response[:parent, :name] # an array with the parent organization names
puts response['parent', 'name'] # strings are also accepted as keys
Creating new records is done using the post
method.
response = Sdk4me::Client.new.post('people', {primary_email: '[email protected]', organization_id: 777})
if response.valid?
puts "New person created with id #{response[:id]}"
else
puts response.message
end
Make sure to validate the success by calling response.valid?
and to take appropriate action in case the response is not valid.
Updating records is done using the put
method.
response = Sdk4me::Client.new.put('people/888', {name: 'Mrs. Susan Smith', organization_id: 777})
if response.valid?
puts "Person with id #{response[:id]} successfully updated"
else
puts response.message
end
Make sure to validate the success by calling response.valid?
and to take appropriate action in case the response is not valid.
Deleting records is done using the delete
method.
response = Sdk4me::Client.new.delete('organizations/88/addresses/')
if response.valid?
puts "Addresses of Organization with id #{response[:id]} successfully removed"
else
puts response.message
end
Make sure to validate the success by calling response.valid?
and to take appropriate action in case the response is not valid.
Adding attachments and inline images to rich text fields is a two-step process. First upload the attachments to 4me, and then refer to the uploaded attachments when creating or updating a 4me record.
To make this process easy, refer to the files using string filepaths or File references:
response = Sdk4me::Client.new.put('requests/416621', {
status: 'waiting_for_customer',
note: 'Please complete the attached forms and assign the request back to us.',
note_attachments: [
'/tmp/forms/README.txt',
File.open('/tmp/forms/PersonalData.xls', 'rb')
]
})
It is also possible to add inline attachments. Refer to attachments by their array index, with the index being zero-based. Text can only refer to inline images in its own attachments collection. For example:
begin
data = {
status: 'waiting_for_customer',
note: 'See [note_attachments: 0] and [note_attachments: 1]',
note_attachments: ['/tmp/info.png', '/tmp/help.png']
}
response = Sdk4me::Client.new.put('requests/416621', data)
if response.valid?
puts "Request #{response[:id]} updated and attachments added to the note"
else
puts "Update of request failed: #{response.message}"
end
catch Sdk4me::UploadFailed => ex
puts "Could not upload an attachment: #{ex.message}"
end
4me also provides an Import API. The 4me SDK Client can be used to upload files to that API.
response = Sdk4me::Client.new.import('\tmp\people.csv', 'people')
if response.valid?
puts "Import queued with token #{response[:token]}"
else
puts "Import upload failed: #{response.message}"
end
The second argument contains the import type.
It is also possible to monitor the progress of the import and block until the import is complete. In that case you will need to add some exception handling to your code.
begin
response = Sdk4me::Client.new.import('\tmp\people.csv', 'people', true)
puts response[:state]
puts response[:results]
puts response[:logfile]
unless response.valid?
puts "Import completed with errors: #{response[:message]}"
end
catch Sdk4me::UploadFailed => ex
puts "Could not upload the people import file: #{ex.message}"
catch Sdk4me::Exception => ex
puts "Unable to monitor progress of the people import: #{ex.message}"
end
Note that blocking for the import to finish is required when you import multiple CSVs that are dependent on each other.
4me also provides an Export API. The 4me SDK Client can be used to download (zipped) CSV files using that API.
response = Sdk4me::Client.new.export(['people', 'people_contact_details'], DateTime.new(2012,03,30,23,00,00))
if response.valid?
puts "Export queued with token #{response[:token]}"
else
puts "Export failed: #{response.message}"
end
The first argument contains the export types. The second argument is optional and limits the export to all changed records since the given time.
It is also possible to monitor the progress of the export and block until the export is complete. In that case you will need to add some exception handling to your code.
require 'open-uri'
begin
response = Sdk4me::Client.new.export(['people', 'people_contact_details'], nil, true)
puts response[:state]
if response.valid?
# write the export file to disk
File.open('/tmp/export.zip', 'wb') { |f| f.write(open(response[:url]).read) }
else
puts "Export failed with errors: #{response[:message]}"
puts response[:logfile]
end
catch Sdk4me::UploadFailed => ex
puts "Could not queue the people export: #{ex.message}"
catch Sdk4me::Exception => ex
puts "Unable to monitor progress of the people export: #{ex.message}"
end
Note that blocking for the export to finish is recommended as you will get direct access to the exported file.
When the currently used API token hits the 4me rate limiter a HTTP 429 response is returned that specifies after how many seconds the rate limit will be lifted.
If that time lies within the max_throttle_time the 4me SDK Client will wait and retry the action, if not, the throttled response will be returned. You can verify if a response was throttled using:
response = client.get('me')
puts response.throttled?
By setting the block_at_rate_limit to false
in the configuration the 4me SDK Client will never wait when a rate limit is hit.
Note that 4me has different rate limiters. If the intention is to only wait when the short-burst (max 20 requests in 2 seconds) rate limiter is hit, you could set the max_throttle_time to e.g. 5 seconds.
When exporting translations, the locale parameter is required:
response = Itrp::Client.new.export('translations', nil, false, 'nl')
The standard methods get
, post
, put
and delete
will always return a Response with an error message in case something went wrong.
By calling response.valid?
you will know if the action succeeded or not, and response.message
provides additinal information in case the response was invalid.
response = Sdk4me::Client.new.get('organizations/1a2b')
puts response.valid?
puts response.message
The methods each
and import
may throw an Sdk4me::Exception
in case something failed, see the examples above.
The changelog is available here.
Copyright (c) 2022 4me, Inc. See LICENSE for further details.