genderapi 1.0.5 → 2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 37dbdf8388b98f265d98ac47a00be36fffba6fd5c7b1580827caebfb7ad24f34
4
- data.tar.gz: 2fb4c652c1e4c3deba6dd3e455d8d781c180f8a254d38b97590e549f27f1848d
3
+ metadata.gz: 19c443b45bcc60a40cebfd3d8ad9c961b3353b74dce046db0d66082c1a00e827
4
+ data.tar.gz: f6c444dd0d3744d885b08c809f60bb3fa89a3cd1178c59b1030a551e0da9a906
5
5
  SHA512:
6
- metadata.gz: 762244904c3ff5fa25f6a822d96f9c713c7864962a7e3e0ab2e37fe8d5783023e6a36b1d02e4b1fca258e93e176041cb4b01373795c9bcfae1fb38b24391875c
7
- data.tar.gz: 86aaf648984d64cd832f3da1c9b0098705c3c2e9a0d061e122b9b41986cfcec3f37fbfaa065bfce8611348512f53d7ca8f5c0ecb57c969fac3e08cbe00072e69
6
+ metadata.gz: 507c514f022c2db81220a69831a8409147ec517f12c4df613d0909165a0179645e1b382c4a10f22359a173a969039b6819695f8a2e4f0a8175703cb7076695af
7
+ data.tar.gz: 4040e050a3278cb070541f28c588c188980fe0ef5ff89501a242baa1cc66ea78c8672bd4369f2aa6b2f3fe83c152aa62bda407dc0bc69b20278a57c63a525a9c
data/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ ## 2.0.0 - 2026-09-30
4
+
5
+ ### Breaking
6
+
7
+ - The client now targets the GenderAPI.io V2 API (`https://api.genderapi.io/api/v2`). 1.x (V1 API) stays available and installable indefinitely (`gem install genderapi -v "~> 1.0"`); no deprecation or shutdown is planned. The source stays on the `v1` branch.
8
+ - New interface: `GenderAPI::Client#gender(type, value, country:, ai_mode:, force_to_genderize:, id:)` with `#name`, `#email` and `#username`, plus `#gender_batch(items)`, `#usage`, `#validate_phone(number, country:)`, `#capabilities` and `#error_catalog`. The V1 methods (`get_gender_by_*` and `get_gender_by_*_bulk`) have been removed.
9
+ - Responses are V2 `data`/`meta` objects wrapped in typed readers (`GenderResult`, `BatchResult`, `UsageResult`, `PhoneResult`) that keep every field. `probability` is replaced by `confidence` (0-1) and `confidence_kind`; `used_credits` is replaced by `meta.usage.charged_credits`.
10
+ - HTTP errors raise `GenderAPI::APIError` subclasses exposing `status`, `code`, `action`, `detail`, `errors`, `request_id`, `retry_after` and `billing_status`, instead of a generic `RuntimeError`.
11
+ - Ruby >= 3.0 is required. The runtime dependencies on `httparty` and `json` have been removed; the gem uses only the standard library.
12
+
13
+ ### Added
14
+
15
+ - `api_key` defaults to `ENV["GENDERAPI_API_KEY"]`. Without a key, the server applies the shared IP trial.
16
+ - Client-side validation that mirrors the V2 request schema. Invalid input raises `GenderAPI::ValidationError` without making a network request.
17
+ - Partial batch success is returned together with `failed_items` and `summary`. Batches where every item failed raise an error whose `items` holds the item outcomes.
18
+ - `UnexpectedAccessModeError` when a configured key is answered with IP-trial access.
19
+
20
+ ### Safety
21
+
22
+ - No automatic retries (including on 429 and for GET requests), no redirects followed, a 10-second default timeout, HTTPS required (plain http only for localhost tests), no request when the gem is loaded or a client is constructed, and the key is never placed in a URL, logged or shown by `inspect`.
data/README.md CHANGED
@@ -1,289 +1,263 @@
1
- # genderapi-ruby
1
+ # genderapi (Ruby)
2
2
 
