serqo 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.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +37 -0
  4. data/lib/serqo.rb +86 -0
  5. metadata +42 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7dc2911cd34ade0dc1143e6df69d3cb81385d3f487e438387900a3d9334bd556
4
+ data.tar.gz: e308b2916891f8ba3fbedd8e6b6ef4ca7862a7f68a08a952c848ab184a4487ac
5
+ SHA512:
6
+ metadata.gz: 875852674ccf27cf1f02e065b3d0025223e666cef2884c0e1dabd3a61f1cfa3a30b725e80a595ebd730a09d8129d04a2cc5df5a9f995ba9ffffd4f92c88d15bc
7
+ data.tar.gz: 21a68d0d8289d4cd83ac22dc8e55b8a163b4e08579572aac91aa9f4ec6dfd24affd0f8d00128d87254512c05b385d0c90a390da85bb0d949ec0860b6501046cc
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Serqo
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,37 @@
1
+ # Serqo Ruby SDK
2
+
3
+ Official Ruby client for the Serqo Search API. Requires Ruby 3.1 or later and uses the standard library.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ gem install serqo
9
+ ```
10
+
11
+ ## Quick start
12
+
13
+ ```ruby
14
+ require "serqo"
15
+
16
+ client = Serqo::Client.new(api_key: ENV.fetch("SERQO_API_KEY"))
17
+ response = client.search({
18
+ "q" => "espresso machines",
19
+ "engine" => "google",
20
+ "type" => "web",
21
+ "gl" => "us",
22
+ "hl" => "en"
23
+ })
24
+
25
+ puts response.dig("data", "results")
26
+ puts "Request ID: #{response.fetch('requestId')}"
27
+ ```
28
+
29
+ Keep the API key in server-side configuration. Methods return the full response envelope as hashes.
30
+
31
+ ## Methods and errors
32
+
33
+ - `search(query, idempotency_key: nil)` sends `POST /v1/search`.
34
+ - `engines`, `me`, and `health` call the corresponding endpoints.
35
+ - API failures raise `Serqo::Error` with `status`, `code`, `request_id`, `retryable`, `retry_after_ms`, and `details`.
36
+ - The client never retries searches automatically. Reuse an idempotency key only with identical parameters.
37
+ - See the [shared API reference](API.md) for all search parameters, response fields, and pagination behavior.
data/lib/serqo.rb ADDED
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "net/http"
5
+ require "uri"
6
+
7
+ module Serqo
8
+ # Structured API error. Inspect status, code, request_id, and retryable.
9
+ class Error < StandardError
10
+ attr_reader :status, :code, :request_id, :retryable, :retry_after_ms, :details
11
+
12
+ def initialize(status:, body: {}, request_id: nil)
13
+ @status = status
14
+ @code = body["code"] || "UNKNOWN_ERROR"
15
+ @request_id = request_id
16
+ @retryable = body.fetch("retryable", false)
17
+ @retry_after_ms = body["retryAfterMs"]
18
+ @details = body["details"]
19
+ super(body["message"] || "Serqo API request failed with HTTP #{status}")
20
+ end
21
+ end
22
+
23
+ # Small standard-library client for the public Serqo Search API.
24
+ class Client
25
+ DEFAULT_BASE_URL = "https://api.serqo.io"
26
+
27
+ # @param api_key [String] Secret key; keep it in trusted server config.
28
+ # @param base_url [String] API origin without a trailing slash.
29
+ # @param timeout [Integer] Read timeout in seconds (default 60).
30
+ def initialize(api_key:, base_url: DEFAULT_BASE_URL, timeout: 60)
31
+ raise ArgumentError, "api_key is required" if api_key.strip.empty?
32
+
33
+ @api_key = api_key
34
+ @base_url = base_url.sub(%r{/+\z}, "")
35
+ @timeout = timeout
36
+ end
37
+
38
+ # Run one search. Pass an idempotency key only when retrying the same query.
39
+ def search(query, idempotency_key: nil)
40
+ headers = {}
41
+ headers["Idempotency-Key"] = idempotency_key if idempotency_key
42
+ request("POST", "/v1/search", body: query, headers: headers)
43
+ end
44
+
45
+ # Return the current engine and search-type capability list.
46
+ def engines
47
+ request("GET", "/v1/engines")
48
+ end
49
+
50
+ # Return account and key identifiers associated with this API key.
51
+ def me
52
+ request("GET", "/v1/me")
53
+ end
54
+
55
+ # Check the product API process without sending an API key.
56
+ def health
57
+ request("GET", "/v1/health", authenticated: false)
58
+ end
59
+
60
+ private
61
+
62
+ def request(method, path, body: nil, headers: {}, authenticated: true)
63
+ uri = URI("#{@base_url}#{path}")
64
+ request_class = method == "POST" ? Net::HTTP::Post : Net::HTTP::Get
65
+ http_request = request_class.new(uri)
66
+ http_request["Accept"] = "application/json"
67
+ http_request["x-api-key"] = @api_key if authenticated
68
+ headers.each { |name, value| http_request[name] = value }
69
+ http_request.body = JSON.generate(body) if body
70
+ http_request["Content-Type"] = "application/json" if body
71
+
72
+ response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", open_timeout: 10, read_timeout: @timeout) do |http|
73
+ http.request(http_request)
74
+ end
75
+ payload = JSON.parse(response.body)
76
+ unless response.is_a?(Net::HTTPSuccess)
77
+ error_body = payload.is_a?(Hash) ? (payload["error"] || {}) : {}
78
+ request_id = payload.is_a?(Hash) ? payload["requestId"] : nil
79
+ raise Error.new(status: response.code.to_i, body: error_body, request_id: request_id || response["x-request-id"])
80
+ end
81
+ raise Error.new(status: response.code.to_i, body: { "code" => "INVALID_RESPONSE" }) unless payload.is_a?(Hash) && payload["success"] == true
82
+
83
+ payload
84
+ end
85
+ end
86
+ end
metadata ADDED
@@ -0,0 +1,42 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: serqo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Serqo
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: A dependency-free HTTP client for the public Serqo Search API.
13
+ executables: []
14
+ extensions: []
15
+ extra_rdoc_files: []
16
+ files:
17
+ - LICENSE
18
+ - README.md
19
+ - lib/serqo.rb
20
+ licenses:
21
+ - MIT
22
+ metadata:
23
+ homepage_uri: https://serqo.io
24
+ source_code_uri: https://github.com/serqo-io/serqo-ruby
25
+ rdoc_options: []
26
+ require_paths:
27
+ - lib
28
+ required_ruby_version: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '3.1'
33
+ required_rubygems_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ requirements: []
39
+ rubygems_version: 3.6.9
40
+ specification_version: 4
41
+ summary: Official Ruby client for the Serqo Search API
42
+ test_files: []