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 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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BigShield
4
+ VERSION = "1.0.0"
5
+ 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: []