Using a name gender API in production: batching, retries and the unknown row
Running a name gender API in production comes down to five decisions: send names in batches, group them by country, retry only errors that can succeed on a second attempt, store the evidence fields next to the answer, and treat unknown as a valid value. Get those right and the integration is a few dozen lines. Get them wrong and the failures are quiet: wasted credits, a French list classified with US data, or a column where "unknown" was silently filled with a default.
The examples use the NameGender API, but most of the checklist applies to any name gender API. If you are still deciding what one does, start with what a name gender API is.
1. Keep the key on the server
Read the key from an environment variable and call the API from your backend. A key in browser or mobile code can be copied by anyone who opens the developer tools, and every lookup they make is charged to you. Send it in the Authorization: Bearer header; the API does not accept keys in the URL.
Prefer POST with a JSON body over GET. The names then stay out of web server and proxy access logs, which matters when they belong to your customers.
2. Deduplicate, then batch
Each value you send costs one credit, including repeats and names that come back unknown. Customer lists repeat first names heavily: a 200,000-row list typically has around 16,000 unique ones. Remove duplicates before sending and map the answers back afterwards.
Then use the bulk endpoint, which accepts up to 100 values per request. Results come back in the same order you sent them, so you can join them by position.
Group by country before batching. The country field applies to the whole batch. If your list mixes countries, send one set of batches per country and a separate set for rows with no country. Sending a mixed list with one country code gives every row that country's answer.
3. Retry only what can succeed
The HTTP status is the success signal. Branch on the machine-readable error field, never on the message text, which is translated and can change.
| Status | Meaning | What to do |
|---|---|---|
200 with gender: null |
No confident answer | Store as unknown. Do not retry. |
429 rate_limited |
Too many requests | Wait for Retry-After seconds, then retry |
| 500, 503 | Server error or dataset reload | Retry up to 3 times with backoff: 1 s, 2 s, 4 s |
402 no_credits |
Balance used up | Stop the job and alert someone |
| 400, 401, 403, 422 | Bad input, key or account problem | Do not retry. Fix the request. |
A 402 on the bulk endpoint means nothing in that batch was processed or charged, because credits are checked for the whole batch first. Once the balance is topped up, the same batch can be sent again. On a free account, the daily credits reset at midnight UTC.
The default rate limit is 1,200 requests per minute per key. With 100 names per request, that is far above what most jobs need, so a small fixed number of workers is enough. Do not start one request per row in parallel.
4. A minimal Python client
import os
import time
import requests
API_URL = "https://namegender.com/api/v1/gender/bulk"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['NAMEGENDER_API_KEY']}"
def resolve_batch(names, country=None, max_attempts=4):
body = {"names": names}
if country:
body["country"] = country
for attempt in range(max_attempts):
response = session.post(API_URL, json=body, timeout=10)
if response.status_code == 200:
return response.json()["results"]
if response.status_code == 429:
time.sleep(int(response.headers.get("Retry-After", "1")))
continue
if response.status_code in (500, 503) and attempt < max_attempts - 1:
time.sleep(2 ** attempt)
continue
try:
error = response.json().get("error", "unknown_error")
except ValueError:
error = "unknown_error"
request_id = response.headers.get("X-Request-Id")
raise RuntimeError(f"{response.status_code} {error} (request_id={request_id})")
raise RuntimeError("Gave up after repeated rate limiting or server errors")
def resolve_names(names, country=None):
unique = sorted({n.strip() for n in names if n and n.strip()})
answers = {}
for start in range(0, len(unique), 100):
batch = unique[start:start + 100]
for query, result in zip(batch, resolve_batch(batch, country)):
answers[query] = result
return answers
Call resolve_names once per country group. The returned dictionary maps each unique input to its full result, so you can write the fields back to every row that shares that name.
5. Store the evidence, not just the label
Save these fields together:
| Field | Why you will need it later |
|---|---|
gender |
The answer, with null kept as null |
probability and sample_size |
To change your threshold without calling the API again |
confidence and source |
To tell counted records from spelling matches and uncounted data |
matched_as |
To audit fuzzy matches such as Micheal → michael |
data_version |
To explain why an answer changed between two runs |
request_id |
To trace a specific call with support |
If you store only male or female, you cannot tighten the rule later, and you cannot find which rows were guesses. Never fill unknown with a default. Unknowns are not spread evenly: they cluster in particular countries and scripts, so filling them in adds a bias that is invisible afterwards.
6. Decide the threshold in code
Write the rule once, next to the code that uses the answer:
def usable_gender(result, min_probability=90):
if result["gender"] is None or result["probability"] < min_probability:
return None
if result["source"] == "llm":
return None
return result["gender"]
Two trade-offs to decide for your own data:
confidence: "unverified"means the answer comes from records without counts. In markets with no published name statistics, such as Turkey, correct answers arrive this way. Rejecting all of them removes most answers for those countries.source: "fuzzy"is usually a spelling variant, but check a sample ofmatched_asvalues from your own list before accepting them automatically.
Our guide to probability, confidence and sample_size explains how these fields relate to measured accuracy.
7. Cache results, and refresh them when the data changes
Apart from llm answers, the result for the same name, country and data version does not change. Cache results keyed on the normalised name and country, and store data_version with each entry. When the version in new responses changes, re-check the cached names that matter to you rather than all of them at once.
Before you go live
- Test 200 to 500 rows from your real data where the answer is known, grouped by country.
- Filter out role addresses such as
info@andsupport@before sending email lists; they return unknown but still cost a credit. - Log
request_idon every failure. - Show unknown as its own category in any report built on the results.
- Do not use the result for decisions about employment, credit, insurance, health or eligibility.
Frequently asked questions
How many names can I send to a gender API in one request? The NameGender bulk endpoint accepts up to 100 values per request. For longer lists, split them into chunks of 100 and send them one after another or through a few parallel workers.
Should I retry when the API returns unknown? No. An unknown result is a complete answer: the data has no confident gender for that name. Retrying costs another credit and returns the same result.
Can I call a gender API from the browser? Not directly. The key would be visible to anyone using the page. Call the API from your server and pass only the result to the browser.
How do I handle a list with names from many countries? Group the rows by country, send each group with its own country code, and send rows without a country separately. One country code applies to every name in a batch.
Every claim on this page is measurable against your own list. The free tier is enough to check it.
Related
-
API keys that don't store the names you send
Turn on one setting per API key and the names you send are never written to our request history. What is still recorded, and why a hashed name is not anonymous.
-
First, middle and last name from any full name, in the same lookup
Every NameGender lookup now splits the input into first_name, middle_name and last_name at no extra cost. The rules, real examples, and where splitting fails.
-
What is a name gender API? How a name becomes male, female or unknown
A name gender API estimates the gender tied to a first name from counted records. What it returns, how each answer is reached, and what it cannot tell you.