sift 1.1.5 → 1.1.6.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 +9 -9
- data/README.rdoc +52 -7
- data/lib/sift.rb +7 -2
- data/lib/sift/client.rb +49 -17
- data/lib/sift/version.rb +2 -1
- data/sift.gemspec +5 -6
- data/spec/unit/client_spec.rb +67 -16
- metadata +11 -24
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
YzY5Mjg2YTQ4ZjRmYWRlOTQ4NTk4NzFmNjNiZjFmYmE3NTg3MDM2MQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
7
|
-
|
6
|
+
ZGIzOTAwOWE4OTExMWQ0MzlkYTRiYjk4OTQzNmU0NzQ5YWVkMzg3OQ==
|
7
|
+
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
NTM1ZTM3ZGU3ODI4ZmE5YzdiMTQ4ZWMxODUxZDhjZTAzZmZiYTAzMGZjMzZk
|
10
|
+
YzYxZjI5MDk1MjQ4ZWI1NDFhNWI1ODY2Y2MyYjk1MTI3YTA4ZjdmZjhjOTNl
|
11
|
+
NTc0ZGYzYzM5NmI0N2ZiM2JmMzgzMDg5M2ZhZWIwYjQwNWY1MGQ=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
NzlhNGRiODU4M2UzNWQzZGRkM2U2OGM4MTk5MWVkZmE4ZjQ1YmJkYzcxZjI2
|
14
|
+
OWY1OGI5OWRjMzA4ODM1YWVkZGE4MGVhMzBhMjRkNDg3M2ZlMzNhY2M0YTQ4
|
15
|
+
OWJkNjkwN2EzNzIyOTRjMGNhOGNjNzIyMWYzNDdmN2Y3OWQ4ZTE=
|
data/README.rdoc
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
= Sift Science Ruby bindings {<img src="https://travis-ci.org/SiftScience/sift-ruby.png?branch=master" alt="Build Status" />}[https://travis-ci.org/SiftScience/sift-ruby]
|
2
2
|
|
3
|
+
== Requirements
|
4
|
+
|
5
|
+
* Ruby 1.8.7 or above. (Ruby 1.8.6 might work if you load ActiveSupport.)
|
6
|
+
* HTTParty, 0.11.0 or greater
|
7
|
+
* Multi Json, 1.0 or greater
|
8
|
+
|
9
|
+
For development only:
|
10
|
+
* bundler
|
11
|
+
* rspec, 2.14.1 or greater
|
12
|
+
* webmock, 1.16 or greater
|
13
|
+
* rake, any version
|
14
|
+
|
3
15
|
== Installation
|
4
16
|
|
5
17
|
If you want to build the gem from source:
|
@@ -9,16 +21,49 @@ If you want to build the gem from source:
|
|
9
21
|
Alternatively, you can install the gem from Rubyforge:
|
10
22
|
|
11
23
|
$ gem install sift
|
24
|
+
|
25
|
+
== Usage
|
26
|
+
require "sift"
|
12
27
|
|
13
|
-
|
28
|
+
Sift.api_key = '<your_api_key_here>'
|
29
|
+
client = Sift::Client.new()
|
30
|
+
|
31
|
+
# send a transaction event -- note this is blocking
|
32
|
+
event = "$transaction"
|
33
|
+
|
34
|
+
user_id = "23056" # User ID's may only contain a-z, A-Z, 0-9, =, ., -, _, +, @, :, &, ^, %, !, $
|
14
35
|
|
15
|
-
|
16
|
-
|
36
|
+
properties = {
|
37
|
+
"$user_id" => user_id,
|
38
|
+
"$user_email" => "buyer@gmail.com",
|
39
|
+
"$seller_user_id" => "2371",
|
40
|
+
"seller_user_email" => "seller@gmail.com",
|
41
|
+
"$transaction_id" => "573050",
|
42
|
+
"$payment_method" => {
|
43
|
+
"$payment_type" => "$credit_card",
|
44
|
+
"$payment_gateway" => "$braintree",
|
45
|
+
"$card_bin" => "542486",
|
46
|
+
"$card_last4" => "4444"
|
47
|
+
},
|
48
|
+
"$currency_code" => "USD",
|
49
|
+
"$amount" => 15230000,
|
50
|
+
}
|
51
|
+
|
52
|
+
response = client.track(event, properties)
|
53
|
+
|
54
|
+
response.ok? # returns true or false
|
55
|
+
|
56
|
+
response.http_status_code # HTTP response code, 200 is ok.
|
17
57
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
58
|
+
response.api_status # status field in the return body, Link to Error Codes
|
59
|
+
|
60
|
+
response.api_error_message # Error message associated with status Error Code
|
61
|
+
|
62
|
+
# Request a score forthe user with user_id 23056
|
63
|
+
response = client.score(user_id)
|
64
|
+
|
65
|
+
# Label the user with user_id 23056 as Bad with all optional fields
|
66
|
+
response = client.label(user_id,{ "$is_bad" => true, "$reasons" => ["$chargeback", ], "$description" => "Chargeback issued", "$source" => "Manual Review", "$analyst" => "analyst.name@your_domain.com"})
|
22
67
|
|
23
68
|
== Building
|
24
69
|
|
data/lib/sift.rb
CHANGED
@@ -5,12 +5,17 @@ module Sift
|
|
5
5
|
|
6
6
|
# Returns the path for the current API version
|
7
7
|
def self.current_rest_api_path
|
8
|
-
"/
|
8
|
+
"/v#{API_VERSION}/events"
|
9
9
|
end
|
10
10
|
|
11
11
|
def self.current_users_label_api_path(user_id)
|
12
12
|
# This API version is a minor version ahead of the /events API
|
13
|
-
"/
|
13
|
+
"/v#{API_VERSION}/users/#{URI.encode(user_id)}/labels"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Adding module scoped public API key
|
17
|
+
class << self
|
18
|
+
attr_accessor :api_key
|
14
19
|
end
|
15
20
|
|
16
21
|
# Sets the Output logger to use within the client. This can be left uninitializaed
|
data/lib/sift/client.rb
CHANGED
@@ -6,11 +6,11 @@ module Sift
|
|
6
6
|
# Represents the payload returned from a call through the track API
|
7
7
|
#
|
8
8
|
class Response
|
9
|
-
attr_reader :
|
9
|
+
attr_reader :body
|
10
10
|
attr_reader :http_status_code
|
11
11
|
attr_reader :api_status
|
12
12
|
attr_reader :api_error_message
|
13
|
-
attr_reader :
|
13
|
+
attr_reader :request
|
14
14
|
|
15
15
|
# Constructor
|
16
16
|
#
|
@@ -21,11 +21,11 @@ module Sift
|
|
21
21
|
# sections.
|
22
22
|
#
|
23
23
|
def initialize(http_response, http_response_code)
|
24
|
-
@
|
25
|
-
@
|
24
|
+
@body = MultiJson.load(http_response)
|
25
|
+
@request = MultiJson.load(@body["request"].to_s) if @body["request"]
|
26
26
|
@http_status_code = http_response_code
|
27
|
-
@api_status = @
|
28
|
-
@api_error_message = @
|
27
|
+
@api_status = @body["status"].to_i
|
28
|
+
@api_error_message = @body["error_message"].to_s
|
29
29
|
end
|
30
30
|
|
31
31
|
# Helper method returns true if and only if the response from the API call was
|
@@ -36,6 +36,19 @@ module Sift
|
|
36
36
|
def ok?
|
37
37
|
0 == @api_status.to_i
|
38
38
|
end
|
39
|
+
|
40
|
+
|
41
|
+
# DEPRECIATED
|
42
|
+
# Getter method for depreciated 'json' member variable.
|
43
|
+
def json
|
44
|
+
@body
|
45
|
+
end
|
46
|
+
|
47
|
+
# DEPRECIATED
|
48
|
+
# Getter method for depreciated 'original_request' member variable.
|
49
|
+
def original_request
|
50
|
+
@request
|
51
|
+
end
|
39
52
|
end
|
40
53
|
|
41
54
|
# This class wraps accesses through the API
|
@@ -46,7 +59,6 @@ module Sift
|
|
46
59
|
|
47
60
|
include HTTParty
|
48
61
|
base_uri API_ENDPOINT
|
49
|
-
default_timeout API_TIMEOUT
|
50
62
|
|
51
63
|
# Constructor
|
52
64
|
#
|
@@ -57,10 +69,22 @@ module Sift
|
|
57
69
|
# path
|
58
70
|
# The path to the event API, e.g., "/v201/events"
|
59
71
|
#
|
60
|
-
def initialize(api_key, path = Sift.current_rest_api_path)
|
61
|
-
|
72
|
+
def initialize(api_key = Sift.api_key, path = Sift.current_rest_api_path, timeout = API_TIMEOUT)
|
73
|
+
raise(RuntimeError, "api_key must be a non-empty string") if (!api_key.is_a? String) || api_key.empty?
|
74
|
+
raise(RuntimeError, "path must be a non-empty string") if (!path.is_a? String) || path.empty?
|
75
|
+
@api_key = api_key
|
62
76
|
@path = path
|
63
|
-
|
77
|
+
@timeout = timeout
|
78
|
+
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
def api_key
|
83
|
+
@api_key
|
84
|
+
end
|
85
|
+
|
86
|
+
def user_agent
|
87
|
+
"SiftScience/v#{API_VERSION} sift-ruby/#{VERSION}"
|
64
88
|
end
|
65
89
|
|
66
90
|
# Tracks an event and associated properties through the Sift Science API. This call
|
@@ -94,13 +118,17 @@ module Sift
|
|
94
118
|
# result, though.
|
95
119
|
#
|
96
120
|
def track(event, properties = {}, timeout = nil, path = nil, return_score = false)
|
97
|
-
raise(RuntimeError, "event must be a string") if event.
|
121
|
+
raise(RuntimeError, "event must be a non-empty string") if (!event.is_a? String) || event.empty?
|
98
122
|
raise(RuntimeError, "properties cannot be empty") if properties.empty?
|
99
123
|
path ||= @path
|
100
|
-
|
124
|
+
timeout ||= @timeout
|
125
|
+
if return_score
|
126
|
+
path = path + "?return_score=true"
|
127
|
+
end
|
101
128
|
options = {
|
102
129
|
:body => MultiJson.dump(delete_nils(properties).merge({"$type" => event,
|
103
|
-
"$api_key" => @api_key}))
|
130
|
+
"$api_key" => @api_key})),
|
131
|
+
:headers => {"User-Agent" => user_agent}
|
104
132
|
}
|
105
133
|
options.merge!(:timeout => timeout) unless timeout.nil?
|
106
134
|
begin
|
@@ -125,11 +153,15 @@ module Sift
|
|
125
153
|
# A Response object is returned and captures the status message and
|
126
154
|
# status code. In general, you can ignore the returned result, though.
|
127
155
|
#
|
128
|
-
def score(user_id)
|
156
|
+
def score(user_id, timeout = nil)
|
157
|
+
|
158
|
+
raise(RuntimeError, "user_id must be a non-empty string") if (!user_id.is_a? String) || user_id.to_s.empty?
|
159
|
+
timetout ||= @timeout
|
129
160
|
|
130
|
-
|
161
|
+
options = { :headers => {"User-Agent" => user_agent} }
|
162
|
+
options.merge!(:timeout => timeout) unless timeout.nil?
|
131
163
|
|
132
|
-
response = self.class.get("/
|
164
|
+
response = self.class.get("/v#{API_VERSION}/score/#{user_id}/?api_key=#{@api_key}", options)
|
133
165
|
Response.new(response.body, response.code)
|
134
166
|
|
135
167
|
end
|
@@ -155,7 +187,7 @@ module Sift
|
|
155
187
|
#
|
156
188
|
def label(user_id, properties = {}, timeout = nil)
|
157
189
|
|
158
|
-
raise(RuntimeError, "user_id must be a string") if user_id.
|
190
|
+
raise(RuntimeError, "user_id must be a non-empty string") if (!user_id.is_a? String) || user_id.to_s.empty?
|
159
191
|
|
160
192
|
path = Sift.current_users_label_api_path(user_id)
|
161
193
|
track("$label", delete_nils(properties), timeout, path)
|
data/lib/sift/version.rb
CHANGED
data/sift.gemspec
CHANGED
@@ -6,8 +6,8 @@ Gem::Specification.new do |s|
|
|
6
6
|
s.name = "sift"
|
7
7
|
s.version = Sift::VERSION
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
|
-
s.authors = ["Fred Sadaghiani"]
|
10
|
-
s.email = ["
|
9
|
+
s.authors = ["Fred Sadaghiani", "Yoav Schatzberg"]
|
10
|
+
s.email = ["support@siftscience.com"]
|
11
11
|
s.homepage = "http://siftscience.com"
|
12
12
|
s.summary = %q{Sift Science Ruby API Gem}
|
13
13
|
s.description = %q{Sift Science Ruby API. Please see http://siftscience.com for more details.}
|
@@ -20,13 +20,12 @@ Gem::Specification.new do |s|
|
|
20
20
|
s.require_paths = ["lib"]
|
21
21
|
|
22
22
|
# Gems that must be intalled for sift to compile and build
|
23
|
-
s.add_development_dependency "rspec", "
|
24
|
-
s.add_development_dependency "webmock", "
|
23
|
+
s.add_development_dependency "rspec", ">=2.14.1"
|
24
|
+
s.add_development_dependency "webmock", ">= 1.16.0"
|
25
25
|
|
26
26
|
# Gems that must be intalled for sift to work
|
27
27
|
s.add_dependency "httparty", ">= 0.11.0"
|
28
|
-
s.add_dependency "multi_json", "
|
28
|
+
s.add_dependency "multi_json", ">= 1.0"
|
29
29
|
|
30
|
-
s.add_development_dependency("rspec", ">= 2.0")
|
31
30
|
s.add_development_dependency("rake")
|
32
31
|
end
|
data/spec/unit/client_spec.rb
CHANGED
@@ -2,6 +2,10 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
|
|
2
2
|
|
3
3
|
describe Sift::Client do
|
4
4
|
|
5
|
+
before :each do
|
6
|
+
Sift.api_key = nil
|
7
|
+
end
|
8
|
+
|
5
9
|
def valid_transaction_properties
|
6
10
|
{
|
7
11
|
:$buyer_user_id => "123456",
|
@@ -22,13 +26,53 @@ describe Sift::Client do
|
|
22
26
|
}
|
23
27
|
end
|
24
28
|
|
29
|
+
def score_response_json
|
30
|
+
{
|
31
|
+
:user_id => "247019",
|
32
|
+
:score => 0.93,
|
33
|
+
:reasons => [{
|
34
|
+
:name => "UsersPerDevice",
|
35
|
+
:value => 4,
|
36
|
+
:details => {
|
37
|
+
:users => "a, b, c, d"
|
38
|
+
}
|
39
|
+
}],
|
40
|
+
:status => 0,
|
41
|
+
:error_message => "OK"
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
25
45
|
def fully_qualified_api_endpoint
|
26
46
|
Sift::Client::API_ENDPOINT + Sift.current_rest_api_path
|
27
47
|
end
|
28
48
|
|
29
|
-
it "
|
49
|
+
it "Can instantiate client with blank api key if Sift.api_key set" do
|
50
|
+
Sift.api_key = "test_global_api_key"
|
51
|
+
Sift::Client.new().api_key.should eq(Sift.api_key)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "Parameter passed api key takes precedence over Sift.api_key" do
|
55
|
+
Sift.api_key = "test_global_api_key"
|
56
|
+
api_key = "test_local_api_key"
|
57
|
+
Sift::Client.new(api_key).api_key.should eq(api_key)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "Cannot instantiate client with nil, empty, non-string, or blank api key" do
|
30
61
|
lambda { Sift::Client.new(nil) }.should raise_error
|
31
62
|
lambda { Sift::Client.new("") }.should raise_error
|
63
|
+
lambda { Sift::Client.new(123456) }.should raise_error
|
64
|
+
lambda { Sift::Client.new() }.should raise_error
|
65
|
+
end
|
66
|
+
|
67
|
+
it "Cannot instantiate client with nil, empty, non-string, or blank path" do
|
68
|
+
api_key = "test_local_api_key"
|
69
|
+
lambda { Sift::Client.new(api_key, nil) }.should raise_error
|
70
|
+
lambda { Sift::Client.new(api_key, "") }.should raise_error
|
71
|
+
lambda { Sift::Client.new(api_key, 123456) }.should raise_error
|
72
|
+
end
|
73
|
+
|
74
|
+
it "Can instantiate client with non-default timeout" do
|
75
|
+
lambda { Sift::Client.new("test_local_api_key", Sift.current_rest_api_path, 4) }.should_not raise_error
|
32
76
|
end
|
33
77
|
|
34
78
|
it "Track call must specify an event name" do
|
@@ -126,31 +170,38 @@ describe Sift::Client do
|
|
126
170
|
it "Successfully fetches a score" do
|
127
171
|
|
128
172
|
api_key = "foobar"
|
129
|
-
|
173
|
+
response_json = score_response_json
|
130
174
|
|
175
|
+
stub_request(:get, "https://api.siftscience.com/v203/score/247019/?api_key=foobar").
|
176
|
+
to_return(:status => 200, :body => MultiJson.dump(response_json), :headers => {})
|
177
|
+
|
178
|
+
response = Sift::Client.new(api_key).score(score_response_json[:user_id])
|
179
|
+
response.ok?.should eq(true)
|
180
|
+
response.api_status.should eq(0)
|
181
|
+
response.api_error_message.should eq("OK")
|
182
|
+
|
183
|
+
response.body["score"].should eq(0.93)
|
184
|
+
end
|
185
|
+
|
186
|
+
it "Successfuly make a sync score request" do
|
187
|
+
|
188
|
+
api_key = "foobar"
|
131
189
|
response_json = {
|
132
|
-
:user_id => user_id,
|
133
|
-
:score => 0.93,
|
134
|
-
:reasons => [{
|
135
|
-
:name => "UsersPerDevice",
|
136
|
-
:value => 4,
|
137
|
-
:details => {
|
138
|
-
:users => "a, b, c, d"
|
139
|
-
}
|
140
|
-
}],
|
141
190
|
:status => 0,
|
142
|
-
:error_message => "OK"
|
191
|
+
:error_message => "OK",
|
192
|
+
:score_response => score_response_json
|
143
193
|
}
|
144
194
|
|
145
|
-
stub_request(:
|
195
|
+
stub_request(:post, "https://api.siftscience.com/v203/events?return_score=true").
|
146
196
|
to_return(:status => 200, :body => MultiJson.dump(response_json), :headers => {})
|
147
197
|
|
148
|
-
|
198
|
+
event = "$transaction"
|
199
|
+
properties = valid_transaction_properties
|
200
|
+
response = Sift::Client.new(api_key).track(event, properties, nil, nil, true)
|
149
201
|
response.ok?.should eq(true)
|
150
202
|
response.api_status.should eq(0)
|
151
203
|
response.api_error_message.should eq("OK")
|
152
|
-
|
153
|
-
response.json["score"].should eq(0.93)
|
204
|
+
response.body["score_response"]["score"].should eq(0.93)
|
154
205
|
end
|
155
206
|
|
156
207
|
end
|
metadata
CHANGED
@@ -1,41 +1,42 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sift
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Fred Sadaghiani
|
8
|
+
- Yoav Schatzberg
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2014-
|
12
|
+
date: 2014-09-03 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: rspec
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
16
17
|
requirements:
|
17
|
-
- -
|
18
|
+
- - ! '>='
|
18
19
|
- !ruby/object:Gem::Version
|
19
20
|
version: 2.14.1
|
20
21
|
type: :development
|
21
22
|
prerelease: false
|
22
23
|
version_requirements: !ruby/object:Gem::Requirement
|
23
24
|
requirements:
|
24
|
-
- -
|
25
|
+
- - ! '>='
|
25
26
|
- !ruby/object:Gem::Version
|
26
27
|
version: 2.14.1
|
27
28
|
- !ruby/object:Gem::Dependency
|
28
29
|
name: webmock
|
29
30
|
requirement: !ruby/object:Gem::Requirement
|
30
31
|
requirements:
|
31
|
-
- -
|
32
|
+
- - ! '>='
|
32
33
|
- !ruby/object:Gem::Version
|
33
34
|
version: 1.16.0
|
34
35
|
type: :development
|
35
36
|
prerelease: false
|
36
37
|
version_requirements: !ruby/object:Gem::Requirement
|
37
38
|
requirements:
|
38
|
-
- -
|
39
|
+
- - ! '>='
|
39
40
|
- !ruby/object:Gem::Version
|
40
41
|
version: 1.16.0
|
41
42
|
- !ruby/object:Gem::Dependency
|
@@ -56,30 +57,16 @@ dependencies:
|
|
56
57
|
name: multi_json
|
57
58
|
requirement: !ruby/object:Gem::Requirement
|
58
59
|
requirements:
|
59
|
-
- -
|
60
|
+
- - ! '>='
|
60
61
|
- !ruby/object:Gem::Version
|
61
62
|
version: '1.0'
|
62
63
|
type: :runtime
|
63
64
|
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ~>
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '1.0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: rspec
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - ! '>='
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '2.0'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
65
|
version_requirements: !ruby/object:Gem::Requirement
|
79
66
|
requirements:
|
80
67
|
- - ! '>='
|
81
68
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
69
|
+
version: '1.0'
|
83
70
|
- !ruby/object:Gem::Dependency
|
84
71
|
name: rake
|
85
72
|
requirement: !ruby/object:Gem::Requirement
|
@@ -96,7 +83,7 @@ dependencies:
|
|
96
83
|
version: '0'
|
97
84
|
description: Sift Science Ruby API. Please see http://siftscience.com for more details.
|
98
85
|
email:
|
99
|
-
-
|
86
|
+
- support@siftscience.com
|
100
87
|
executables: []
|
101
88
|
extensions: []
|
102
89
|
extra_rdoc_files: []
|
@@ -137,7 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
137
124
|
version: '0'
|
138
125
|
requirements: []
|
139
126
|
rubyforge_project: sift
|
140
|
-
rubygems_version: 2.
|
127
|
+
rubygems_version: 2.2.2
|
141
128
|
signing_key:
|
142
129
|
specification_version: 4
|
143
130
|
summary: Sift Science Ruby API Gem
|