veille 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: 570b9abadd612e47a06d4b4687312972b677d848679c04a7439e3d69547bf3e9
4
+ data.tar.gz: 4c262b03980615c40faf4f684e9239f03b601ca731f16823f7b5c08bebe6010d
5
+ SHA512:
6
+ metadata.gz: 7962d828395e73c1169f9ba6f53edb5277541c171896a1fa2f18b09dc91ca67b70d898afa07962be43fb100a49a5899aa1d0e22fdd0765a00b3f1d29e2fcc2c0
7
+ data.tar.gz: 908dbafb4a2ab06c4a9ee7110072c5d7b728c8f87cbb91341c43adb14714d2956431fa2807bfb8c6cce307ae4fa33848a1cc51adc4dc58193e597f2db3c9d0cb
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Veille
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,145 @@
1
+ # Veille Ruby SDK
2
+
3
+ The official Ruby SDK for the [Veille](https://www.veille.io) fraud prevention API.
4
+
5
+ Validate emails, domains, IPs, VAT numbers and IBANs in milliseconds to block fake sign-ups, bots and fraud.
6
+
7
+ The SDK exposes **11 methods**, one per documented endpoint, plus a generic `request`. It only uses the Ruby standard library.
8
+
9
+ ## Requirements
10
+
11
+ - Ruby 3.1 or newer
12
+
13
+ ## Installation
14
+
15
+ ```sh
16
+ gem install veille
17
+ ```
18
+
19
+ Or in your `Gemfile`:
20
+
21
+ ```ruby
22
+ gem "veille"
23
+ ```
24
+
25
+ ## Get your API key
26
+
27
+ [Sign up for Veille](https://app.veille.io/register) or sign in, then copy your API key from the [dashboard](https://app.veille.io). The client reads `ENV["VEILLE_API_KEY"]` when no key is passed.
28
+
29
+ ## Quick start
30
+
31
+ ```ruby
32
+ require "veille"
33
+
34
+ client = Veille::Client.new(api_key: ENV.fetch("VEILLE_API_KEY"))
35
+ response = client.intelligence_email(query: "jane.doe@example.com")
36
+ puts response.data
37
+ ```
38
+
39
+ Keyword arguments use the parameter names of the [API reference](https://app.veille.io/openapi). Unknown names raise `Veille::SerializationError`; use `request` for undocumented parameters.
40
+
41
+ ## Async jobs
42
+
43
+ Any call accepts `is_async: true` (useful past the 60-second server limit): the response is then a `job_id`. `wait_for_job` polls `GET /v1/jobs/{id}` until a terminal status (`CompletedJob`, `Error`, `NotFound`, `BadRequest`):
44
+
45
+ ```ruby
46
+ submitted = client.intelligence_email(query: "jane.doe@example.com", is_async: true)
47
+ job = client.wait_for_job(submitted.data["job_id"]) { |j| puts "#{j['status']} #{j['progress']}%" }
48
+ puts job.data
49
+ ```
50
+
51
+ ## GET or POST
52
+
53
+ Methods use GET when the endpoint supports it (query string). POST sends the same parameters as JSON:
54
+
55
+ `nil` values are omitted, `false` and `0` are sent, and arrays repeat the parameter name.
56
+
57
+ ## Any endpoint
58
+
59
+ ```ruby
60
+ response = client.request("GET", "/v1/usage")
61
+ ```
62
+
63
+ ## Errors
64
+
65
+ ```ruby
66
+ begin
67
+ client.intelligence_email(query: "jane.doe@example.com")
68
+ rescue Veille::APIError => e
69
+ warn "HTTP #{e.status}: #{e.message}"
70
+ end
71
+ ```
72
+
73
+ | Error | When |
74
+ | --- | --- |
75
+ | `Veille::ConfigurationError` | Missing API key, invalid options or unsupported method. |
76
+ | `Veille::SerializationError` | A required parameter is missing, unknown or can't be sent. |
77
+ | `Veille::TransportError` | Network failure or timeout. |
78
+ | `Veille::APIError` | Non-2xx status (`status`, `error_message`, `response_body`, `raw_response`). |
79
+ | `Veille::ResponseDecodingError` | A success response could not be decoded. |
80
+
81
+ All of them inherit from `Veille::Error`.
82
+
83
+ ## Retries
84
+
85
+ Requests are retried up to 3 times after a network error, a `408`, `500`, `502`, `503` or `504`, or a rate limit — which the API returns as `401` (or `429`) with a `Rate limit exceeded` message. Per-key quota errors (`…: quota daily`) are not retried, nor are `400`, `402`, `404` or client-side timeouts. The delay starts at 1 second and grows 1.5× per retry (capped at 30 seconds); a `Retry-After` header wins.
86
+
87
+ ```ruby
88
+ client = Veille::Client.new(max_retries: 5, retry_delay: 0.5)
89
+ client.intelligence_email(query: "jane.doe@example.com", request_options: { max_retries: 0 }) # one request without retries
90
+ ```
91
+
92
+ ## Batch requests
93
+
94
+ `batch` runs the block for every item on 10 threads (`concurrency:`) and returns one `BatchResult` per item, in input order. A failing item never stops the others.
95
+
96
+ ```ruby
97
+ inputs = ["jane.doe@example.com", "john@mailinator.com", "contact@veille.io"]
98
+ results = client.batch(inputs, concurrency: 5) { |value| client.intelligence_email(query: value) }
99
+ results.each do |result|
100
+ if result.ok?
101
+ puts result.value.data
102
+ else
103
+ warn "#{inputs[result.index]}: #{result.error.message}"
104
+ end
105
+ end
106
+ ```
107
+
108
+ | Constant | Default | Description |
109
+ | --- | --- | --- |
110
+ | `Veille::Client::DEFAULT_CONCURRENCY` | `10` | Tasks run at the same time by batch |
111
+ | `Veille::Client::DEFAULT_TIMEOUT` | `120` | Request timeout (seconds) |
112
+ | `Veille::Client::MAX_RETRIES` | `3` | Retry attempts after a network error, a 408/5xx or a rate limit |
113
+ | `Veille::Client::RETRY_BACKOFF_FACTOR` | `1.5` | Exponential backoff multiplier |
114
+ | `Veille::Client::DEFAULT_RETRY_DELAY` | `1.0` | Delay before the first retry (seconds) |
115
+
116
+ ## Timeouts and custom transports
117
+
118
+ ```ruby
119
+ client = Veille::Client.new(timeout: 60) # seconds, default 120
120
+ client.intelligence_email(query: "jane.doe@example.com", request_options: { timeout: 30 })
121
+ ```
122
+
123
+ A transport is any object with `call(method:, url:, headers:, body:, timeout:)` returning `[status, headers, body]`; the default is `Veille::NetHTTPTransport`.
124
+
125
+ ## Endpoints
126
+
127
+ 11 methods, generated from the [OpenAPI document](https://app.veille.io/openapi).
128
+
129
+ | Method | HTTP | Description |
130
+ | --- | --- | --- |
131
+ | `intelligence_domain` | GET `/v1/intelligence/domain` | Check any domain instantly. Detects disposable email providers, public webmail, relay services, and education domains. Returns DNS health w… |
132
+ | `intelligence_email` | GET `/v1/intelligence/email` | Validate any email address in a single call. Detects disposable and temporary inboxes, identifies role accounts, public webmail, relay serv… |
133
+ | `intelligence_emailq` | GET `/v1/intelligence/emailq` | Deep email deliverability verification. Performs full Email Validation plus SMTP inbox check, catch-all detection, and returns a qualificat… |
134
+ | `intelligence_ip` | GET `/v1/intelligence/ip` | Assess the reputation of any IP address. Detects VPNs, proxies, Tor exit nodes, datacenter hosting, abusive traffic, and bots. Returns geol… |
135
+ | `intelligence_phone` | GET `/v1/intelligence/phone` | Validate any phone number in E.164 format. Detects disposable and virtual numbers, identifies carrier and line type (mobile/fixed/VoIP), re… |
136
+ | `jobs` | GET `/v1/jobs/{id}` | Poll the status, progress (0–100) and result of a job started with is_async=true. Polling is free. Poll until status is terminal: Completed… |
137
+ | `stats` | GET `/v1/stats` | Infrastructure health for an engine or endpoint over the last hour. Not scoped to your account or tenant. Enabled per account: HTTP 403 mea… |
138
+ | `usage` | GET `/v1/usage` | Retrieve real-time usage statistics and credit balance for your Veille account. |
139
+ | `vat` | GET `/v1/vat` | Validate a European VAT number against the VIES database. Returns company name, registered address, and validity status for any EU member s… |
140
+ | `vat_iban` | GET `/v1/vat/iban` | Validate any IBAN and retrieve associated bank details. Returns the BIC/SWIFT code, bank name, country, BBAN structural breakdown, and SEPA… |
141
+ | `vat_rates` | GET `/v1/vat/rates` | Get current ECB exchange rates for any supported base currency. Optionally filter to specific target currencies. Updated daily from the Eur… |
142
+
143
+ ## License
144
+
145
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,11 @@
1
+ # Examples
2
+
3
+ ```sh
4
+ gem install veille
5
+ export VEILLE_API_KEY="YOUR_API_KEY"
6
+ ruby examples/json.rb
7
+ ```
8
+
9
+ - `json.rb` — call the featured endpoint and print the JSON result.
10
+ - `request.rb` — call any path with `request` and handle API errors.
11
+ - `wait_for_job.rb` — start an async job and wait for its result.
data/examples/json.rb ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "veille"
5
+
6
+ client = Veille::Client.new # reads VEILLE_API_KEY
7
+ response = client.intelligence_email(query: "jane.doe@example.com")
8
+ puts JSON.pretty_generate(response.data)
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "veille"
4
+
5
+ client = Veille::Client.new # reads VEILLE_API_KEY
6
+ begin
7
+ response = client.request("GET", "/v1/usage")
8
+ puts response.data if response.json?
9
+ rescue Veille::APIError => e
10
+ warn "HTTP #{e.status}: #{e.message}"
11
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "veille"
4
+
5
+ client = Veille::Client.new # reads VEILLE_API_KEY
6
+ submitted = client.intelligence_email(query: "jane.doe@example.com", is_async: true)
7
+ job = client.wait_for_job(submitted.data["job_id"]) { |j| puts "#{j['status']} #{j['progress']}%" }
8
+ puts job.data
@@ -0,0 +1,266 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Veille
6
+ # Official Veille API client. Create one instance and reuse it.
7
+ class Client
8
+ include Operations
9
+
10
+ DEFAULT_BASE_URL = "https://api.veille.io"
11
+ DEFAULT_TIMEOUT = 120
12
+ # Tasks run at the same time by #batch.
13
+ DEFAULT_CONCURRENCY = 10
14
+ # Retries after a network error or a 408, 429, 500, 502, 503 or 504 response.
15
+ MAX_RETRIES = 3
16
+ # Each retry waits this many times longer than the previous one (a Retry-After header wins).
17
+ RETRY_BACKOFF_FACTOR = 1.5
18
+ # Seconds before the first retry.
19
+ DEFAULT_RETRY_DELAY = 1.0
20
+ MAX_RETRY_DELAY = 30.0
21
+ RETRYABLE_STATUSES = [408, 500, 502, 503, 504].freeze
22
+ API_KEY_ENVIRONMENT_VARIABLE = "VEILLE_API_KEY"
23
+ JOB_TERMINAL_STATUSES = %w[CompletedJob Error NotFound BadRequest].freeze
24
+ ERROR_MESSAGE_KEYS = %w[error message error_message detail].freeze
25
+
26
+ # @param api_key [String, nil] defaults to ENV["VEILLE_API_KEY"]
27
+ # @param base_url [String] API origin
28
+ # @param timeout [Numeric, nil] seconds per request; nil disables it
29
+ # @param headers [Hash] extra headers for every request
30
+ # @param max_retries [Integer] retries after a network error or a 408/429/5xx response; 0 disables them
31
+ # @param retry_delay [Numeric] seconds before the first retry, growing 1.5x per retry
32
+ # @param transport [#call] custom transport, see {NetHTTPTransport}
33
+ def initialize(api_key: nil, base_url: DEFAULT_BASE_URL, timeout: DEFAULT_TIMEOUT, headers: {},
34
+ max_retries: MAX_RETRIES, retry_delay: DEFAULT_RETRY_DELAY, transport: nil)
35
+ api_key = ENV.fetch(API_KEY_ENVIRONMENT_VARIABLE, nil) if api_key.nil?
36
+ if !api_key.is_a?(String) || api_key.strip.empty?
37
+ raise ConfigurationError,
38
+ "A non-blank Veille API key is required. Pass api_key: or set #{API_KEY_ENVIRONMENT_VARIABLE}."
39
+ end
40
+ raise ConfigurationError, "The Veille API key cannot be used in an HTTP header." if api_key.match?(/[\r\n]/)
41
+
42
+ uri = URI.parse(base_url.to_s)
43
+ unless %w[http https].include?(uri.scheme) && uri.host
44
+ raise ConfigurationError, "base_url must be an absolute http or https URL."
45
+ end
46
+ raise ConfigurationError, "timeout must be a positive number or nil." if timeout && !(timeout.is_a?(Numeric) && timeout.positive?)
47
+ raise ConfigurationError, "transport must respond to #call." if transport && !transport.respond_to?(:call)
48
+
49
+ @api_key = api_key
50
+ @base_url = base_url.to_s.sub(%r{/+\z}, "")
51
+ @timeout = timeout
52
+ @headers = headers.to_h { |name, value| [name.to_s, value.to_s] }.freeze
53
+ raise ConfigurationError, "max_retries must be a non-negative Integer." unless max_retries.is_a?(Integer) && max_retries >= 0
54
+ raise ConfigurationError, "retry_delay must be a non-negative number." unless retry_delay.is_a?(Numeric) && retry_delay >= 0
55
+
56
+ @max_retries = max_retries
57
+ @retry_delay = retry_delay
58
+ @transport = transport || NetHTTPTransport.new
59
+ end
60
+
61
+ def inspect
62
+ "#<#{self.class.name} base_url=#{@base_url.inspect}>"
63
+ end
64
+
65
+ # Sends any request, including endpoints released after this SDK version.
66
+ # GET parameters travel in the query string; POST parameters travel in a JSON body.
67
+ # @return [JsonResponse, BinaryResponse]
68
+ def request(method, path, params = nil, timeout: nil, headers: nil, max_retries: nil)
69
+ send_request(method.to_s.upcase, path, params, { timeout: timeout, headers: headers, max_retries: max_retries }, :any)
70
+ end
71
+
72
+ # Runs the block for every item on +concurrency+ threads (default 10) and returns one {BatchResult} per item,
73
+ # in input order. A failing item never stops the others; the client is thread-safe.
74
+ #
75
+ # results = client.batch(ids, concurrency: 5) { |id| client.some_endpoint(query: id) }
76
+ # @return [Array<BatchResult>]
77
+ def batch(items, concurrency: DEFAULT_CONCURRENCY)
78
+ raise ConfigurationError, "concurrency must be a positive Integer." unless concurrency.is_a?(Integer) && concurrency.positive?
79
+ raise ArgumentError, "batch needs a block" unless block_given?
80
+
81
+ list = items.to_a
82
+ results = Array.new(list.size)
83
+ queue = Queue.new
84
+ list.each_with_index { |item, index| queue << [item, index] }
85
+ queue.close
86
+ workers = Array.new([concurrency, list.size].min) do
87
+ Thread.new do
88
+ while (job = queue.pop)
89
+ item, index = job
90
+ results[index] = begin
91
+ BatchResult.new(index: index, value: yield(item), error: nil)
92
+ rescue StandardError => e
93
+ BatchResult.new(index: index, value: nil, error: e)
94
+ end
95
+ end
96
+ end
97
+ end
98
+ workers.each(&:join)
99
+ results
100
+ end
101
+
102
+ # Polls GET /v1/jobs/{id} until the job reaches a terminal status. The delay starts at +interval+ seconds
103
+ # and grows 1.5x per poll up to 60 s; +timeout+ defaults to 12 hours.
104
+ # @yieldparam job [Hash] the latest job document after every poll
105
+ # @return [JsonResponse]
106
+ def wait_for_job(job_id, interval: 3, timeout: 43_200)
107
+ raise SerializationError.new("A non-blank job id is required.", "id") if job_id.to_s.strip.empty?
108
+
109
+ started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
110
+ loop do
111
+ last = request("GET", "/v1/jobs/{id}", { "id" => job_id })
112
+ yield last.data if block_given?
113
+ return last if last.data.is_a?(Hash) && JOB_TERMINAL_STATUSES.include?(last.data["status"])
114
+
115
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
116
+ raise JobTimeoutError.new(job_id, last) if elapsed + interval > timeout
117
+
118
+ sleep(interval)
119
+ interval = (interval * 1.5).clamp(interval, [interval, 60].max)
120
+ end
121
+ end
122
+
123
+ private
124
+
125
+ def call_operation(name, params, options)
126
+ endpoint = ENDPOINTS.fetch(name)
127
+ options = (options || {}).transform_keys(&:to_sym)
128
+ method = (options[:method] || endpoint[:methods].first).to_s.upcase
129
+ unless endpoint[:methods].include?(method)
130
+ raise ConfigurationError, "#{endpoint[:path]} supports #{endpoint[:methods].join(', ')}, not #{method}."
131
+ end
132
+
133
+ params = params.transform_keys(&:to_s)
134
+ unknown = params.keys - endpoint[:params]
135
+ unless unknown.empty?
136
+ raise SerializationError.new(
137
+ "Unknown parameter(s) for #{name}: #{unknown.join(', ')}. Use #request for undocumented parameters.",
138
+ unknown.first
139
+ )
140
+ end
141
+ endpoint[:required].each do |required|
142
+ raise SerializationError.new("The \"#{required}\" parameter is required.", required) if params[required].nil?
143
+ end
144
+ send_request(method, endpoint[:path], params, options, endpoint[:response])
145
+ end
146
+
147
+ def send_request(method, path, params, options, expected)
148
+ raise ConfigurationError, "Unsupported HTTP method: #{method.inspect}." unless %w[GET POST].include?(method)
149
+ raise ConfigurationError, 'The request path must start with "/".' unless path.is_a?(String) && path.start_with?("/")
150
+
151
+ path, pairs, body = Serialization.serialize(method, path, params)
152
+ url = @base_url + path
153
+ url += "?#{Serialization.encode_query(pairs)}" unless pairs.empty?
154
+ headers = {
155
+ "Accept" => "application/json, */*;q=0.8",
156
+ "User-Agent" => "veille-ruby/#{VERSION}"
157
+ }.merge(@headers).merge((options[:headers] || {}).to_h { |k, v| [k.to_s, v.to_s] })
158
+ headers["x-api-key"] = @api_key
159
+ payload = nil
160
+ if body
161
+ headers["Content-Type"] = "application/json"
162
+ payload = JSON.generate(body)
163
+ end
164
+
165
+ max_retries = options[:max_retries] || @max_retries
166
+ attempt = 0
167
+ loop do
168
+ begin
169
+ status, response_headers, response_body = @transport.call(
170
+ method: method, url: url, headers: headers, body: payload, timeout: options[:timeout] || @timeout
171
+ )
172
+ rescue Timeout::Error => e
173
+ raise TransportError, "Veille request timed out: #{e.message}"
174
+ rescue StandardError => e
175
+ raise TransportError, "Veille request failed before an HTTP response was received: #{e.message}" if attempt >= max_retries
176
+
177
+ sleep(retry_delay(attempt, nil))
178
+ attempt += 1
179
+ next
180
+ end
181
+ raw = build_raw_response(status, response_headers, response_body)
182
+ return decode(raw, expected) if (200..299).cover?(raw.status)
183
+ raise api_error(raw) unless retryable?(raw) && attempt < max_retries
184
+
185
+ sleep(retry_delay(attempt, raw.headers["retry-after"]&.first))
186
+ attempt += 1
187
+ end
188
+ end
189
+
190
+ # Server errors and plan rate limits (401/429 "Rate limit exceeded") are retried; per-key quotas are not.
191
+ def retryable?(raw)
192
+ return true if RETRYABLE_STATUSES.include?(raw.status)
193
+ return false unless [401, 429].include?(raw.status)
194
+
195
+ message = api_error(raw).error_message.to_s.downcase
196
+ return false if message.include?("quota")
197
+
198
+ message.include?("rate limit") || (raw.status == 429 && message.empty?)
199
+ end
200
+
201
+ def retry_delay(attempt, retry_after)
202
+ seconds = Float(retry_after, exception: false) if retry_after
203
+ return [seconds, 60].min if seconds && seconds >= 0
204
+
205
+ [@retry_delay * (RETRY_BACKOFF_FACTOR**attempt), MAX_RETRY_DELAY].min
206
+ end
207
+
208
+ def build_raw_response(status, headers, body)
209
+ normalized = {}
210
+ (headers || {}).each { |name, values| normalized[name.to_s.downcase] = Array(values).map(&:to_s).freeze }
211
+ RawResponse.new(
212
+ status: status.to_i,
213
+ headers: normalized.freeze,
214
+ content_type: normalized["content-type"]&.first,
215
+ body: body.to_s.b
216
+ )
217
+ end
218
+
219
+ def api_error(raw)
220
+ parsed = parse_json(raw.body)
221
+ message = nil
222
+ if parsed.is_a?(Hash)
223
+ ERROR_MESSAGE_KEYS.each do |key|
224
+ value = parsed[key]
225
+ if value.is_a?(String) && !value.empty?
226
+ message = value
227
+ break
228
+ end
229
+ end
230
+ end
231
+ APIError.new(raw_response: raw, response_body: parsed == NOT_JSON ? nil : parsed, error_message: message)
232
+ end
233
+
234
+ def decode(raw, expected)
235
+ unless json_media_type?(raw.content_type)
236
+ if expected == :json
237
+ raise ResponseDecodingError.new("Veille returned a successful non-JSON response for a JSON operation.", raw)
238
+ end
239
+
240
+ return BinaryResponse.new(bytes: raw.body, content_type: raw.content_type, raw_response: raw)
241
+ end
242
+ data = parse_json(raw.body)
243
+ if data == NOT_JSON
244
+ raise ResponseDecodingError.new("Veille returned a successful JSON response that could not be decoded.", raw)
245
+ end
246
+
247
+ JsonResponse.new(data: data, raw_response: raw)
248
+ end
249
+
250
+ NOT_JSON = Object.new.freeze
251
+ private_constant :NOT_JSON
252
+
253
+ def parse_json(body)
254
+ JSON.parse(body.dup.force_encoding(Encoding::UTF_8))
255
+ rescue JSON::ParserError, EncodingError
256
+ NOT_JSON
257
+ end
258
+
259
+ def json_media_type?(content_type)
260
+ return false if content_type.nil?
261
+
262
+ media_type = content_type.split(";", 2).first.strip.downcase
263
+ media_type == "application/json" || media_type.end_with?("+json")
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Generated from the Veille OpenAPI document. Do not edit by hand.
4
+
5
+ module Veille
6
+ # Path, methods, response kind and parameter names of every documented operation.
7
+ ENDPOINTS = {
8
+ "intelligence_domain" => {
9
+ path: "/v1/intelligence/domain",
10
+ methods: ["GET"].freeze,
11
+ response: :json,
12
+ required: ["query"].freeze,
13
+ params: ["query", "certificate_transparency", "fields", "is_async", "soft_fail"].freeze
14
+ }.freeze,
15
+ "intelligence_email" => {
16
+ path: "/v1/intelligence/email",
17
+ methods: ["GET"].freeze,
18
+ response: :json,
19
+ required: ["query"].freeze,
20
+ params: ["query", "fields", "is_async", "soft_fail"].freeze
21
+ }.freeze,
22
+ "intelligence_emailq" => {
23
+ path: "/v1/intelligence/emailq",
24
+ methods: ["GET"].freeze,
25
+ response: :json,
26
+ required: ["query"].freeze,
27
+ params: ["query", "fields", "is_async", "soft_fail"].freeze
28
+ }.freeze,
29
+ "intelligence_ip" => {
30
+ path: "/v1/intelligence/ip",
31
+ methods: ["GET"].freeze,
32
+ response: :json,
33
+ required: ["query"].freeze,
34
+ params: ["query", "fields", "is_async", "soft_fail"].freeze
35
+ }.freeze,
36
+ "intelligence_phone" => {
37
+ path: "/v1/intelligence/phone",
38
+ methods: ["GET"].freeze,
39
+ response: :json,
40
+ required: ["query"].freeze,
41
+ params: ["query", "fields", "is_async", "soft_fail"].freeze
42
+ }.freeze,
43
+ "jobs" => {
44
+ path: "/v1/jobs/{id}",
45
+ methods: ["GET"].freeze,
46
+ response: :json,
47
+ required: ["id"].freeze,
48
+ params: ["id"].freeze
49
+ }.freeze,
50
+ "stats" => {
51
+ path: "/v1/stats",
52
+ methods: ["GET"].freeze,
53
+ response: :json,
54
+ required: [].freeze,
55
+ params: ["engine", "path"].freeze
56
+ }.freeze,
57
+ "usage" => {
58
+ path: "/v1/usage",
59
+ methods: ["GET"].freeze,
60
+ response: :json,
61
+ required: [].freeze,
62
+ params: [].freeze
63
+ }.freeze,
64
+ "vat" => {
65
+ path: "/v1/vat",
66
+ methods: ["GET"].freeze,
67
+ response: :json,
68
+ required: ["query"].freeze,
69
+ params: ["query", "fields", "is_async", "soft_fail"].freeze
70
+ }.freeze,
71
+ "vat_iban" => {
72
+ path: "/v1/vat/iban",
73
+ methods: ["GET"].freeze,
74
+ response: :json,
75
+ required: ["query"].freeze,
76
+ params: ["query", "fields", "is_async", "soft_fail"].freeze
77
+ }.freeze,
78
+ "vat_rates" => {
79
+ path: "/v1/vat/rates",
80
+ methods: ["GET"].freeze,
81
+ response: :json,
82
+ required: [].freeze,
83
+ params: ["base", "symbols", "fields", "is_async", "soft_fail"].freeze
84
+ }.freeze
85
+ }.freeze
86
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Veille
4
+ # Base class for every SDK-defined error.
5
+ class Error < StandardError; end
6
+
7
+ # The client configuration is missing or unusable.
8
+ class ConfigurationError < Error; end
9
+
10
+ # A request value cannot be represented safely on the wire.
11
+ class SerializationError < Error
12
+ attr_reader :param
13
+
14
+ def initialize(message, param = nil)
15
+ super(message)
16
+ @param = param
17
+ end
18
+ end
19
+
20
+ # The request failed before a complete HTTP response was received.
21
+ class TransportError < Error; end
22
+
23
+ # Veille returned a non-success HTTP status.
24
+ class APIError < Error
25
+ attr_reader :status, :error_message, :response_body, :raw_response
26
+
27
+ def initialize(raw_response:, response_body: nil, error_message: nil)
28
+ super(error_message || "Veille API request failed with status #{raw_response.status}.")
29
+ @status = raw_response.status
30
+ @error_message = error_message
31
+ @response_body = response_body
32
+ @raw_response = raw_response
33
+ end
34
+ end
35
+
36
+ # A successful response could not be decoded as the documented representation.
37
+ class ResponseDecodingError < Error
38
+ attr_reader :raw_response
39
+
40
+ def initialize(message, raw_response)
41
+ super(message)
42
+ @raw_response = raw_response
43
+ end
44
+ end
45
+
46
+ # An async job did not reach a terminal status before the deadline.
47
+ class JobTimeoutError < Error
48
+ attr_reader :job_id, :last_response
49
+
50
+ def initialize(job_id, last_response = nil)
51
+ super("Job #{job_id} did not finish before the deadline.")
52
+ @job_id = job_id
53
+ @last_response = last_response
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Generated from the Veille OpenAPI document. Do not edit by hand.
4
+
5
+ module Veille
6
+ # One method per documented endpoint. Mixed into {Client}.
7
+ module Operations
8
+ # Domain Validation. Check any domain instantly. Detects disposable email providers, public webmail, relay services, and education domains. Returns DNS health with MX provider detection, domain history, and optional SSL/TLS certificate transparency. Credit cost: 2 credits per call.
9
+ #
10
+ # +GET /v1/intelligence/domain+
11
+ #
12
+ # @param query [String] (required) The domain to validate. Accepts raw domains (tempmail.org), full URLs (https://www.tempmail.org/page), or domains with www. prefix, all are normalized automatically.
13
+ # @param certificate_transparency [Boolean] Enable Certificate Transparency lookup. Returns SSL/TLS certificate history, current certificate, issuers, and all Subject Alternative Names (SANs) ever associated with the domain. Adds 1-5 seconds o…
14
+ # @param fields [String] Comma-separated list of fields to keep in the response, with dot notation for nested fields (e.g. name,location.city). Does not change the credit cost.
15
+ # @param is_async [Boolean] Process the request asynchronously: the response is a job_id to poll on /v1/jobs/{id} (see waitForJob).
16
+ # @param soft_fail [Boolean] Return HTTP 200 even when an error occurs; the error is then in the response body.
17
+ # @param request_options [Hash, nil] +:method+ ("GET"/"POST"), +:timeout+ (seconds), +:headers+
18
+ # @return [JsonResponse]
19
+ def intelligence_domain(request_options: nil, **params)
20
+ call_operation("intelligence_domain", params, request_options)
21
+ end
22
+
23
+ # Email Validation. Validate any email address in a single call. Detects disposable and temporary inboxes, identifies role accounts, public webmail, relay services, and returns a 0-100 risk score. For deep deliverability verification (SMTP check, catch-all de… Credit cost: 3 credits per call.
24
+ #
25
+ # +GET /v1/intelligence/email+
26
+ #
27
+ # @param query [String] (required) The email address to validate (e.g. john@example.com). Automatically normalized to lowercase.
28
+ # @param fields [String] Comma-separated list of fields to keep in the response, with dot notation for nested fields (e.g. name,location.city). Does not change the credit cost.
29
+ # @param is_async [Boolean] Process the request asynchronously: the response is a job_id to poll on /v1/jobs/{id} (see waitForJob).
30
+ # @param soft_fail [Boolean] Return HTTP 200 even when an error occurs; the error is then in the response body.
31
+ # @param request_options [Hash, nil] +:method+ ("GET"/"POST"), +:timeout+ (seconds), +:headers+
32
+ # @return [JsonResponse]
33
+ def intelligence_email(request_options: nil, **params)
34
+ call_operation("intelligence_email", params, request_options)
35
+ end
36
+
37
+ # Email Qualification. Deep email deliverability verification. Performs full Email Validation plus SMTP inbox check, catch-all detection, and returns a qualification verdict (valid, invalid, risky, disposable, unknown). Credit cost: 5 credits per call.
38
+ #
39
+ # +GET /v1/intelligence/emailq+
40
+ #
41
+ # @param query [String] (required) The email address to qualify (e.g. john@example.com). Automatically normalized to lowercase.
42
+ # @param fields [String] Comma-separated list of fields to keep in the response, with dot notation for nested fields (e.g. name,location.city). Does not change the credit cost.
43
+ # @param is_async [Boolean] Process the request asynchronously: the response is a job_id to poll on /v1/jobs/{id} (see waitForJob).
44
+ # @param soft_fail [Boolean] Return HTTP 200 even when an error occurs; the error is then in the response body.
45
+ # @param request_options [Hash, nil] +:method+ ("GET"/"POST"), +:timeout+ (seconds), +:headers+
46
+ # @return [JsonResponse]
47
+ def intelligence_emailq(request_options: nil, **params)
48
+ call_operation("intelligence_emailq", params, request_options)
49
+ end
50
+
51
+ # IP Reputation. Assess the reputation of any IP address. Detects VPNs, proxies, Tor exit nodes, datacenter hosting, abusive traffic, and bots. Returns geolocation, network owner, and a 0-100 threat score. Credit cost: 2 credits per call.
52
+ #
53
+ # +GET /v1/intelligence/ip+
54
+ #
55
+ # @param query [String] (required) The IP address to analyze (IPv4 or IPv6).
56
+ # @param fields [String] Comma-separated list of fields to keep in the response, with dot notation for nested fields (e.g. name,location.city). Does not change the credit cost.
57
+ # @param is_async [Boolean] Process the request asynchronously: the response is a job_id to poll on /v1/jobs/{id} (see waitForJob).
58
+ # @param soft_fail [Boolean] Return HTTP 200 even when an error occurs; the error is then in the response body.
59
+ # @param request_options [Hash, nil] +:method+ ("GET"/"POST"), +:timeout+ (seconds), +:headers+
60
+ # @return [JsonResponse]
61
+ def intelligence_ip(request_options: nil, **params)
62
+ call_operation("intelligence_ip", params, request_options)
63
+ end
64
+
65
+ # Phone Validation. Validate any phone number in E.164 format. Detects disposable and virtual numbers, identifies carrier and line type (mobile/fixed/VoIP), returns country and timezone, and computes a 0-100 risk score. Credit cost: 1 credit per call.
66
+ #
67
+ # +GET /v1/intelligence/phone+
68
+ #
69
+ # @param query [String] (required) The phone number to validate in E.164 format (must start with +, e.g. +33612345678).
70
+ # @param fields [String] Comma-separated list of fields to keep in the response, with dot notation for nested fields (e.g. name,location.city). Does not change the credit cost.
71
+ # @param is_async [Boolean] Process the request asynchronously: the response is a job_id to poll on /v1/jobs/{id} (see waitForJob).
72
+ # @param soft_fail [Boolean] Return HTTP 200 even when an error occurs; the error is then in the response body.
73
+ # @param request_options [Hash, nil] +:method+ ("GET"/"POST"), +:timeout+ (seconds), +:headers+
74
+ # @return [JsonResponse]
75
+ def intelligence_phone(request_options: nil, **params)
76
+ call_operation("intelligence_phone", params, request_options)
77
+ end
78
+
79
+ # Jobs. Poll the status, progress (0–100) and result of a job started with is_async=true. Polling is free. Poll until status is terminal: CompletedJob, Error, NotFound or BadRequest.
80
+ #
81
+ # +GET /v1/jobs/{id}+
82
+ #
83
+ # @param id [String] (required) The job_id returned by an endpoint called with is_async=true.
84
+ # @param request_options [Hash, nil] +:method+ ("GET"/"POST"), +:timeout+ (seconds), +:headers+
85
+ # @return [JsonResponse]
86
+ def jobs(request_options: nil, **params)
87
+ call_operation("jobs", params, request_options)
88
+ end
89
+
90
+ # Stats. Infrastructure health for an engine or endpoint over the last hour. Not scoped to your account or tenant. Enabled per account: HTTP 403 means contact support to activate it.
91
+ #
92
+ # +GET /v1/stats+
93
+ #
94
+ # @param engine [String] API family to check, for example vat. Provide at least engine or path.
95
+ # @param path [String] Specific endpoint to check, for example /v1/vat. If you also pass engine, use an endpoint from that API.
96
+ # @param request_options [Hash, nil] +:method+ ("GET"/"POST"), +:timeout+ (seconds), +:headers+
97
+ # @return [JsonResponse]
98
+ def stats(request_options: nil, **params)
99
+ call_operation("stats", params, request_options)
100
+ end
101
+
102
+ # Usage. Retrieve real-time usage statistics and credit balance for your Veille account.
103
+ #
104
+ # +GET /v1/usage+
105
+ #
106
+ # @param request_options [Hash, nil] +:method+ ("GET"/"POST"), +:timeout+ (seconds), +:headers+
107
+ # @return [JsonResponse]
108
+ def usage(request_options: nil, **params)
109
+ call_operation("usage", params, request_options)
110
+ end
111
+
112
+ # VAT Validation. Validate a European VAT number against the VIES database. Returns company name, registered address, and validity status for any EU member state. Credit cost: 1 credit per call.
113
+ #
114
+ # +GET /v1/vat+
115
+ #
116
+ # @param query [String] (required) The VAT number to validate (e.g. FR40303265045).
117
+ # @param fields [String] Comma-separated list of fields to keep in the response, with dot notation for nested fields (e.g. name,location.city). Does not change the credit cost.
118
+ # @param is_async [Boolean] Process the request asynchronously: the response is a job_id to poll on /v1/jobs/{id} (see waitForJob).
119
+ # @param soft_fail [Boolean] Return HTTP 200 even when an error occurs; the error is then in the response body.
120
+ # @param request_options [Hash, nil] +:method+ ("GET"/"POST"), +:timeout+ (seconds), +:headers+
121
+ # @return [JsonResponse]
122
+ def vat(request_options: nil, **params)
123
+ call_operation("vat", params, request_options)
124
+ end
125
+
126
+ # IBAN Validation. Validate any IBAN and retrieve associated bank details. Returns the BIC/SWIFT code, bank name, country, BBAN structural breakdown, and SEPA zone membership. Credit cost: 1 credit per call.
127
+ #
128
+ # +GET /v1/vat/iban+
129
+ #
130
+ # @param query [String] (required) The IBAN to validate (e.g. FR7630006000011234567890189).
131
+ # @param fields [String] Comma-separated list of fields to keep in the response, with dot notation for nested fields (e.g. name,location.city). Does not change the credit cost.
132
+ # @param is_async [Boolean] Process the request asynchronously: the response is a job_id to poll on /v1/jobs/{id} (see waitForJob).
133
+ # @param soft_fail [Boolean] Return HTTP 200 even when an error occurs; the error is then in the response body.
134
+ # @param request_options [Hash, nil] +:method+ ("GET"/"POST"), +:timeout+ (seconds), +:headers+
135
+ # @return [JsonResponse]
136
+ def vat_iban(request_options: nil, **params)
137
+ call_operation("vat_iban", params, request_options)
138
+ end
139
+
140
+ # Exchange Rates. Get current ECB exchange rates for any supported base currency. Optionally filter to specific target currencies. Updated daily from the European Central Bank. Credit cost: 1 credit per call.
141
+ #
142
+ # +GET /v1/vat/rates+
143
+ #
144
+ # @param base [String] Base currency ISO 4217 code (defaults to EUR).
145
+ # @param symbols [String] Comma-separated target currency codes to filter (e.g. USD,GBP,JPY).
146
+ # @param fields [String] Comma-separated list of fields to keep in the response, with dot notation for nested fields (e.g. name,location.city). Does not change the credit cost.
147
+ # @param is_async [Boolean] Process the request asynchronously: the response is a job_id to poll on /v1/jobs/{id} (see waitForJob).
148
+ # @param soft_fail [Boolean] Return HTTP 200 even when an error occurs; the error is then in the response body.
149
+ # @param request_options [Hash, nil] +:method+ ("GET"/"POST"), +:timeout+ (seconds), +:headers+
150
+ # @return [JsonResponse]
151
+ def vat_rates(request_options: nil, **params)
152
+ call_operation("vat_rates", params, request_options)
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Veille
4
+ # Buffered HTTP response: status, lower-cased multi-value headers, content type and body bytes.
5
+ RawResponse = Struct.new(:status, :headers, :content_type, :body, keyword_init: true)
6
+
7
+ # Outcome of one Client#batch item: +value+ when +ok?+, else +error+.
8
+ BatchResult = Struct.new(:index, :value, :error, keyword_init: true) do
9
+ def ok? = error.nil?
10
+ end
11
+
12
+ # A successful JSON response; +data+ is the parsed body (string keys).
13
+ class JsonResponse
14
+ attr_reader :data, :raw_response
15
+
16
+ def initialize(data:, raw_response:)
17
+ @data = data
18
+ @raw_response = raw_response
19
+ end
20
+
21
+ def kind = :json
22
+ def json? = true
23
+ def binary? = false
24
+ end
25
+
26
+ # A successful non-JSON response (image, PDF, HTML…).
27
+ class BinaryResponse
28
+ attr_reader :bytes, :content_type, :raw_response
29
+
30
+ def initialize(bytes:, content_type:, raw_response:)
31
+ @bytes = bytes
32
+ @content_type = content_type
33
+ @raw_response = raw_response
34
+ end
35
+
36
+ def kind = :binary
37
+ def json? = false
38
+ def binary? = true
39
+
40
+ # Writes the body to +path+ and returns the number of bytes written.
41
+ def save(path)
42
+ File.binwrite(path, bytes)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "uri"
5
+
6
+ module Veille
7
+ # Request serialization: path filling, query pairs and JSON bodies.
8
+ module Serialization
9
+ QUERY_STRING_PARAMS = %w[fields is_async soft_fail].freeze
10
+
11
+ module_function
12
+
13
+ # Fills {name} path segments, then returns [path, query_pairs, body].
14
+ # nil values are omitted; false and 0 are sent.
15
+ def serialize(method, path_template, params)
16
+ raise SerializationError, "Request parameters must be a Hash." unless params.nil? || params.is_a?(Hash)
17
+
18
+ remaining = {}
19
+ (params || {}).each { |name, value| remaining[name.to_s] = value unless value.nil? }
20
+ path = path_template.gsub(/\{([^}]+)\}/) do
21
+ name = Regexp.last_match(1)
22
+ raise SerializationError.new("The \"#{name}\" path parameter is required.", name) unless remaining.key?(name)
23
+
24
+ URI.encode_www_form_component(wire_string(name, remaining.delete(name))).gsub("+", "%20")
25
+ end
26
+
27
+ if method == "POST"
28
+ # The API reads the global parameters from the query string, whatever the method.
29
+ globals = QUERY_STRING_PARAMS.filter_map { |name| [name, wire_string(name, remaining.delete(name))] if remaining.key?(name) }
30
+ return [path, globals, remaining]
31
+ end
32
+
33
+ pairs = []
34
+ remaining.each do |name, value|
35
+ if value.is_a?(Array)
36
+ value.each { |item| pairs << [name, wire_string(name, item)] unless item.nil? }
37
+ else
38
+ pairs << [name, wire_string(name, value)]
39
+ end
40
+ end
41
+ [path, pairs, nil]
42
+ end
43
+
44
+ def wire_string(name, value)
45
+ case value
46
+ when String then value
47
+ when true, false, Integer, Symbol then value.to_s
48
+ when Float
49
+ raise SerializationError.new("The \"#{name}\" parameter must be a finite number.", name) unless value.finite?
50
+
51
+ value == value.floor ? value.to_i.to_s : value.to_s
52
+ when Hash, Array then JSON.generate(value)
53
+ else
54
+ raise SerializationError.new("The \"#{name}\" parameter has an unsupported type (#{value.class}).", name)
55
+ end
56
+ end
57
+
58
+ def encode_query(pairs)
59
+ pairs.map { |name, value| "#{escape(name)}=#{escape(value)}" }.join("&")
60
+ end
61
+
62
+ def escape(value)
63
+ URI.encode_www_form_component(value).gsub("+", "%20")
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "uri"
5
+
6
+ module Veille
7
+ # Default transport on Net::HTTP. A custom transport is any object with
8
+ # +call(method:, url:, headers:, body:, timeout:)+ returning +[status, headers, body]+,
9
+ # where headers is a Hash of lower-cased names to arrays of values.
10
+ class NetHTTPTransport
11
+ def call(method:, url:, headers:, body:, timeout:)
12
+ uri = URI(url)
13
+ request = (method == "POST" ? Net::HTTP::Post : Net::HTTP::Get).new(uri)
14
+ headers.each { |name, value| request[name] = value }
15
+ request.body = body if body
16
+ http = Net::HTTP.new(uri.host, uri.port)
17
+ http.use_ssl = uri.scheme == "https"
18
+ if timeout
19
+ http.open_timeout = timeout
20
+ http.read_timeout = timeout
21
+ http.write_timeout = timeout
22
+ end
23
+ response = http.start { |connection| connection.request(request) }
24
+ [response.code.to_i, response.to_hash, response.body || ""]
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Veille
4
+ VERSION = "0.1.0"
5
+ end
data/lib/veille.rb ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "veille/version"
4
+ require_relative "veille/errors"
5
+ require_relative "veille/responses"
6
+ require_relative "veille/serialization"
7
+ require_relative "veille/transport"
8
+ require_relative "veille/endpoints"
9
+ require_relative "veille/operations"
10
+ require_relative "veille/client"
11
+
12
+ # Official Ruby SDK for the Veille API (https://www.veille.io).
13
+ module Veille
14
+ end
data/sig/veille.rbs ADDED
@@ -0,0 +1,89 @@
1
+ module Veille
2
+ VERSION: String
3
+ ENDPOINTS: Hash[String, Hash[Symbol, untyped]]
4
+
5
+ class Error < StandardError
6
+ end
7
+ class ConfigurationError < Error
8
+ end
9
+ class SerializationError < Error
10
+ attr_reader param: String?
11
+ def initialize: (String message, ?String? param) -> void
12
+ end
13
+ class TransportError < Error
14
+ end
15
+ class APIError < Error
16
+ attr_reader status: Integer
17
+ attr_reader error_message: String?
18
+ attr_reader response_body: untyped
19
+ attr_reader raw_response: RawResponse
20
+ end
21
+ class ResponseDecodingError < Error
22
+ attr_reader raw_response: RawResponse
23
+ end
24
+ class JobTimeoutError < Error
25
+ attr_reader job_id: String
26
+ attr_reader last_response: JsonResponse?
27
+ end
28
+
29
+ class RawResponse < Struct[untyped]
30
+ attr_accessor status: Integer
31
+ attr_accessor headers: Hash[String, Array[String]]
32
+ attr_accessor content_type: String?
33
+ attr_accessor body: String
34
+ end
35
+
36
+ class JsonResponse
37
+ attr_reader data: untyped
38
+ attr_reader raw_response: RawResponse
39
+ def kind: () -> :json
40
+ def json?: () -> true
41
+ def binary?: () -> false
42
+ end
43
+ class BinaryResponse
44
+ attr_reader bytes: String
45
+ attr_reader content_type: String?
46
+ attr_reader raw_response: RawResponse
47
+ def kind: () -> :binary
48
+ def json?: () -> false
49
+ def binary?: () -> true
50
+ def save: (String path) -> Integer
51
+ end
52
+
53
+ class BatchResult < Struct[untyped]
54
+ attr_accessor index: Integer
55
+ attr_accessor value: untyped
56
+ attr_accessor error: StandardError?
57
+ def ok?: () -> bool
58
+ end
59
+
60
+ type response = JsonResponse | BinaryResponse
61
+ type request_options = { ?method: String, ?timeout: Numeric?, ?headers: Hash[String, String], ?max_retries: Integer }
62
+
63
+ module Operations
64
+ def intelligence_domain: (query: String, ?certificate_transparency: bool, ?fields: String, ?is_async: bool, ?soft_fail: bool, ?request_options: request_options?) -> JsonResponse
65
+ def intelligence_email: (query: String, ?fields: String, ?is_async: bool, ?soft_fail: bool, ?request_options: request_options?) -> JsonResponse
66
+ def intelligence_emailq: (query: String, ?fields: String, ?is_async: bool, ?soft_fail: bool, ?request_options: request_options?) -> JsonResponse
67
+ def intelligence_ip: (query: String, ?fields: String, ?is_async: bool, ?soft_fail: bool, ?request_options: request_options?) -> JsonResponse
68
+ def intelligence_phone: (query: String, ?fields: String, ?is_async: bool, ?soft_fail: bool, ?request_options: request_options?) -> JsonResponse
69
+ def jobs: (id: String, ?request_options: request_options?) -> JsonResponse
70
+ def stats: (?engine: String, ?path: String, ?request_options: request_options?) -> JsonResponse
71
+ def usage: (?request_options: request_options?) -> JsonResponse
72
+ def vat: (query: String, ?fields: String, ?is_async: bool, ?soft_fail: bool, ?request_options: request_options?) -> JsonResponse
73
+ def vat_iban: (query: String, ?fields: String, ?is_async: bool, ?soft_fail: bool, ?request_options: request_options?) -> JsonResponse
74
+ def vat_rates: (?base: String, ?symbols: String, ?fields: String, ?is_async: bool, ?soft_fail: bool, ?request_options: request_options?) -> JsonResponse
75
+ end
76
+
77
+ class Client
78
+ include Operations
79
+ DEFAULT_CONCURRENCY: Integer
80
+ MAX_RETRIES: Integer
81
+ RETRY_BACKOFF_FACTOR: Float
82
+ DEFAULT_RETRY_DELAY: Float
83
+ DEFAULT_TIMEOUT: Integer
84
+ def initialize: (?api_key: String?, ?base_url: String, ?timeout: Numeric?, ?headers: Hash[untyped, untyped], ?max_retries: Integer, ?retry_delay: Numeric, ?transport: untyped) -> void
85
+ def request: (String | Symbol method, String path, ?Hash[untyped, untyped]? params, ?timeout: Numeric?, ?headers: Hash[untyped, untyped]?, ?max_retries: Integer?) -> response
86
+ def batch: [I, T] (Enumerable[I] items, ?concurrency: Integer) { (I item) -> T } -> Array[BatchResult]
87
+ def wait_for_job: (String job_id, ?interval: Numeric, ?timeout: Numeric) ?{ (untyped job) -> void } -> JsonResponse
88
+ end
89
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: veille
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Veille
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-11-06 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Validate emails, domains, IPs, VAT numbers and IBANs in milliseconds
14
+ to block fake sign-ups, bots and fraud.
15
+ email:
16
+ - contact@veille.io
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - LICENSE
22
+ - README.md
23
+ - examples/README.md
24
+ - examples/json.rb
25
+ - examples/request.rb
26
+ - examples/wait_for_job.rb
27
+ - lib/veille.rb
28
+ - lib/veille/client.rb
29
+ - lib/veille/endpoints.rb
30
+ - lib/veille/errors.rb
31
+ - lib/veille/operations.rb
32
+ - lib/veille/responses.rb
33
+ - lib/veille/serialization.rb
34
+ - lib/veille/transport.rb
35
+ - lib/veille/version.rb
36
+ - sig/veille.rbs
37
+ homepage: https://www.veille.io
38
+ licenses:
39
+ - MIT
40
+ metadata:
41
+ bug_tracker_uri: https://github.com/veilleio/veille-ruby/issues
42
+ documentation_uri: https://app.veille.io/openapi
43
+ homepage_uri: https://www.veille.io
44
+ rubygems_mfa_required: 'true'
45
+ source_code_uri: https://github.com/veilleio/veille-ruby
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '3.1'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubygems_version: 3.5.22
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: Official Ruby SDK for the Veille fraud prevention API.
65
+ test_files: []