3
- Official Ruby SDK for [GenderAPI.io](https://www.genderapi.io) — determine gender from **names**, **emails**, and **usernames** using AI.
3
+ Official GenderAPI.io V2 client for Ruby.
4
4
 
5
- ---
5
+ It sends names, email addresses and usernames to the [GenderAPI.io V2 API](https://www.genderapi.io/api-documentation) and returns the complete V2 response: the prediction in `data` and access and billing information in `meta`. Results are inferences, not identity verification, and they can be unknown.
6
6
 
7
- Get your Free API Key: [https://app.genderapi.io](https://app.genderapi.io)
7
+ > **Version 2.0.0 is a breaking release.** It targets the V2 API (`https://api.genderapi.io/api/v2`). 1.x (V1 API) stays available and installable indefinitely; no deprecation or shutdown is planned. To keep using it, pin 1.x with `gem install genderapi -v "~> 1.0"` (Gemfile: `gem "genderapi", "~> 1.0"`). The source stays on the [`v1` branch](https://github.com/GenderAPI/genderapi-ruby/tree/v1). See [Migrating from 1.x](#migrating-from-1x).
8
8
 
9
- ---
9
+ - Ruby >= 3.0
10
+ - No runtime dependencies (standard library `net/http` and `json`)
11
+ - **Server-side only.** Keep your API key in the server environment. Never embed it in browser, mobile or other client-side code.
10
12
 
11
- ## 🚀 Installation
12
-
13
- Add this line to your Gemfile:
13
+ ## Installation
14
14
 
15
15
  ```ruby
16
- gem 'genderapi'
16
+ # Gemfile
17
+ gem "genderapi", "~> 2.0"
17
18
  ```
18
19
 
19
- Then execute:
20
-
21
20
  ```bash
22
21
  bundle install
23
- ```
24
-
25
- Or install it manually:
26
-
27
- ```bash
22
+ # or
28
23
  gem install genderapi
29
24
  ```
30
25
 
31
- ---
32
-
33
- ## 📝 Usage
34
-
35
- ### 🔹 Get Gender by Name
26
+ ## Quick start
36
27
 
37
28
  ```ruby
38
- require 'genderapi'
29
+ require "genderapi"
39
30
 
40
- api = GenderAPI::Client.new(api_key: "YOUR_API_KEY")
31
+ # Reads ENV["GENDERAPI_API_KEY"] when api_key is not given.
32
+ client = GenderAPI::Client.new(api_key: ENV["GENDERAPI_API_KEY"])
41
33
 
42
- # Basic usage
43
- result = api.get_gender_by_name(name: "Michael")
44
- puts result
34
+ result = client.name("Andrea", country: "IT")
35
+ prediction = result.data
45
36
 
46
- # With askToAI set to true
47
- result = api.get_gender_by_name(name: "李雷", ask_to_ai: true)
48
- puts result
37
+ prediction.gender # => "female", "male" or nil
38
+ prediction.result_status # => "identified" or "unknown"
39
+ prediction.confidence # => 0..1 or nil (not a calibrated probability)
40
+ prediction.confidence_kind # => "observed_frequency", "model_reported" or nil
41
+ result.usage.charged_credits
42
+ result.usage.remaining_credits
43
+ result.request_id
49
44
  ```
50
45
 
51
- ---
46
+ Requiring the gem or constructing a client never makes a network request. Every method call makes exactly one HTTP request.
52
47
 
53
- ### 🔹 Get Gender by Email
48
+ ### Email and username
54
49
 
55
50
  ```ruby
56
- result = api.get_gender_by_email(email: "michael.smith@example.com")
57
- puts result
51
+ client.email("alex.smith@example.com")
52
+ client.username("prenses", country: "TR", force_to_genderize: true)
58
53
 
59
- # With askToAI set to true
60
- result = api.get_gender_by_email(email: "michael.smith@example.com", ask_to_ai: true)
61
- puts result
54
+ # Generic form: type is "name", "email" or "username"
55
+ client.gender("username", "michael_dev", ai_mode: "off", id: "row-17")
62
56
  ```
63
57
 
64
- ---
65
-
66
- ### 🔹 Get Gender by Username
58
+ ### Batch (1-50 items)
67
59
 
68
60
  ```ruby
69
- result = api.get_gender_by_username(username: "michael_dev")
70
- puts result
71
-
72
- # With askToAI set to true
73
- result = api.get_gender_by_username(username: "michael_dev", ask_to_ai: true)
74
- puts result
61
+ result = client.gender_batch([
62
+ { type: "name", value: "Andrea", country: "IT", id: "a-1" },
63
+ { type: "email", value: "alex@example.com", id: "a-2" },
64
+ { type: "username", value: "prenses", id: "a-3", ai_mode: "fallback", force_to_genderize: true }
65
+ ])
66
+
67
+ result.items.each do |item|
68
+ if item.success?
69
+ puts "#{item.id}: #{item.data.gender.inspect} (#{item.data.result_status})"
70
+ else
71
+ puts "#{item.id}: #{item.error.code} (#{item.error.action})"
72
+ end
73
+ end
74
+
75
+ result.summary.to_h # => {"total"=>3, "succeeded"=>2, "identified"=>1, "unknown"=>1, "failed"=>1}
76
+ result.failed_items # failed rows; partial success is returned, not raised
75
77
  ```
76
78
 
77
- ---
79
+ The server may allow fewer items than 50 (IP-trial batches allow at most 10). Split larger jobs yourself. Item ids are optional, but when you give them they must be unique and at most 64 characters.
78
80
 
79
- ### 🔹 Get Gender by Name (Bulk)
81
+ ### Usage (free)
80
82
 
81
83
  ```ruby
82
- bulk_data = [
83
- { name: "Andrea", country: "DE", id: "123" },
84
- { name: "andrea", country: "IT", id: "456" }
85
- ]
86
-
87
- result = api.get_gender_by_name_bulk(data: bulk_data)
88
- puts result
84
+ usage = client.usage
85
+ usage.data.remaining_credits
86
+ usage.data.expires_at
87
+ usage.meta.access.mode # "api_key", "ip_trial" or "unauthenticated"
89
88
  ```
90
89
 
91
- ---
92
-
93
- ### 🔹 Get Gender by Email (Bulk)
90
+ ### Phone validation
94
91
 
95
92
  ```ruby
96
- bulk_data = [
97
- { email: "john@example.com", country: "US", id: "abc123" },
98
- { email: "maria@domain.de", country: "DE", id: "def456" }
99
- ]
100
-
101
- result = api.get_gender_by_email_bulk(data: bulk_data)
102
- puts result
93
+ client.validate_phone("+1 415 555 0100")
94
+ client.validate_phone("415 555 0100", country: "US") # country is required without a leading +
103
95
  ```
104
96
 
105
- ---
97
+ This checks the number's structure, not whether a subscriber exists. It costs 1 credit, including for invalid numbers.
106
98
 
107
- ### 🔹 Get Gender by Username (Bulk)
99
+ ### Discovery (no key sent)
108
100
 
109
101
  ```ruby
110
- bulk_data = [
111
- { username: "johnwhite", country: "US", id: "u001" },
112
- { username: "maria2025", country: "DE", id: "u002" }
113
- ]
114
-
115
- result = api.get_gender_by_username_bulk(data: bulk_data)
116
- puts result
102
+ client.capabilities # GET /api/v2
103
+ client.error_catalog # GET /api/v2/errors
117
104
  ```
118
105
 
119
- ---
120
-
121
- ## 📥 API Parameters
122
-
123
- All API methods accept parameters as keyword arguments. All fields are optional except the primary identifier (name, email, or username).
124
-
125
- ---
126
-
127
- ### Name Lookup
128
-
129
- | Parameter | Type | Required | Description |
130
- |--------------------|----------|----------|-------------|
131
- | name | String | Yes | Name to query. |
132
- | country | String | No | Two-letter country code (e.g. "US"). Helps narrow down gender detection results by region. |
133
- | askToAI | Boolean | No | Default is `false`. If `true`, sends the query directly to AI for maximum accuracy, consuming 3 credits per request. If `false`, GenderAPI first tries its internal database and uses AI only if necessary, without spending 3 credits. Recommended for non-latin characters or unusual strings. |
134
- | forceToGenderize | Boolean | No | Default is `false`. When `true`, analyzes even nicknames, emojis, or unconventional strings like "spider man" instead of returning `null` for non-standard names. |
135
-
136
- ---
106
+ ## Options
107
+
108
+ ### Client
109
+
110
+ | Option | Default | Description |
111
+ | --- | --- | --- |
112
+ | `api_key` | `ENV["GENDERAPI_API_KEY"]` | Your API key. It is sent only as `Authorization: Bearer <key>`, never in a URL, and it is never logged or shown by `inspect`. |
113
+ | `base_url` | `https://api.genderapi.io/api/v2` | HTTPS is required. Plain `http://` is accepted only for `localhost`, `127.0.0.1` and `[::1]`, for tests. |
114
+ | `timeout` | `10` | Seconds allowed for each connect, write and read operation. |
115
+ | `user_agent` | `nil` | Text appended to the default `genderapi-ruby/2.0.0 (Ruby x.y.z)` User-Agent. |
116
+ | `require_api_key_access` | `true` | When a key is configured and the response reports IP-trial or unauthenticated access (the key was not accepted), raise `GenderAPI::UnexpectedAccessModeError`. The request has already been processed, so IP-trial credits may have been used. The complete result is in `error.result`, the mode in `error.access_mode`. Set `false` to return the result instead. Never applies without a key, or to `capabilities`/`error_catalog`. |
117
+
118
+ ### Prediction (`gender`, `name`, `email`, `username`, batch items)
119
+
120
+ | Ruby argument | Wire field | Description |
121
+ | --- | --- | --- |
122
+ | `type` | `type` | `"name"`, `"email"` or `"username"` |
123
+ | `value` | `value` | 1-254 characters that are not all whitespace and contain no control characters |
124
+ | `country:` | `country` | Optional uppercase ISO 3166-1 alpha-2 code such as `"US"`. Leave it out when the country is unknown. |
125
+ | `ai_mode:` | `options.ai_mode` | `"off"`, `"fallback"` or `"always"`. If you leave it out, the server uses its default: `fallback` for single requests and `off` for batch items. |
126
+ | `force_to_genderize:` | `forceToGenderize` | `true` checks the dataset first (1 credit). If that result is unknown, it uses nickname-aware AI inference (2 credits total). It cannot be combined with `ai_mode` `off` or `always`. |
127
+ | `id:` | `id` | Optional correlation id, 1-64 characters |
128
+
129
+ Invalid input raises `GenderAPI::ValidationError` (with `#field`) **before** any network request. The API remains authoritative for email syntax and ISO country membership, and it reports problems with those as HTTP 422.
130
+
131
+ Credits (server rules): a dataset result or automatic AI fallback costs 1 credit, including unknown results. `ai_mode: "always"` costs 2. A request needs a positive starting balance, and the full tariff can take the balance below zero (for example, 1 - 2 = -1).
132
+
133
+ ## Response fields
134
+
135
+ Every result wraps the parsed JSON. Typed readers are provided for the documented fields. `[]`, `dig` and `to_h` give access to all fields, including ones added in future API versions. Values are never converted: `confidence` stays on its 0-1 scale and is never turned into a percentage or probability.
136
+
137
+ | Reader | Meaning |
138
+ | --- | --- |
139
+ | `data.gender` | `"male"`, `"female"` or `nil` |
140
+ | `data.result_status` | `"identified"` (gender is set) or `"unknown"` (gender is nil). An unknown result is a successful, billed outcome. |
141
+ | `data.reason` | `nil`, `"not_found"`, `"no_name_candidate"`, `"ambiguous"` or `"insufficient_evidence"` |
142
+ | `data.confidence` / `data.confidence_kind` | Score from 0 to 1 and what it is: `observed_frequency` (dominant dataset count / total) or `model_reported` (AI score, not calibrated). Both are nil when gender is nil. |
143
+ | `data.sample_count` | Dataset sample count; nil for AI |
144
+ | `data.source` | `"dataset"`, `"ai"` or `"none"` |
145
+ | `data.name`, `data.country`, `data.country_source` | The returned name, the country and where the country came from (`dataset`, `ai_association` or nil). These fields never indicate nationality or residence. |
146
+ | `data.match` | `{"name", "method", "scope", "country"}`: the dataset candidate and lookup scope |
147
+ | `data.input` | The input as the server understood it |
148
+ | `meta.request_id`, `meta.duration_ms` | Identifier and duration of this HTTP attempt |
149
+ | `meta.access.mode` / `.reason` | `api_key`, `ip_trial` or `unauthenticated`; the trial reason, if any |
150
+ | `meta.usage.billing_status` | `not_charged`, `confirmed` or `unconfirmed` |
151
+ | `meta.usage.charged_credits` | Net charge. It is nil when billing is unconfirmed. |
152
+ | `meta.usage.remaining_credits` | Balance when the request completed. It can be negative, or nil if unknown. |
153
+ | `meta.usage.resets_at`, `.limit`, `.period_seconds` | IP-trial window (nil otherwise) |
154
+ | Batch `items[i].index`, `.id`, `.charged_credits` | Each row has exactly one of `.data` (Prediction) or `.error` (item problem) |
155
+ | Batch `meta.summary` | `total`, `succeeded`, `identified`, `unknown`, `failed` |
156
+
157
+ ## Errors
158
+
159
+ All errors inherit from `GenderAPI::Error`. Messages never include your key or input values.
160
+
161
+ | Class | When |
162
+ | --- | --- |
163
+ | `ValidationError` | Invalid arguments. No request was sent. |
164
+ | `APIError` | HTTP >= 400. Subclasses: `BadRequestError` (400), `AuthenticationError` (401), `PermissionDeniedError` (403), `NotFoundError` (404), `UnprocessableEntityError` (422), `RateLimitError` (429), `ServerError` (5xx) |
165
+ | `RedirectError` | The server answered with a 3xx. Redirects are never followed, so your key is never forwarded to another location. |
166
+ | `TransportError` / `TimeoutError` | No usable response was received. The request may still have completed and been billed. |
167
+ | `InvalidResponseError` | A 2xx response that is not the expected JSON structure |
168
+ | `UnexpectedAccessModeError` | A key was configured, but the response reports IP-trial or unauthenticated access (see `require_api_key_access`). The request has already been processed and trial credits may have been used; the complete result is in `error.result`. Do not resend automatically. |
169
+
170
+ `APIError` exposes `status`, `code` (a stable machine code; match on this, never on `detail`), `title`, `detail`, `type`, `instance`, `action`, `documentation`, `errors` (validation pointers such as `[{"pointer" => "/value", "message" => "..."}]`), `request_id` (from `meta.request_id`, the body, or the `X-Request-ID` header), `retry_after` (from the `Retry-After` header: Integer seconds, or the raw String for an HTTP date), `usage`, `billing_status`, `billing_unconfirmed?`, `items` / `data` (item outcomes when every batch item failed), `body` (parsed) and `raw_body`. Proxy errors can be non-JSON. In that case `code` is nil and `raw_body` holds the response text. The error body can contain your inputs, so inspect it securely and do not log it wholesale.
137
171
 
138
- ### Email Lookup
139
-
140
- | Parameter | Type | Required | Description |
141
- |-----------|--------|----------|-------------|
142
- | email | String | Yes | Email address to query. |
143
- | country | String | No | Two-letter country code (e.g. "US"). Helps narrow down gender detection results by region. |
144
- | askToAI | Boolean | No | Default is `false`. If `true`, sends the query directly to AI for maximum accuracy, consuming 3 credits per request. If `false`, GenderAPI first tries its internal database and uses AI only if necessary, without spending 3 credits. Recommended for non-latin characters or unusual strings. |
145
-
146
- ---
147
-
148
- ### Username Lookup
149
-
150
- | Parameter | Type | Required | Description |
151
- |--------------------|----------|----------|-------------|
152
- | username | String | Yes | Username to query. |
153
- | country | String | No | Two-letter country code (e.g. "US"). Helps narrow down gender detection results by region. |
154
- | askToAI | Boolean | No | Default is `false`. If `true`, sends the query directly to AI for maximum accuracy, consuming 3 credits per request. If `false`, GenderAPI first tries its internal database and uses AI only if necessary, without spending 3 credits. Recommended for non-latin characters or unusual strings. |
155
- | forceToGenderize | Boolean | No | Default is `false`. When `true`, analyzes even nicknames, emojis, or unconventional strings like "spider man" instead of returning `null` for non-standard names. |
156
-
157
- ---
158
-
159
- ### Name Lookup (Bulk)
160
-
161
- | Parameter | Type | Required | Description |
162
- |--------------------|----------|----------|-------------|
163
- | data | Array<Hash> | Yes | Array of objects containing name, optional country, and optional id. Limit is 100 records per request. |
164
-
165
- Each object in the data array may include:
166
- - `name`: The name to analyze (required).
167
- - `country`: Two-letter country code (optional).
168
- - `id`: Custom identifier to correlate input/output (optional).
169
-
170
- ---
171
-
172
- ### Email Lookup (Bulk)
173
-
174
- | Parameter | Type | Required | Description |
175
- |--------------------|----------|----------|-------------|
176
- | data | Array<Hash> | Yes | Array of objects containing email, optional country, and optional id. Limit is 50 records per request. |
172
+ ```ruby
173
+ begin
174
+ client.name("Andrea")
175
+ rescue GenderAPI::RateLimitError => e
176
+ # Wait e.retry_after seconds. A later request is a new, billable operation.
177
+ rescue GenderAPI::APIError => e
178
+ if e.billing_unconfirmed?
179
+ # Contact support with e.request_id. Do not retry automatically.
180
+ end
181
+ warn "GenderAPI #{e.status} #{e.code} #{e.action} #{e.request_id}"
182
+ rescue GenderAPI::TransportError => e
183
+ # The outcome is unknown. Check client.usage before sending again.
184
+ end
185
+ ```
177
186
 
178
- Each object in the data array may include:
179
- - `email`: The email address to analyze (required).
180
- - `country`: Two-letter country code (optional).
181
- - `id`: Custom identifier to correlate input/output (optional).
187
+ The machine-readable catalog of codes and actions is at [`/api/v2/errors`](https://api.genderapi.io/api/v2/errors) (`client.error_catalog`).
182
188
 
183
- ---
189
+ ## Billing and no-retry rules
184
190
 
185
- ### Username Lookup (Bulk)
191
+ - **No automatic retries, ever.** Every prediction or phone request is a new, billable operation. If a response is lost, the request may still have been billed, so the client never retries, not even on 429.
192
+ - **429:** wait for `retry_after` before sending another request. That request is a new operation with normal charges.
193
+ - **`billing_status: "unconfirmed"`** (for example `billing_reconciliation_required`): contact support with the `request_id` before retrying.
194
+ - **5xx prediction failures:** check `billing_status` and fix the cause before sending another request.
195
+ - **Partial batch success:** retry only the failed items, and only after billing is confirmed. Resubmitting successful items charges them again.
196
+ - **Timeouts or transport errors:** check `client.usage` before sending again.
197
+ - Redirects are never followed. HTTPS is required. The default timeout is 10 seconds.
186
198
 
187
- | Parameter | Type | Required | Description |
188
- |--------------------|----------|----------|-------------|
189
- | data | Array<Hash> | Yes | Array of objects containing username, optional country, and optional id. Limit is 50 records per request. |
199
+ ## IP trial (no key)
190
200
 
191
- Each object in the data array may include:
192
- - `username`: The username to analyze (required).
193
- - `country`: Two-letter country code (optional).
194
- - `id`: Custom identifier to correlate input/output (optional).
201
+ The client also works without an API key. The server then applies a shared IP trial: 10 credits per 24 hours per public IP address, normal tariffs, and batches of at most 10 items. Clients behind the same public IP share this quota. `meta.access.mode` is `ip_trial`, and `meta.usage.resets_at` shows when the window resets. The client has no trial logic of its own; the server decides.
195
202
 
196
- ---
203
+ If you configure a key and the server does not accept it, the request can fall back to the IP trial. By default the client then raises `UnexpectedAccessModeError`. The request has already been processed and may have used IP-trial credits. Check your key.
197
204
 
198
- ## ✅ API Response
205
+ ## Migrating from 1.x
199
206
 
200
- Example JSON response for all endpoints:
207
+ You do not have to migrate. 1.x (V1 API) stays available and installable indefinitely, with no deprecation or shutdown planned. To stay on it:
201
208
 
202
- ```json
203
- {
204
- "status": true,
205
- "used_credits": 1,
206
- "remaining_credits": 4999,
207
- "expires": 1743659200,
208
- "q": "michael.smith@example.com",
209
- "name": "Michael",
210
- "gender": "male",
211
- "country": "US",
212
- "total_names": 325,
213
- "probability": 98,
214
- "duration": "4ms"
215
- }
209
+ ```bash
210
+ gem install genderapi -v "~> 1.0"
216
211
  ```
217
212
 
218
- ---
219
-
220
- ### Response Fields
221
-
222
- | Field | Type | Description |
223
- |-------------------|--------------------|-----------------------------------------------------|
224
- | status | Boolean | `true` or `false`. Check errors if false. |
225
- | used_credits | Integer | Credits used for this request. |
226
- | remaining_credits | Integer | Remaining credits on your package. |
227
- | expires | Integer (timestamp)| Package expiration date (in seconds). |
228
- | q | String | Your input query (name, email, or username). |
229
- | name | String | Found name. |
230
- | gender | Enum[String] | `"male"`, `"female"`, or `"null"`. |
231
- | country | Enum[String] | Most likely country (e.g. `"US"`, `"DE"`, etc.). |
232
- | total_names | Integer | Number of samples behind the prediction. |
233
- | probability | Integer | Likelihood percentage (50-100). |
234
- | duration | String | Processing time (e.g. `"4ms"`). |
235
-
236
- ---
237
-
238
- ## ⚠️ Error Codes
239
-
240
- When `status` is `false`, check the following error codes:
241
-
242
- | errno | errmsg | Description |
243
- |-------|-----------------------------|-------------------------------------------------------------------|
244
- | 50 | access denied | Unauthorized IP Address or Referrer. Check your access privileges. |
245
- | 90 | invalid country code | Check supported country codes. [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) |
246
- | 91 | name not set \|\| email not set | Missing `name` or `email` parameter on your request. |
247
- | 92 | too many names \|\| too many emails | Limit is 100 for names, 50 for emails in one request. |
248
- | 93 | limit reached | The API key credit has been finished. |
249
- | 94 | invalid or missing key | The API key cannot be found. |
250
- | 99 | API key has expired | Please renew your API key. |
251
-
252
- Example error response:
253
-
254
- ```json
255
- {
256
- "status": false,
257
- "errno": 94,
258
- "errmsg": "invalid or missing key"
259
- }
260
- ```
213
+ or in your Gemfile:
261
214
 
262
- ---
215
+ ```ruby
216
+ gem "genderapi", "~> 1.0"
217
+ ```
263
218
 
264
- ## 🔗 Live Test Pages
219
+ The 1.x source stays on the [`v1` branch](https://github.com/GenderAPI/genderapi-ruby/tree/v1).
265
220
 
266
- You can try live gender detection directly on GenderAPI.io:
221
+ V2 is a different request and response contract, so changing only the URL is not enough. Your API key and credit balance stay the same.
267
222
 
268
- - **Determine gender from a name:**
269
- [www.genderapi.io](https://www.genderapi.io)
223
+ | 1.x (V1) | 2.x (V2) |
224
+ | --- | --- |
225
+ | `get_gender_by_name(name:)`, `get_gender_by_email(email:)`, `get_gender_by_username(username:)` | `client.name(value)`, `client.email(value)`, `client.username(value)` or `client.gender(type, value)` |
226
+ | V1 routes `/api`, `/api/email`, `/api/username` | `POST /api/v2/gender` with `type` and `value` |
227
+ | `get_gender_by_*_bulk(data:)` on `/api/*/multi/country` | `client.gender_batch(items)` -> `POST /api/v2/gender/batch` with `items` (1-50) |
228
+ | `ask_to_ai:` / `askToAI` | `ai_mode:` -> `options.ai_mode` (`off`, `fallback`, `always`). Single requests already default to `fallback`. |
229
+ | `force_to_genderize:` (name, username) | `force_to_genderize:` -> `forceToGenderize` for name, email and username; dataset first, then nickname-aware AI |
230
+ | Flat response fields (`q`, `name`, `gender`, ...) | `data` for the result and `meta` for access and billing |
231
+ | `probability` (percentage) | `data.confidence` (0-1) plus `data.confidence_kind`. AI scores are not calibrated probabilities. |
232
+ | `total_names` | `data.sample_count` (nullable) |
233
+ | `used_credits` / `remaining_credits` | `meta.usage.charged_credits` / `meta.usage.remaining_credits` |
234
+ | `expires` | `client.usage.data.expires_at` |
235
+ | `status: false` with `errno` / `errmsg` | HTTP status plus a Problem Details `code` and `action`, raised as `GenderAPI::APIError` |
236
+ | Generic `RuntimeError` on 5xx | Typed errors with `billing_status`, `request_id` and `retry_after` |
237
+ | HTTParty dependency | Standard library only |
238
+ | Ruby >= 2.6 | Ruby >= 3.0 |
270
239
 
271
- - **Determine gender from an email address:**
272
- [https://www.genderapi.io/determine-gender-from-email](https://www.genderapi.io/determine-gender-from-email)
240
+ ## Documentation
273
241
 
274
- - **Determine gender from a username:**
275
- [https://www.genderapi.io/determine-gender-from-username](https://www.genderapi.io/determine-gender-from-username)
242
+ - API documentation: https://www.genderapi.io/api-documentation
243
+ - V2 guides: [responses](https://www.genderapi.io/docs/v2/responses), [request parameters](https://www.genderapi.io/docs/v2/request-parameters), [AI options](https://www.genderapi.io/docs/v2/ai-options), [batch](https://www.genderapi.io/docs/v2/batch), [credits and usage](https://www.genderapi.io/docs/v2/credits-and-usage), [errors and retries](https://www.genderapi.io/docs/v2/errors-and-retries), [authentication](https://www.genderapi.io/docs/v2/authentication), [phone validation](https://www.genderapi.io/docs/v2/phone-validation), [migration](https://www.genderapi.io/docs/v2/migration)
244
+ - OpenAPI: https://api.genderapi.io/api/v2/openapi.json
276
245
 
277
- ---
246
+ ## Development
278
247
 
279
- ## 📚 Detailed API Documentation
248
+ ```bash
249
+ bundle config set --local path vendor/bundle
250
+ bundle install
251
+ bundle exec rake test # local stub server only: no real API, no credits
252
+ gem build genderapi.gemspec
253
+ ```
280
254
 
281
- For the complete API reference, visit:
255
+ Test fixtures in `test/fixtures/openapi_examples.json` are the response examples from the V2 OpenAPI document.
282
256
 
283
- [https://www.genderapi.io/api-documentation](https://www.genderapi.io/api-documentation)
257
+ ### Releasing
284
258
 
285
- ---
259
+ Pushing a `v*` tag (for example `v2.0.0`) runs `.github/workflows/publish.yml`. The workflow tests the gem, checks that the tag matches `GenderAPI::VERSION`, builds it and pushes it to RubyGems. It authenticates through RubyGems trusted publishing (OIDC) configured for this repository and workflow, so no API key is stored.
286
260
 
287
- ## ⚖️ License
261
+ ## License
288
262
 
289
- MIT License
263
+ MIT