bigshield 1.0.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +162 -0
- data/lib/bigshield/client.rb +139 -0
- data/lib/bigshield/errors.rb +63 -0
- data/lib/bigshield/http_client.rb +155 -0
- data/lib/bigshield/types.rb +378 -0
- data/lib/bigshield/version.rb +5 -0
- data/lib/bigshield.rb +23 -0
- metadata +70 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 38b2d82913a30fd6abedec5e0ab61866f92bbf8e33bf1d1fb788fd2af6f99eed
|
|
4
|
+
data.tar.gz: cdc34299a70002a370cf37400ddb59e03e595457633cccf592329219da195074
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b53386268e60d5e56eef855c6c34db0505b752616c4e89e1f81ec2dc8ece100821938066402dfd167d22bca8ce172cead24a6889eeaa72acc3416819c8ef3986
|
|
7
|
+
data.tar.gz: c32e3f9c4e46b6222418cb632795ad0a5f48d29e49ae44230bf605a63afa278d644f970ccc2e2d9f1ad00f77f4cdd94833bfa59ef6d9a148d496aca0ff2709b6
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 BigShield
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# BigShield Ruby SDK
|
|
2
|
+
|
|
3
|
+
Official Ruby SDK for the [BigShield](https://bigshield.app) email validation API. Detect fake signups, burner emails, and disposable domains with 30+ detection signals.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Ruby 3.0+
|
|
8
|
+
- net-http (stdlib)
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
gem install bigshield
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Or add to your Gemfile:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
gem "bigshield"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
require "bigshield"
|
|
26
|
+
|
|
27
|
+
client = BigShield::Client.new("ev_live_...")
|
|
28
|
+
result = client.validate("user@example.com")
|
|
29
|
+
|
|
30
|
+
puts result.risk_score # 0-100
|
|
31
|
+
puts result.fraud_decision # "allow" | "block" | "review" | "require_verification"
|
|
32
|
+
puts result.recommendation # "accept" | "review" | "reject"
|
|
33
|
+
|
|
34
|
+
raise "Please use a valid email address" if result.fraud_decision == "block"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Configuration
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
# Simple
|
|
41
|
+
client = BigShield::Client.new("ev_live_...")
|
|
42
|
+
|
|
43
|
+
# Advanced options
|
|
44
|
+
client = BigShield::Client.new(
|
|
45
|
+
"ev_live_...",
|
|
46
|
+
base_url: "https://bigshield.app", # default
|
|
47
|
+
timeout: 30, # request timeout in seconds
|
|
48
|
+
retries: 2 # retry on 5xx errors
|
|
49
|
+
)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Methods
|
|
53
|
+
|
|
54
|
+
### `validate(email, **options) -> ValidationResult`
|
|
55
|
+
|
|
56
|
+
Validate a single email address.
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
result = client.validate(
|
|
60
|
+
"user@example.com",
|
|
61
|
+
ip: request.remote_ip,
|
|
62
|
+
user_agent: request.user_agent,
|
|
63
|
+
fingerprint: client_fingerprint,
|
|
64
|
+
wait: true, # long-poll for async signals
|
|
65
|
+
webhook_url: "https://myapp.com/webhook",
|
|
66
|
+
metadata: { source: "signup" }
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
puts result.fraud_decision
|
|
70
|
+
puts result.fraud_flags # e.g. ["proxy_ip", "burner_domain"]
|
|
71
|
+
puts result.signals # individual signal results
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### `batch_validate(emails, **options) -> BatchValidateResponse`
|
|
75
|
+
|
|
76
|
+
Validate multiple emails in a single request. Max batch size depends on your plan.
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
batch = client.batch_validate(
|
|
80
|
+
["user1@gmail.com", "fake@tempmail.com", "real@company.org"],
|
|
81
|
+
ip: request.remote_ip
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
blocked = batch.results.select { |r| r.fraud_decision == "block" }
|
|
85
|
+
puts "Blocked #{blocked.size} of #{batch.total} emails"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### `get_validation(id) -> ValidationResult`
|
|
89
|
+
|
|
90
|
+
Retrieve a previous validation result by ID.
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
result = client.get_validation("val_a1b2c3d4")
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### `wait_for_completion(id, interval:, max_attempts:) -> ValidationResult`
|
|
97
|
+
|
|
98
|
+
Poll until async signals complete.
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
initial = client.validate("user@example.com")
|
|
102
|
+
|
|
103
|
+
if initial.status == "partial"
|
|
104
|
+
final = client.wait_for_completion(
|
|
105
|
+
initial.id,
|
|
106
|
+
interval: 1.0, # poll every 1s
|
|
107
|
+
max_attempts: 30 # give up after 30s
|
|
108
|
+
)
|
|
109
|
+
end
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### `get_usage -> UsageStats`
|
|
113
|
+
|
|
114
|
+
Get current usage statistics.
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
usage = client.get_usage
|
|
118
|
+
puts "#{usage.usage.total} / #{usage.usage.limit} validations used"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### `health -> HealthResponse`
|
|
122
|
+
|
|
123
|
+
Check API health status.
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
health = client.health
|
|
127
|
+
puts health.status
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### `register_webhook(url, events:) -> Hash`
|
|
131
|
+
|
|
132
|
+
Register a webhook URL for validation events.
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
client.register_webhook("https://myapp.com/webhook", events: ["validation.completed"])
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Error Handling
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
require "bigshield"
|
|
142
|
+
|
|
143
|
+
begin
|
|
144
|
+
result = client.validate("test@example.com")
|
|
145
|
+
rescue BigShield::AuthError
|
|
146
|
+
# Invalid or missing API key (401)
|
|
147
|
+
rescue BigShield::RateLimitError => e
|
|
148
|
+
# Rate limit exceeded (429)
|
|
149
|
+
puts "Retry after #{e.retry_after} seconds"
|
|
150
|
+
rescue BigShield::Error => e
|
|
151
|
+
# Other API error
|
|
152
|
+
puts "#{e.code} #{e.status} #{e.message}"
|
|
153
|
+
end
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Documentation
|
|
157
|
+
|
|
158
|
+
Full API documentation is available at [bigshield.app/docs](https://bigshield.app/docs).
|
|
159
|
+
|
|
160
|
+
## License
|
|
161
|
+
|
|
162
|
+
MIT
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BigShield
|
|
4
|
+
# Synchronous BigShield email validation client.
|
|
5
|
+
#
|
|
6
|
+
# @example Basic usage
|
|
7
|
+
# client = BigShield::Client.new("ev_live_abc123")
|
|
8
|
+
# result = client.validate("user@example.com")
|
|
9
|
+
# puts result.risk_score
|
|
10
|
+
# puts result.valid?
|
|
11
|
+
#
|
|
12
|
+
# @example Wait for all signals to complete
|
|
13
|
+
# result = client.validate("user@example.com", wait: true)
|
|
14
|
+
#
|
|
15
|
+
# @example Poll for completion
|
|
16
|
+
# result = client.validate("user@example.com")
|
|
17
|
+
# if result.status == "partial"
|
|
18
|
+
# result = client.wait_for_completion(result.id)
|
|
19
|
+
# end
|
|
20
|
+
class Client
|
|
21
|
+
# @param api_key [String] your BigShield API key (e.g. "ev_live_...")
|
|
22
|
+
# @param base_url [String] API base URL
|
|
23
|
+
# @param timeout [Integer] request timeout in seconds
|
|
24
|
+
# @param retries [Integer] number of retry attempts for 5xx / network errors
|
|
25
|
+
# @raise [ArgumentError] if the API key is empty or nil
|
|
26
|
+
def initialize(api_key, base_url: "https://www.bigshield.app", timeout: 30, retries: 2)
|
|
27
|
+
raise ArgumentError, "API key is required" if api_key.nil? || api_key.empty?
|
|
28
|
+
|
|
29
|
+
@http = HttpClient.new(
|
|
30
|
+
api_key: api_key,
|
|
31
|
+
base_url: base_url,
|
|
32
|
+
timeout: timeout,
|
|
33
|
+
retries: retries
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Validate a single email address.
|
|
38
|
+
#
|
|
39
|
+
# @param email [String] the email address to validate
|
|
40
|
+
# @param wait [Boolean] if true, long-poll until Tier 2 signals complete
|
|
41
|
+
# @param webhook_url [String, nil] URL to receive a callback when validation completes
|
|
42
|
+
# @param metadata [Hash, nil] custom metadata to attach to the validation
|
|
43
|
+
# @param ip [String, nil] client IP address for contextual signals
|
|
44
|
+
# @param fingerprint [String, nil] device fingerprint for multi-account detection
|
|
45
|
+
# @param user_agent [String, nil] browser user agent string
|
|
46
|
+
# @return [ValidationResult] validation result with risk score, signals, and recommendation
|
|
47
|
+
def validate(email, wait: false, webhook_url: nil, metadata: nil, ip: nil, fingerprint: nil, user_agent: nil)
|
|
48
|
+
body = { "email" => email }
|
|
49
|
+
body["wait"] = true if wait
|
|
50
|
+
body["webhook_url"] = webhook_url unless webhook_url.nil?
|
|
51
|
+
body["metadata"] = metadata unless metadata.nil?
|
|
52
|
+
body["ip"] = ip unless ip.nil?
|
|
53
|
+
body["fingerprint"] = fingerprint unless fingerprint.nil?
|
|
54
|
+
body["user_agent"] = user_agent unless user_agent.nil?
|
|
55
|
+
|
|
56
|
+
data = @http.request(:post, "/api/v1/validate", body: body)
|
|
57
|
+
ValidationResult.from_hash(data)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Retrieve a validation result by ID.
|
|
61
|
+
#
|
|
62
|
+
# Useful for polling Tier 2/3 signal completion after an initial
|
|
63
|
+
# {#validate} call returns with status +"partial"+.
|
|
64
|
+
#
|
|
65
|
+
# @param id [String] the validation ID
|
|
66
|
+
# @return [ValidationResult]
|
|
67
|
+
def get_validation(id)
|
|
68
|
+
data = @http.request(:get, "/api/v1/validate/#{id}")
|
|
69
|
+
ValidationResult.from_hash(data)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Validate multiple email addresses in a single request.
|
|
73
|
+
#
|
|
74
|
+
# Maximum batch size depends on your plan (5-100 emails).
|
|
75
|
+
#
|
|
76
|
+
# @param emails [Array<String>] list of email addresses to validate
|
|
77
|
+
# @param webhook_url [String, nil] URL to receive a callback when validation completes
|
|
78
|
+
# @param ip [String, nil] client IP address for contextual signals
|
|
79
|
+
# @param fingerprint [String, nil] device fingerprint for multi-account detection
|
|
80
|
+
# @param user_agent [String, nil] browser user agent string
|
|
81
|
+
# @return [BatchValidateResponse]
|
|
82
|
+
def batch_validate(emails, webhook_url: nil, ip: nil, fingerprint: nil, user_agent: nil)
|
|
83
|
+
body = { "emails" => emails }
|
|
84
|
+
body["webhook_url"] = webhook_url unless webhook_url.nil?
|
|
85
|
+
body["ip"] = ip unless ip.nil?
|
|
86
|
+
body["fingerprint"] = fingerprint unless fingerprint.nil?
|
|
87
|
+
body["user_agent"] = user_agent unless user_agent.nil?
|
|
88
|
+
|
|
89
|
+
data = @http.request(:post, "/api/v1/validate/batch", body: body)
|
|
90
|
+
BatchValidateResponse.from_hash(data)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Get usage statistics for the current API key.
|
|
94
|
+
#
|
|
95
|
+
# @return [UsageStats]
|
|
96
|
+
def get_usage
|
|
97
|
+
data = @http.request(:get, "/api/v1/usage")
|
|
98
|
+
UsageStats.from_hash(data)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Check API health status.
|
|
102
|
+
#
|
|
103
|
+
# @return [HealthResponse]
|
|
104
|
+
def health
|
|
105
|
+
data = @http.request(:get, "/api/v1/health")
|
|
106
|
+
HealthResponse.from_hash(data)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Register a webhook URL for validation completion events.
|
|
110
|
+
#
|
|
111
|
+
# @param url [String] the webhook endpoint URL
|
|
112
|
+
# @param events [Array<String>, nil] list of event types to subscribe to
|
|
113
|
+
# @return [Hash] registration confirmation from the API
|
|
114
|
+
def register_webhook(url, events: nil)
|
|
115
|
+
body = { "url" => url }
|
|
116
|
+
body["events"] = events unless events.nil?
|
|
117
|
+
|
|
118
|
+
@http.request(:post, "/api/v1/webhooks", body: body)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Poll until a validation reaches "completed" or "failed" status.
|
|
122
|
+
#
|
|
123
|
+
# @param id [String] the validation ID to poll
|
|
124
|
+
# @param interval [Float] seconds between polling attempts
|
|
125
|
+
# @param max_attempts [Integer] maximum number of polling attempts
|
|
126
|
+
# @return [ValidationResult] the final validation result
|
|
127
|
+
def wait_for_completion(id, interval: 1.0, max_attempts: 30)
|
|
128
|
+
max_attempts.times do
|
|
129
|
+
result = get_validation(id)
|
|
130
|
+
return result if %w[completed failed].include?(result.status)
|
|
131
|
+
|
|
132
|
+
sleep(interval)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Final attempt after exhausting retries
|
|
136
|
+
get_validation(id)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BigShield
|
|
4
|
+
# Base exception for all BigShield API errors.
|
|
5
|
+
#
|
|
6
|
+
# @example Catching any BigShield error
|
|
7
|
+
# begin
|
|
8
|
+
# client.validate("user@example.com")
|
|
9
|
+
# rescue BigShield::Error => e
|
|
10
|
+
# puts "#{e.code}: #{e.message} (HTTP #{e.status})"
|
|
11
|
+
# end
|
|
12
|
+
class Error < StandardError
|
|
13
|
+
# @return [String] machine-readable error code
|
|
14
|
+
attr_reader :code
|
|
15
|
+
|
|
16
|
+
# @return [Integer] HTTP status code (0 for network/timeout errors)
|
|
17
|
+
attr_reader :status
|
|
18
|
+
|
|
19
|
+
# @return [Hash] additional error details from the API
|
|
20
|
+
attr_reader :details
|
|
21
|
+
|
|
22
|
+
# @param message [String] human-readable error message
|
|
23
|
+
# @param code [String] machine-readable error code
|
|
24
|
+
# @param status [Integer] HTTP status code
|
|
25
|
+
# @param details [Hash, nil] additional error details
|
|
26
|
+
def initialize(message = "Unknown error", code: "UNKNOWN_ERROR", status: 0, details: nil)
|
|
27
|
+
super(message)
|
|
28
|
+
@code = code
|
|
29
|
+
@status = status
|
|
30
|
+
@details = details || {}
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Raised when the API key is invalid or missing (HTTP 401).
|
|
35
|
+
class AuthError < Error
|
|
36
|
+
# @param message [String] human-readable error message
|
|
37
|
+
def initialize(message = "Invalid or missing API key")
|
|
38
|
+
super(message, code: "UNAUTHORIZED", status: 401)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Raised when the rate limit is exceeded (HTTP 429).
|
|
43
|
+
class RateLimitError < Error
|
|
44
|
+
# @return [Integer] seconds to wait before retrying
|
|
45
|
+
attr_reader :retry_after
|
|
46
|
+
|
|
47
|
+
# @param message [String] human-readable error message
|
|
48
|
+
# @param retry_after [Integer] seconds to wait before retrying
|
|
49
|
+
def initialize(message = "Rate limit exceeded", retry_after: 60)
|
|
50
|
+
super(message, code: "RATE_LIMIT_EXCEEDED", status: 429)
|
|
51
|
+
@retry_after = retry_after
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Raised for invalid request parameters (HTTP 400).
|
|
56
|
+
class ValidationError < Error
|
|
57
|
+
# @param message [String] human-readable error message
|
|
58
|
+
# @param details [Hash, nil] field-level validation errors
|
|
59
|
+
def initialize(message = "Validation error", details: nil)
|
|
60
|
+
super(message, code: "VALIDATION_ERROR", status: 400, details: details)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "json"
|
|
6
|
+
|
|
7
|
+
module BigShield
|
|
8
|
+
# Internal HTTP client with retry logic and error handling.
|
|
9
|
+
#
|
|
10
|
+
# Uses Ruby's built-in +net/http+ -- no external dependencies required.
|
|
11
|
+
#
|
|
12
|
+
# @api private
|
|
13
|
+
class HttpClient
|
|
14
|
+
# @param api_key [String] BigShield API key
|
|
15
|
+
# @param base_url [String] API base URL
|
|
16
|
+
# @param timeout [Integer] request timeout in seconds
|
|
17
|
+
# @param retries [Integer] number of retry attempts for 5xx / network errors
|
|
18
|
+
def initialize(api_key:, base_url:, timeout:, retries:)
|
|
19
|
+
@api_key = api_key
|
|
20
|
+
@base_url = base_url.chomp("/")
|
|
21
|
+
@timeout = timeout
|
|
22
|
+
@retries = retries
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Perform an HTTP request with automatic retries and error handling.
|
|
26
|
+
#
|
|
27
|
+
# @param method [Symbol] HTTP method (+:get+, +:post+, etc.)
|
|
28
|
+
# @param path [String] API path (e.g. "/api/v1/validate")
|
|
29
|
+
# @param body [Hash, nil] request body (will be serialized to JSON)
|
|
30
|
+
# @return [Hash] parsed JSON response
|
|
31
|
+
# @raise [BigShield::Error] on API or network errors
|
|
32
|
+
def request(method, path, body: nil)
|
|
33
|
+
uri = URI("#{@base_url}#{path}")
|
|
34
|
+
last_error = nil
|
|
35
|
+
|
|
36
|
+
(@retries + 1).times do |attempt|
|
|
37
|
+
begin
|
|
38
|
+
response = execute_request(method, uri, body)
|
|
39
|
+
status = response.code.to_i
|
|
40
|
+
|
|
41
|
+
if status >= 200 && status < 300
|
|
42
|
+
return JSON.parse(response.body)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Don't retry client errors (except potentially retryable ones handled below)
|
|
46
|
+
if !should_retry?(status) || attempt == @retries
|
|
47
|
+
handle_error(response)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
last_error = Error.new("Server error (#{status})", code: "SERVER_ERROR", status: status)
|
|
51
|
+
rescue Error, AuthError, RateLimitError, ValidationError
|
|
52
|
+
raise
|
|
53
|
+
rescue Net::OpenTimeout, Net::ReadTimeout => e
|
|
54
|
+
last_error = e
|
|
55
|
+
if attempt == @retries
|
|
56
|
+
raise Error.new("Request timed out", code: "TIMEOUT", status: 0)
|
|
57
|
+
end
|
|
58
|
+
rescue StandardError => e
|
|
59
|
+
last_error = e
|
|
60
|
+
if attempt == @retries
|
|
61
|
+
raise Error.new("Network error: #{e.message}", code: "NETWORK_ERROR", status: 0)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Exponential backoff: 0.5s, 1s, 2s, ...
|
|
66
|
+
sleep(0.5 * (2**attempt))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
raise last_error
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
# Build and send the HTTP request.
|
|
75
|
+
#
|
|
76
|
+
# @param method [Symbol]
|
|
77
|
+
# @param uri [URI]
|
|
78
|
+
# @param body [Hash, nil]
|
|
79
|
+
# @return [Net::HTTPResponse]
|
|
80
|
+
def execute_request(method, uri, body)
|
|
81
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
82
|
+
http.use_ssl = (uri.scheme == "https")
|
|
83
|
+
http.open_timeout = @timeout
|
|
84
|
+
http.read_timeout = @timeout
|
|
85
|
+
|
|
86
|
+
request = build_request(method, uri, body)
|
|
87
|
+
http.request(request)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Build the Net::HTTP request object.
|
|
91
|
+
#
|
|
92
|
+
# @param method [Symbol]
|
|
93
|
+
# @param uri [URI]
|
|
94
|
+
# @param body [Hash, nil]
|
|
95
|
+
# @return [Net::HTTPRequest]
|
|
96
|
+
def build_request(method, uri, body)
|
|
97
|
+
klass = case method
|
|
98
|
+
when :get then Net::HTTP::Get
|
|
99
|
+
when :post then Net::HTTP::Post
|
|
100
|
+
when :put then Net::HTTP::Put
|
|
101
|
+
when :delete then Net::HTTP::Delete
|
|
102
|
+
else raise ArgumentError, "Unsupported HTTP method: #{method}"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
req = klass.new(uri)
|
|
106
|
+
req["Content-Type"] = "application/json"
|
|
107
|
+
req["Authorization"] = "Bearer #{@api_key}"
|
|
108
|
+
req.body = JSON.generate(body) if body
|
|
109
|
+
req
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Determine whether a response status code is retryable.
|
|
113
|
+
#
|
|
114
|
+
# @param status [Integer]
|
|
115
|
+
# @return [Boolean]
|
|
116
|
+
def should_retry?(status)
|
|
117
|
+
status >= 500
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Parse an error response and raise the appropriate error class.
|
|
121
|
+
#
|
|
122
|
+
# @param response [Net::HTTPResponse]
|
|
123
|
+
# @raise [BigShield::AuthError] on 401
|
|
124
|
+
# @raise [BigShield::RateLimitError] on 429
|
|
125
|
+
# @raise [BigShield::ValidationError] on 400
|
|
126
|
+
# @raise [BigShield::Error] on all other error statuses
|
|
127
|
+
def handle_error(response)
|
|
128
|
+
status = response.code.to_i
|
|
129
|
+
body = begin
|
|
130
|
+
JSON.parse(response.body)
|
|
131
|
+
rescue StandardError
|
|
132
|
+
{}
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
message = body["error"] || body["message"] || response.body
|
|
136
|
+
|
|
137
|
+
case status
|
|
138
|
+
when 401
|
|
139
|
+
raise AuthError.new(message || "Invalid or missing API key")
|
|
140
|
+
when 429
|
|
141
|
+
retry_after = (response["Retry-After"] || "60").to_i
|
|
142
|
+
raise RateLimitError.new(message || "Rate limit exceeded", retry_after: retry_after)
|
|
143
|
+
when 400
|
|
144
|
+
raise ValidationError.new(message || "Validation error", details: body["details"])
|
|
145
|
+
else
|
|
146
|
+
raise Error.new(
|
|
147
|
+
message || "API error (#{status})",
|
|
148
|
+
code: body["code"] || "API_ERROR",
|
|
149
|
+
status: status,
|
|
150
|
+
details: body["details"]
|
|
151
|
+
)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BigShield
|
|
4
|
+
# Result for a single validation signal.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# signal = result.signals.first
|
|
8
|
+
# puts "#{signal.name}: impact=#{signal.score_impact}, confidence=#{signal.confidence}"
|
|
9
|
+
class SignalResult
|
|
10
|
+
# @return [String] signal name
|
|
11
|
+
attr_reader :name
|
|
12
|
+
|
|
13
|
+
# @return [String] signal tier (e.g. "tier1", "tier2")
|
|
14
|
+
attr_reader :tier
|
|
15
|
+
|
|
16
|
+
# @return [Float] impact on the overall risk score
|
|
17
|
+
attr_reader :score_impact
|
|
18
|
+
|
|
19
|
+
# @return [Float] confidence level (0.0 to 1.0)
|
|
20
|
+
attr_reader :confidence
|
|
21
|
+
|
|
22
|
+
# @return [String] human-readable description
|
|
23
|
+
attr_reader :description
|
|
24
|
+
|
|
25
|
+
# @return [String] ISO 8601 timestamp when the signal was executed
|
|
26
|
+
attr_reader :executed_at
|
|
27
|
+
|
|
28
|
+
# @return [Float] execution time in milliseconds
|
|
29
|
+
attr_reader :duration_ms
|
|
30
|
+
|
|
31
|
+
# @return [Hash] additional signal-specific details
|
|
32
|
+
attr_reader :details
|
|
33
|
+
|
|
34
|
+
# @param name [String]
|
|
35
|
+
# @param tier [String]
|
|
36
|
+
# @param score_impact [Float]
|
|
37
|
+
# @param confidence [Float]
|
|
38
|
+
# @param description [String]
|
|
39
|
+
# @param executed_at [String]
|
|
40
|
+
# @param duration_ms [Float]
|
|
41
|
+
# @param details [Hash]
|
|
42
|
+
def initialize(name:, tier:, score_impact:, confidence:, description:, executed_at:, duration_ms:, details: {})
|
|
43
|
+
@name = name
|
|
44
|
+
@tier = tier
|
|
45
|
+
@score_impact = score_impact
|
|
46
|
+
@confidence = confidence
|
|
47
|
+
@description = description
|
|
48
|
+
@executed_at = executed_at
|
|
49
|
+
@duration_ms = duration_ms
|
|
50
|
+
@details = details
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Build a SignalResult from an API response hash.
|
|
54
|
+
#
|
|
55
|
+
# @param data [Hash] raw hash from the API
|
|
56
|
+
# @return [SignalResult]
|
|
57
|
+
def self.from_hash(data)
|
|
58
|
+
new(
|
|
59
|
+
name: data["name"] || "",
|
|
60
|
+
tier: data["tier"] || "",
|
|
61
|
+
score_impact: data["score_impact"] || 0,
|
|
62
|
+
confidence: data["confidence"] || 0,
|
|
63
|
+
description: data["description"] || "",
|
|
64
|
+
executed_at: data["executed_at"] || "",
|
|
65
|
+
duration_ms: data["duration_ms"] || 0,
|
|
66
|
+
details: data["details"] || {}
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# The result of an email validation request.
|
|
72
|
+
#
|
|
73
|
+
# @example
|
|
74
|
+
# result = client.validate("user@example.com")
|
|
75
|
+
# if result.valid?
|
|
76
|
+
# puts "Email looks good! Score: #{result.risk_score}"
|
|
77
|
+
# end
|
|
78
|
+
class ValidationResult
|
|
79
|
+
# @return [String] unique validation ID
|
|
80
|
+
attr_reader :id
|
|
81
|
+
|
|
82
|
+
# @return [String] the validated email address
|
|
83
|
+
attr_reader :email
|
|
84
|
+
|
|
85
|
+
# @return [String] validation status: "pending", "partial", "completed", or "failed"
|
|
86
|
+
attr_reader :status
|
|
87
|
+
|
|
88
|
+
# @return [Float] overall risk score (0-100)
|
|
89
|
+
attr_reader :risk_score
|
|
90
|
+
|
|
91
|
+
# @return [String] risk level: "very_low", "low", "medium", "high", or "very_high"
|
|
92
|
+
attr_reader :risk_level
|
|
93
|
+
|
|
94
|
+
# @return [String] recommendation: "accept", "review", or "reject"
|
|
95
|
+
attr_reader :recommendation
|
|
96
|
+
|
|
97
|
+
# @return [Array<SignalResult>] individual signal results
|
|
98
|
+
attr_reader :signals
|
|
99
|
+
|
|
100
|
+
# @return [String] ISO 8601 creation timestamp
|
|
101
|
+
attr_reader :created_at
|
|
102
|
+
|
|
103
|
+
# @return [String] ISO 8601 last-updated timestamp
|
|
104
|
+
attr_reader :updated_at
|
|
105
|
+
|
|
106
|
+
# @return [String, nil] fraud decision: "allow", "block", "require_verification", or "review"
|
|
107
|
+
attr_reader :fraud_decision
|
|
108
|
+
|
|
109
|
+
# @return [Array<String>, nil] list of fraud flag identifiers
|
|
110
|
+
attr_reader :fraud_flags
|
|
111
|
+
|
|
112
|
+
# @return [String, nil] ISO 8601 timestamp when tier 1 signals completed
|
|
113
|
+
attr_reader :tier1_completed_at
|
|
114
|
+
|
|
115
|
+
# @return [String, nil] ISO 8601 timestamp when tier 2 signals completed
|
|
116
|
+
attr_reader :tier2_completed_at
|
|
117
|
+
|
|
118
|
+
# @return [String, nil] ISO 8601 timestamp when tier 3 signals completed
|
|
119
|
+
attr_reader :tier3_completed_at
|
|
120
|
+
|
|
121
|
+
# @param id [String]
|
|
122
|
+
# @param email [String]
|
|
123
|
+
# @param status [String]
|
|
124
|
+
# @param risk_score [Float]
|
|
125
|
+
# @param risk_level [String]
|
|
126
|
+
# @param recommendation [String]
|
|
127
|
+
# @param signals [Array<SignalResult>]
|
|
128
|
+
# @param created_at [String]
|
|
129
|
+
# @param updated_at [String]
|
|
130
|
+
# @param fraud_decision [String, nil]
|
|
131
|
+
# @param fraud_flags [Array<String>, nil]
|
|
132
|
+
# @param tier1_completed_at [String, nil]
|
|
133
|
+
# @param tier2_completed_at [String, nil]
|
|
134
|
+
# @param tier3_completed_at [String, nil]
|
|
135
|
+
def initialize(id:, email:, status:, risk_score:, risk_level:, recommendation:, signals:,
|
|
136
|
+
created_at:, updated_at:, fraud_decision: nil, fraud_flags: nil,
|
|
137
|
+
tier1_completed_at: nil, tier2_completed_at: nil, tier3_completed_at: nil)
|
|
138
|
+
@id = id
|
|
139
|
+
@email = email
|
|
140
|
+
@status = status
|
|
141
|
+
@risk_score = risk_score
|
|
142
|
+
@risk_level = risk_level
|
|
143
|
+
@recommendation = recommendation
|
|
144
|
+
@signals = signals
|
|
145
|
+
@created_at = created_at
|
|
146
|
+
@updated_at = updated_at
|
|
147
|
+
@fraud_decision = fraud_decision
|
|
148
|
+
@fraud_flags = fraud_flags
|
|
149
|
+
@tier1_completed_at = tier1_completed_at
|
|
150
|
+
@tier2_completed_at = tier2_completed_at
|
|
151
|
+
@tier3_completed_at = tier3_completed_at
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Returns true if the recommendation is "accept".
|
|
155
|
+
#
|
|
156
|
+
# @return [Boolean]
|
|
157
|
+
def valid?
|
|
158
|
+
recommendation == "accept"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Build a ValidationResult from an API response hash.
|
|
162
|
+
#
|
|
163
|
+
# @param data [Hash] raw hash from the API
|
|
164
|
+
# @return [ValidationResult]
|
|
165
|
+
def self.from_hash(data)
|
|
166
|
+
new(
|
|
167
|
+
id: data["id"] || "",
|
|
168
|
+
email: data["email"] || "",
|
|
169
|
+
status: data["status"] || "pending",
|
|
170
|
+
risk_score: data["risk_score"] || 0,
|
|
171
|
+
risk_level: data["risk_level"] || "medium",
|
|
172
|
+
recommendation: data["recommendation"] || "review",
|
|
173
|
+
signals: (data["signals"] || []).map { |s| SignalResult.from_hash(s) },
|
|
174
|
+
created_at: data["created_at"] || "",
|
|
175
|
+
updated_at: data["updated_at"] || "",
|
|
176
|
+
fraud_decision: data["fraud_decision"],
|
|
177
|
+
fraud_flags: data["fraud_flags"],
|
|
178
|
+
tier1_completed_at: data["tier1_completed_at"],
|
|
179
|
+
tier2_completed_at: data["tier2_completed_at"],
|
|
180
|
+
tier3_completed_at: data["tier3_completed_at"]
|
|
181
|
+
)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Response from a batch validation request.
|
|
186
|
+
#
|
|
187
|
+
# @example
|
|
188
|
+
# batch = client.batch_validate(["a@b.com", "c@d.com"])
|
|
189
|
+
# puts "#{batch.completed}/#{batch.total} completed"
|
|
190
|
+
class BatchValidateResponse
|
|
191
|
+
# @return [Array<ValidationResult>] individual validation results
|
|
192
|
+
attr_reader :results
|
|
193
|
+
|
|
194
|
+
# @return [Integer] total number of emails in the batch
|
|
195
|
+
attr_reader :total
|
|
196
|
+
|
|
197
|
+
# @return [Integer] number of validations that have completed
|
|
198
|
+
attr_reader :completed
|
|
199
|
+
|
|
200
|
+
# @param results [Array<ValidationResult>]
|
|
201
|
+
# @param total [Integer]
|
|
202
|
+
# @param completed [Integer]
|
|
203
|
+
def initialize(results:, total:, completed:)
|
|
204
|
+
@results = results
|
|
205
|
+
@total = total
|
|
206
|
+
@completed = completed
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Build a BatchValidateResponse from an API response hash.
|
|
210
|
+
#
|
|
211
|
+
# @param data [Hash] raw hash from the API
|
|
212
|
+
# @return [BatchValidateResponse]
|
|
213
|
+
def self.from_hash(data)
|
|
214
|
+
new(
|
|
215
|
+
results: (data["results"] || []).map { |r| ValidationResult.from_hash(r) },
|
|
216
|
+
total: data["total"] || 0,
|
|
217
|
+
completed: data["completed"] || 0
|
|
218
|
+
)
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Usage period boundaries.
|
|
223
|
+
class UsagePeriod
|
|
224
|
+
# @return [String] ISO 8601 period start date
|
|
225
|
+
attr_reader :start
|
|
226
|
+
|
|
227
|
+
# @return [String] ISO 8601 period end date
|
|
228
|
+
attr_reader :end_date
|
|
229
|
+
|
|
230
|
+
# @param start [String]
|
|
231
|
+
# @param end_date [String]
|
|
232
|
+
def initialize(start:, end_date:)
|
|
233
|
+
@start = start
|
|
234
|
+
@end_date = end_date
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Aggregate usage information.
|
|
239
|
+
class UsageInfo
|
|
240
|
+
# @return [Integer] total validations used in the current period
|
|
241
|
+
attr_reader :total
|
|
242
|
+
|
|
243
|
+
# @return [Integer] plan validation limit
|
|
244
|
+
attr_reader :limit
|
|
245
|
+
|
|
246
|
+
# @return [Integer] remaining validations
|
|
247
|
+
attr_reader :remaining
|
|
248
|
+
|
|
249
|
+
# @param total [Integer]
|
|
250
|
+
# @param limit [Integer]
|
|
251
|
+
# @param remaining [Integer]
|
|
252
|
+
def initialize(total:, limit:, remaining:)
|
|
253
|
+
@total = total
|
|
254
|
+
@limit = limit
|
|
255
|
+
@remaining = remaining
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Daily usage entry.
|
|
260
|
+
class DailyUsage
|
|
261
|
+
# @return [String] date string (YYYY-MM-DD)
|
|
262
|
+
attr_reader :date
|
|
263
|
+
|
|
264
|
+
# @return [Integer] number of validations on this day
|
|
265
|
+
attr_reader :count
|
|
266
|
+
|
|
267
|
+
# @param date [String]
|
|
268
|
+
# @param count [Integer]
|
|
269
|
+
def initialize(date:, count:)
|
|
270
|
+
@date = date
|
|
271
|
+
@count = count
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Usage statistics for the current API key.
|
|
276
|
+
#
|
|
277
|
+
# @example
|
|
278
|
+
# usage = client.get_usage
|
|
279
|
+
# puts "#{usage.usage.remaining} validations remaining on #{usage.plan} plan"
|
|
280
|
+
class UsageStats
|
|
281
|
+
# @return [String] first characters of the API key
|
|
282
|
+
attr_reader :api_key_prefix
|
|
283
|
+
|
|
284
|
+
# @return [String] plan name
|
|
285
|
+
attr_reader :plan
|
|
286
|
+
|
|
287
|
+
# @return [UsagePeriod] current billing period
|
|
288
|
+
attr_reader :period
|
|
289
|
+
|
|
290
|
+
# @return [UsageInfo] aggregate usage information
|
|
291
|
+
attr_reader :usage
|
|
292
|
+
|
|
293
|
+
# @return [Array<DailyUsage>] daily usage breakdown
|
|
294
|
+
attr_reader :daily
|
|
295
|
+
|
|
296
|
+
# @param api_key_prefix [String]
|
|
297
|
+
# @param plan [String]
|
|
298
|
+
# @param period [UsagePeriod]
|
|
299
|
+
# @param usage [UsageInfo]
|
|
300
|
+
# @param daily [Array<DailyUsage>]
|
|
301
|
+
def initialize(api_key_prefix:, plan:, period:, usage:, daily:)
|
|
302
|
+
@api_key_prefix = api_key_prefix
|
|
303
|
+
@plan = plan
|
|
304
|
+
@period = period
|
|
305
|
+
@usage = usage
|
|
306
|
+
@daily = daily
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Build a UsageStats from an API response hash.
|
|
310
|
+
#
|
|
311
|
+
# @param data [Hash] raw hash from the API
|
|
312
|
+
# @return [UsageStats]
|
|
313
|
+
def self.from_hash(data)
|
|
314
|
+
period_data = data["period"] || {}
|
|
315
|
+
usage_data = data["usage"] || {}
|
|
316
|
+
|
|
317
|
+
new(
|
|
318
|
+
api_key_prefix: data["api_key_prefix"] || "",
|
|
319
|
+
plan: data["plan"] || "",
|
|
320
|
+
period: UsagePeriod.new(
|
|
321
|
+
start: period_data["start"] || "",
|
|
322
|
+
end_date: period_data["end"] || ""
|
|
323
|
+
),
|
|
324
|
+
usage: UsageInfo.new(
|
|
325
|
+
total: usage_data["total"] || 0,
|
|
326
|
+
limit: usage_data["limit"] || 0,
|
|
327
|
+
remaining: usage_data["remaining"] || 0
|
|
328
|
+
),
|
|
329
|
+
daily: (data["daily"] || []).map do |d|
|
|
330
|
+
DailyUsage.new(date: d["date"] || "", count: d["count"] || 0)
|
|
331
|
+
end
|
|
332
|
+
)
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# API health check response.
|
|
337
|
+
#
|
|
338
|
+
# @example
|
|
339
|
+
# health = client.health
|
|
340
|
+
# puts "API is #{health.status}, version #{health.version}"
|
|
341
|
+
class HealthResponse
|
|
342
|
+
# @return [String] health status: "ok", "degraded", or "down"
|
|
343
|
+
attr_reader :status
|
|
344
|
+
|
|
345
|
+
# @return [String] API version string
|
|
346
|
+
attr_reader :version
|
|
347
|
+
|
|
348
|
+
# @return [String] ISO 8601 timestamp
|
|
349
|
+
attr_reader :timestamp
|
|
350
|
+
|
|
351
|
+
# @return [Hash<String, String>] individual service statuses
|
|
352
|
+
attr_reader :services
|
|
353
|
+
|
|
354
|
+
# @param status [String]
|
|
355
|
+
# @param version [String]
|
|
356
|
+
# @param timestamp [String]
|
|
357
|
+
# @param services [Hash]
|
|
358
|
+
def initialize(status:, version:, timestamp:, services:)
|
|
359
|
+
@status = status
|
|
360
|
+
@version = version
|
|
361
|
+
@timestamp = timestamp
|
|
362
|
+
@services = services
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# Build a HealthResponse from an API response hash.
|
|
366
|
+
#
|
|
367
|
+
# @param data [Hash] raw hash from the API
|
|
368
|
+
# @return [HealthResponse]
|
|
369
|
+
def self.from_hash(data)
|
|
370
|
+
new(
|
|
371
|
+
status: data["status"] || "down",
|
|
372
|
+
version: data["version"] || "",
|
|
373
|
+
timestamp: data["timestamp"] || "",
|
|
374
|
+
services: data["services"] || {}
|
|
375
|
+
)
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
end
|
data/lib/bigshield.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "bigshield/version"
|
|
4
|
+
require_relative "bigshield/errors"
|
|
5
|
+
require_relative "bigshield/types"
|
|
6
|
+
require_relative "bigshield/http_client"
|
|
7
|
+
require_relative "bigshield/client"
|
|
8
|
+
|
|
9
|
+
# Multi-signal email validation SDK for BigShield.
|
|
10
|
+
#
|
|
11
|
+
# @example Quick start
|
|
12
|
+
# require "bigshield"
|
|
13
|
+
#
|
|
14
|
+
# client = BigShield::Client.new("ev_live_abc123")
|
|
15
|
+
# result = client.validate("user@example.com")
|
|
16
|
+
#
|
|
17
|
+
# if result.valid?
|
|
18
|
+
# puts "Email is safe (score: #{result.risk_score})"
|
|
19
|
+
# else
|
|
20
|
+
# puts "Risky email: #{result.recommendation} (score: #{result.risk_score})"
|
|
21
|
+
# end
|
|
22
|
+
module BigShield
|
|
23
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: bigshield
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- BigShield
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-03-17 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: net-http
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
description: Ruby SDK for the BigShield email validation API. Provides multi-signal
|
|
28
|
+
risk scoring to detect fake signups, burner emails, disposable domains, and other
|
|
29
|
+
fraudulent email patterns.
|
|
30
|
+
email:
|
|
31
|
+
executables: []
|
|
32
|
+
extensions: []
|
|
33
|
+
extra_rdoc_files: []
|
|
34
|
+
files:
|
|
35
|
+
- LICENSE
|
|
36
|
+
- README.md
|
|
37
|
+
- lib/bigshield.rb
|
|
38
|
+
- lib/bigshield/client.rb
|
|
39
|
+
- lib/bigshield/errors.rb
|
|
40
|
+
- lib/bigshield/http_client.rb
|
|
41
|
+
- lib/bigshield/types.rb
|
|
42
|
+
- lib/bigshield/version.rb
|
|
43
|
+
homepage: https://www.bigshield.app
|
|
44
|
+
licenses:
|
|
45
|
+
- MIT
|
|
46
|
+
metadata:
|
|
47
|
+
homepage_uri: https://www.bigshield.app
|
|
48
|
+
source_code_uri: https://github.com/bigshield/bigshield-ruby
|
|
49
|
+
documentation_uri: https://www.bigshield.app/docs
|
|
50
|
+
post_install_message:
|
|
51
|
+
rdoc_options: []
|
|
52
|
+
require_paths:
|
|
53
|
+
- lib
|
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - ">="
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '3.0'
|
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
60
|
+
requirements:
|
|
61
|
+
- - ">="
|
|
62
|
+
- !ruby/object:Gem::Version
|
|
63
|
+
version: '0'
|
|
64
|
+
requirements: []
|
|
65
|
+
rubygems_version: 3.0.3.1
|
|
66
|
+
signing_key:
|
|
67
|
+
specification_version: 4
|
|
68
|
+
summary: Multi-signal email validation SDK. Detect fake signups, burner emails, and
|
|
69
|
+
disposable domains.
|
|
70
|
+
test_files: []
|