apiddress 0.1.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: 659eb249d983cd4c87e29610a1a83d659d3487fb3d49f69859553585c4ec5a65
4
+ data.tar.gz: 9b7ff658b157ff64f474d377732c92dedbc259cb663df0949580816925bf4168
5
+ SHA512:
6
+ metadata.gz: b35284431baef688f6588a9b93d5fd14a62b85bff8e401700d1efc93815e9dc76b39a9aa5d27284f4415a5bac5b2c1c2df713c5403d5c83d5b70dcb82d482619
7
+ data.tar.gz: 868c82a52672a1f6ef90f65520a76ea7360f43b4fcc4c20f313b9a817574b7ca74339fe40bb3291ffd48e5398f8c97e855138fa74e6d95b5b46b57a33fc8592d
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 APIddress
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,142 @@
1
+ # apiddress
2
+
3
+ Official Ruby SDK for the [APIddress](https://api.apiddress.com) email validation API.
4
+
5
+ - Zero runtime gem dependencies (stdlib `net/http` only)
6
+ - Ruby 3.0+, immutable `Data` value objects, fully type-documented
7
+ - Automatic retry with backoff on `429` and `5xx` (batch creation is never retried)
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ gem install apiddress
13
+ ```
14
+
15
+ Or add to your `Gemfile`:
16
+
17
+ ```ruby
18
+ gem "apiddress"
19
+ ```
20
+
21
+ ## Quickstart
22
+
23
+ ```ruby
24
+ require "apiddress"
25
+
26
+ client = Apiddress::Client.new(ENV["APIDDRESS_API_KEY"])
27
+
28
+ result = client.validate_email("ada@stripe.com")
29
+ puts result.status # => "valid"
30
+ puts result.score # => 0.98
31
+ ```
32
+
33
+ ## Configuration
34
+
35
+ ```ruby
36
+ client = Apiddress::Client.new(
37
+ "YOUR_API_KEY",
38
+ base_url: "https://api.apiddress.com", # default
39
+ timeout: 10, # per-request timeout (seconds), default 10
40
+ max_retries: 2, # retries on 429/5xx, default 2
41
+ )
42
+ ```
43
+
44
+ ## Usage
45
+
46
+ ### Validate one email
47
+
48
+ ```ruby
49
+ result = client.validate_email(
50
+ "john@company.com",
51
+ check_smtp: false, # default
52
+ allow_role_based: true, # server default
53
+ )
54
+ # result.status: "valid" | "invalid" | "risky" | "disposable" | "unknown"
55
+ # result.suggestion: "john@gmail.com" for typo-like addresses, else nil
56
+ # result.checks: Apiddress::ValidationChecks (syntax, domain_exists, mx, smtp, ...)
57
+ ```
58
+
59
+ A malformed value (e.g. `"not-an-email"`) is a verdict, not an error: you get
60
+ `status == "invalid"` with `reason == "invalid_syntax"`.
61
+
62
+ ### Validate up to 100 emails synchronously
63
+
64
+ ```ruby
65
+ response = client.validate_emails(["a@example.com", "b@example.com"])
66
+ puts response.count
67
+ response.results.each { |r| puts "#{r.email} #{r.status}" }
68
+ ```
69
+
70
+ ### Batch jobs (up to 5000 emails)
71
+
72
+ ```ruby
73
+ batch = client.create_batch(
74
+ emails,
75
+ callback_url: "https://yourapp.com/webhooks/apiddress", # optional
76
+ )
77
+
78
+ done = client.wait_for_batch(
79
+ batch.batch_id,
80
+ poll_interval: 1.0, # default
81
+ timeout: 60.0, # default
82
+ )
83
+ puts "#{done.status} #{done.results.length}"
84
+
85
+ # Or poll yourself:
86
+ status = client.get_batch(batch.batch_id)
87
+ ```
88
+
89
+ `wait_for_batch` returns the terminal state (`"completed"` or `"failed"`) —
90
+ check `status` before using `results`.
91
+
92
+ ### Account
93
+
94
+ ```ruby
95
+ profile = client.me # plan, limits, usage
96
+ usage = client.usage # current month
97
+ may = client.usage("2026-05") # specific month
98
+ health = client.health # no auth required
99
+ ```
100
+
101
+ ## Error handling
102
+
103
+ Every failed request raises an `Apiddress::Error`:
104
+
105
+ ```ruby
106
+ require "apiddress"
107
+
108
+ begin
109
+ client.validate_email("john@company.com")
110
+ rescue Apiddress::Error => err
111
+ puts err.status # 429
112
+ puts err.code # "quota_exceeded"
113
+ puts err.message # "Monthly request limit exceeded."
114
+ p err.details # {"requests_used"=>..., "requests_limit"=>...}
115
+ end
116
+ ```
117
+
118
+ | `status` | `code` |
119
+ | -------- | ------------------- |
120
+ | 400 | `invalid_request` |
121
+ | 401 | `unauthorized` |
122
+ | 404 | `not_found` |
123
+ | 429 | `quota_exceeded` |
124
+ | 500 | `internal_error` |
125
+ | 0 | `timeout` (request or `wait_for_batch` timeout) |
126
+
127
+ ## Development
128
+
129
+ ```bash
130
+ bundle install
131
+
132
+ # Unit tests (no network required):
133
+ ruby test/test_client.rb
134
+
135
+ # Integration tests need a live backend:
136
+ APIDDRESS_BASE_URL=http://localhost:3000 APIDDRESS_API_KEY=test_key_local_dev \
137
+ ruby test/test_client.rb
138
+ ```
139
+
140
+ ## License
141
+
142
+ MIT
@@ -0,0 +1,208 @@
1
+ require "json"
2
+ require "net/http"
3
+ require "uri"
4
+
5
+ module Apiddress
6
+ # Official APIddress client (stdlib net/http, Ruby 3.0+).
7
+ #
8
+ # client = Apiddress::Client.new(ENV["APIDDRESS_API_KEY"])
9
+ # result = client.validate_email("ada@stripe.com")
10
+ # puts result.status # => "valid"
11
+ class Client
12
+ DEFAULT_BASE_URL = "https://api.apiddress.com"
13
+ DEFAULT_TIMEOUT = 10 # seconds
14
+ DEFAULT_MAX_RETRIES = 2
15
+
16
+ # @param api_key [String] Your APIddress API key (sent as x-api-key).
17
+ # @param base_url [String] API origin. Defaults to production.
18
+ # @param timeout [Integer] Per-request timeout in seconds. Default 10.
19
+ # @param max_retries [Integer] Retries on 429/5xx. Default 2.
20
+ # Batch creation is never retried.
21
+ def initialize(api_key, base_url: DEFAULT_BASE_URL, timeout: DEFAULT_TIMEOUT, max_retries: DEFAULT_MAX_RETRIES)
22
+ raise ArgumentError, "Apiddress::Client: api_key must not be empty" if api_key.nil? || api_key.empty?
23
+
24
+ @api_key = api_key
25
+ @base_url = base_url.chomp("/")
26
+ @timeout = timeout
27
+ @max_retries = max_retries
28
+ end
29
+
30
+ # -------------------------------------------------------------------------
31
+ # Validation
32
+ # -------------------------------------------------------------------------
33
+
34
+ # Validate a single email address. Costs 1 request.
35
+ #
36
+ # @param email [String]
37
+ # @param check_smtp [Boolean] Attempt an SMTP-level probe. Default false.
38
+ # @param allow_role_based [Boolean, nil] Treat role-based addresses as acceptable. Default nil (use server default).
39
+ # @return [ValidateEmailResponse]
40
+ def validate_email(email, check_smtp: false, allow_role_based: nil)
41
+ body = { email: email, check_smtp: check_smtp }
42
+ body[:allow_role_based] = allow_role_based unless allow_role_based.nil?
43
+ ValidateEmailResponse.from_hash(request(:post, "/api/v1/validate-email", body:))
44
+ end
45
+
46
+ # Validate 1-100 email addresses synchronously. Costs one request per email.
47
+ #
48
+ # @param emails [Array<String>]
49
+ # @param check_smtp [Boolean]
50
+ # @return [BulkValidateResponse]
51
+ def validate_emails(emails, check_smtp: false)
52
+ BulkValidateResponse.from_hash(
53
+ request(:post, "/api/v1/validate-emails", body: { emails:, check_smtp: }),
54
+ )
55
+ end
56
+
57
+ # -------------------------------------------------------------------------
58
+ # Batch jobs
59
+ # -------------------------------------------------------------------------
60
+
61
+ # Create an asynchronous batch job for 1-5000 email addresses.
62
+ # Never retried automatically (to avoid duplicate jobs).
63
+ #
64
+ # @param emails [Array<String>]
65
+ # @param callback_url [String, nil] Webhook URL invoked when the batch completes.
66
+ # @param check_smtp [Boolean]
67
+ # @return [BatchAcceptedResponse]
68
+ def create_batch(emails, callback_url: nil, check_smtp: false)
69
+ body = { emails:, check_smtp: }
70
+ body[:callback_url] = callback_url unless callback_url.nil?
71
+ BatchAcceptedResponse.from_hash(
72
+ request(:post, "/api/v1/batches", body:, retryable: false),
73
+ )
74
+ end
75
+
76
+ # Get the current state (and results, when completed) of a batch job.
77
+ #
78
+ # @param batch_id [String]
79
+ # @return [BatchStatusResponse]
80
+ def get_batch(batch_id)
81
+ BatchStatusResponse.from_hash(
82
+ request(:get, "/api/v1/batches/#{URI.encode_www_form_component(batch_id)}"),
83
+ )
84
+ end
85
+
86
+ # Poll a batch job until it reaches a terminal state ("completed" or "failed").
87
+ #
88
+ # Returns the final state. Raises {Error} with code +"timeout"+ if the job
89
+ # is still running after +timeout+ seconds.
90
+ #
91
+ # @param batch_id [String]
92
+ # @param poll_interval [Float] Delay between polls in seconds. Default 1.0.
93
+ # @param timeout [Float] Give up after this many seconds. Default 60.0.
94
+ # @return [BatchStatusResponse]
95
+ def wait_for_batch(batch_id, poll_interval: 1.0, timeout: 60.0)
96
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
97
+
98
+ loop do
99
+ batch = get_batch(batch_id)
100
+ return batch if %w[completed failed].include?(batch.status)
101
+
102
+ if Process.clock_gettime(Process::CLOCK_MONOTONIC) + poll_interval > deadline
103
+ raise Error.new(0, "timeout",
104
+ "Batch #{batch_id} did not complete within #{timeout}s (last status: #{batch.status})")
105
+ end
106
+ sleep(poll_interval)
107
+ end
108
+ end
109
+
110
+ # -------------------------------------------------------------------------
111
+ # Account
112
+ # -------------------------------------------------------------------------
113
+
114
+ # Get the profile (plan, limits, usage) of the authenticated API key.
115
+ # @return [ApiKeyProfileResponse]
116
+ def me
117
+ ApiKeyProfileResponse.from_hash(request(:get, "/api/v1/me"))
118
+ end
119
+
120
+ # Get usage for a month ("YYYY-MM"). Defaults to the current month.
121
+ # @param month [String, nil]
122
+ # @return [UsageResponse]
123
+ def usage(month = nil)
124
+ query = month ? { month: } : nil
125
+ UsageResponse.from_hash(request(:get, "/api/v1/usage", query:))
126
+ end
127
+
128
+ # Service health check. Does not require authentication.
129
+ # @return [HealthResponse]
130
+ def health
131
+ HealthResponse.from_hash(request(:get, "/api/v1/health", auth: false))
132
+ end
133
+
134
+ # -------------------------------------------------------------------------
135
+ # Transport
136
+ # -------------------------------------------------------------------------
137
+
138
+ private
139
+
140
+ def request(method, path, body: nil, query: nil, auth: true, retryable: true)
141
+ url = URI.parse(@base_url + path)
142
+ url.query = URI.encode_www_form(query.transform_keys(&:to_s)) if query
143
+
144
+ max_attempts = retryable ? @max_retries + 1 : 1
145
+
146
+ attempt = 0
147
+ loop do
148
+ http = Net::HTTP.new(url.host, url.port)
149
+ http.use_ssl = url.scheme == "https"
150
+ http.open_timeout = @timeout
151
+ http.read_timeout = @timeout
152
+
153
+ req_class = method == :get ? Net::HTTP::Get : Net::HTTP::Post
154
+ req = req_class.new(url.request_uri)
155
+ req["Accept"] = "application/json"
156
+ req["x-api-key"] = @api_key if auth
157
+
158
+ if body
159
+ req["Content-Type"] = "application/json"
160
+ req.body = JSON.generate(body)
161
+ end
162
+
163
+ begin
164
+ response = http.request(req)
165
+ rescue Net::OpenTimeout, Net::ReadTimeout, Timeout::Error => e
166
+ raise Error.new(0, "timeout", "Request to #{path} timed out after #{@timeout}s")
167
+ end
168
+
169
+ http_status = response.code.to_i
170
+
171
+ if http_status >= 200 && http_status < 300
172
+ return JSON.parse(response.body)
173
+ end
174
+
175
+ api_error = parse_error_envelope(response.body, http_status)
176
+ should_retry = attempt + 1 < max_attempts && (http_status == 429 || http_status >= 500)
177
+
178
+ raise api_error unless should_retry
179
+
180
+ sleep(backoff_seconds(attempt))
181
+ attempt += 1
182
+ end
183
+ end
184
+
185
+ # Exponential backoff with a little jitter: ~0.25s, ~0.5s, ~1s, capped at 4s.
186
+ def backoff_seconds(attempt)
187
+ [0.25 * (2**attempt), 4.0].min + rand * 0.1
188
+ end
189
+
190
+ def parse_error_envelope(body, http_status)
191
+ code = "unknown_error"
192
+ message = "HTTP #{http_status}"
193
+ details = nil
194
+
195
+ begin
196
+ payload = JSON.parse(body)
197
+ envelope = payload["error"] || {}
198
+ code = envelope["code"] || code
199
+ message = envelope["message"] || message
200
+ details = envelope["details"]
201
+ rescue JSON::ParserError
202
+ # Non-JSON error body; keep defaults.
203
+ end
204
+
205
+ Error.new(http_status, code, message, details)
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,38 @@
1
+ module Apiddress
2
+ # Raised for any failed APIddress request.
3
+ #
4
+ # For HTTP errors, +status+ is the response status code and +code+ / +message+ /
5
+ # +details+ come from the API error envelope +{"error": {"code", "message", "details"}}+.
6
+ #
7
+ # For client-side failures (request timeout, wait_for_batch timeout), +status+
8
+ # is 0 and +code+ is +"timeout"+.
9
+ class Error < StandardError
10
+ # @return [Integer] HTTP status code, or 0 for client-side failures.
11
+ attr_reader :status
12
+
13
+ # @return [String] Machine-readable error code, e.g. "quota_exceeded".
14
+ attr_reader :code
15
+
16
+ # @return [String] Human-readable description (also the exception message).
17
+ attr_reader :message
18
+
19
+ # @return [Hash, nil] Extra context from the API, or nil.
20
+ attr_reader :details
21
+
22
+ # @param status [Integer]
23
+ # @param code [String]
24
+ # @param message [String]
25
+ # @param details [Hash, nil]
26
+ def initialize(status, code, message, details = nil)
27
+ super(message)
28
+ @status = status
29
+ @code = code
30
+ @message = message
31
+ @details = details
32
+ end
33
+
34
+ def inspect
35
+ "#<Apiddress::Error status=#{status.inspect} code=#{code.inspect} message=#{message.inspect} details=#{details.inspect}>"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,180 @@
1
+ module Apiddress
2
+ # Response models for the APIddress API.
3
+ #
4
+ # Hand-mapped 1:1 from the OpenAPI 3.1 contract (backend/openapi.yaml).
5
+ # Attribute names match the wire format exactly (snake_case).
6
+
7
+ # Individual checks performed on an email address.
8
+ ValidationChecks = Data.define(
9
+ :syntax,
10
+ :domain_exists,
11
+ :mx,
12
+ # nil when the SMTP probe was skipped or inconclusive.
13
+ :smtp,
14
+ :disposable,
15
+ :role_based,
16
+ :free_provider,
17
+ # nil when not determined.
18
+ :catch_all,
19
+ :typo,
20
+ # "low" | "medium" | "high" | nil
21
+ :spam_trap_risk,
22
+ ) do
23
+ # @param data [Hash]
24
+ # @return [ValidationChecks]
25
+ def self.from_hash(data)
26
+ new(
27
+ syntax: data["syntax"],
28
+ domain_exists: data["domain_exists"],
29
+ mx: data["mx"],
30
+ smtp: data["smtp"],
31
+ disposable: data["disposable"],
32
+ role_based: data["role_based"],
33
+ free_provider: data["free_provider"],
34
+ catch_all: data["catch_all"],
35
+ typo: data["typo"],
36
+ spam_trap_risk: data["spam_trap_risk"],
37
+ )
38
+ end
39
+ end
40
+
41
+ # Result of validating a single email address.
42
+ ValidateEmailResponse = Data.define(
43
+ :email,
44
+ :normalized_email,
45
+ # "valid" | "invalid" | "risky" | "disposable" | "unknown"
46
+ :status,
47
+ :valid,
48
+ # Confidence score between 0 and 1.
49
+ :score,
50
+ # Machine-readable reason, e.g. "accepted_email", "invalid_syntax".
51
+ :reason,
52
+ # Suggested correction for typo-like addresses, or nil.
53
+ :suggestion,
54
+ :checks,
55
+ :provider,
56
+ :created_at,
57
+ ) do
58
+ def self.from_hash(data)
59
+ new(
60
+ email: data["email"],
61
+ normalized_email: data["normalized_email"],
62
+ status: data["status"],
63
+ valid: data["valid"],
64
+ score: data["score"].to_f,
65
+ reason: data["reason"],
66
+ suggestion: data["suggestion"],
67
+ checks: ValidationChecks.from_hash(data["checks"]),
68
+ provider: data["provider"],
69
+ created_at: data["created_at"],
70
+ )
71
+ end
72
+ end
73
+
74
+ # Result of a synchronous bulk validation.
75
+ BulkValidateResponse = Data.define(:count, :results) do
76
+ def self.from_hash(data)
77
+ new(
78
+ count: data["count"].to_i,
79
+ results: data["results"].map { |item| ValidateEmailResponse.from_hash(item) },
80
+ )
81
+ end
82
+ end
83
+
84
+ # Returned when an asynchronous batch job is accepted (HTTP 202).
85
+ BatchAcceptedResponse = Data.define(
86
+ :batch_id,
87
+ # Always "pending" on acceptance.
88
+ :status,
89
+ :submitted_count,
90
+ :created_at,
91
+ ) do
92
+ def self.from_hash(data)
93
+ new(
94
+ batch_id: data["batch_id"],
95
+ status: data["status"],
96
+ submitted_count: data["submitted_count"].to_i,
97
+ created_at: data["created_at"],
98
+ )
99
+ end
100
+ end
101
+
102
+ # Current state of an asynchronous batch job.
103
+ BatchStatusResponse = Data.define(
104
+ :batch_id,
105
+ # "pending" | "processing" | "completed" | "failed"
106
+ :status,
107
+ :submitted_count,
108
+ :processed_count,
109
+ :created_at,
110
+ :completed_at,
111
+ # Per-email results once the batch is completed, otherwise nil.
112
+ :results,
113
+ ) do
114
+ def self.from_hash(data)
115
+ raw_results = data["results"]
116
+ new(
117
+ batch_id: data["batch_id"],
118
+ status: data["status"],
119
+ submitted_count: data["submitted_count"].to_i,
120
+ processed_count: data["processed_count"].to_i,
121
+ created_at: data["created_at"],
122
+ completed_at: data["completed_at"],
123
+ results: raw_results.nil? ? nil : raw_results.map { |item| ValidateEmailResponse.from_hash(item) },
124
+ )
125
+ end
126
+ end
127
+
128
+ # Profile of the authenticated API key.
129
+ ApiKeyProfileResponse = Data.define(
130
+ :id,
131
+ :email,
132
+ # "free" | "paid" | "enterprise"
133
+ :plan,
134
+ :is_active,
135
+ :requests_limit,
136
+ :requests_used,
137
+ :created_at,
138
+ ) do
139
+ def self.from_hash(data)
140
+ new(
141
+ id: data["id"],
142
+ email: data["email"],
143
+ plan: data["plan"],
144
+ is_active: data["is_active"],
145
+ requests_limit: data["requests_limit"].to_i,
146
+ requests_used: data["requests_used"].to_i,
147
+ created_at: data["created_at"],
148
+ )
149
+ end
150
+ end
151
+
152
+ # Monthly usage for the authenticated API key.
153
+ UsageResponse = Data.define(
154
+ # Month in YYYY-MM format.
155
+ :month,
156
+ :requests_used,
157
+ :requests_limit,
158
+ :remaining,
159
+ ) do
160
+ def self.from_hash(data)
161
+ new(
162
+ month: data["month"],
163
+ requests_used: data["requests_used"].to_i,
164
+ requests_limit: data["requests_limit"].to_i,
165
+ remaining: data["remaining"].to_i,
166
+ )
167
+ end
168
+ end
169
+
170
+ # Service health payload.
171
+ HealthResponse = Data.define(:status, :timestamp, :version) do
172
+ def self.from_hash(data)
173
+ new(
174
+ status: data["status"],
175
+ timestamp: data["timestamp"],
176
+ version: data["version"],
177
+ )
178
+ end
179
+ end
180
+ end
data/lib/apiddress.rb ADDED
@@ -0,0 +1,8 @@
1
+ # Official Ruby SDK for the APIddress email validation API.
2
+ require_relative "apiddress/error"
3
+ require_relative "apiddress/models"
4
+ require_relative "apiddress/client"
5
+
6
+ module Apiddress
7
+ VERSION = "0.1.0"
8
+ end
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: apiddress
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - APIddress
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Validate email addresses with the APIddress API. Stdlib net/http only,
13
+ Ruby 3.0+, automatic retries.
14
+ executables: []
15
+ extensions: []
16
+ extra_rdoc_files: []
17
+ files:
18
+ - LICENSE
19
+ - README.md
20
+ - lib/apiddress.rb
21
+ - lib/apiddress/client.rb
22
+ - lib/apiddress/error.rb
23
+ - lib/apiddress/models.rb
24
+ homepage: https://api.apiddress.com
25
+ licenses:
26
+ - MIT
27
+ metadata:
28
+ homepage_uri: https://api.apiddress.com
29
+ source_code_uri: https://github.com/apiddress/sdk-ruby
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '3.0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubygems_version: 4.0.11
45
+ specification_version: 4
46
+ summary: Official Ruby SDK for the APIddress email validation API
47
+ test_files: []