GenderAPI V2 · Practical guide

Determine gender from an email address

Use an email's name signal with GenderAPI V2. Try a live lookup, compare dataset and AI options, and learn how to handle unknown results and bulk email lists.

Last reviewed

Try the V2 API

Analyze an email

Dataset + AI fallback

Submit one value and inspect the complete response, including unknown results and credit usage.

Ordinary lookup: 1 credit, including AI fallback or an unknown result. With nickname inference enabled: 1 credit for a usable dataset result, or 2 credits total for AI, including an unknown result.

The submitted full email address goes to GenderAPI and may be sent to the configured AI service for fallback. Processing details.

No saved API key. The shared IP trial is available while credits remain.

Synthetic example below. Select Analyze email to run a live lookup.

Synthetic V2 example · not a live result
{
  "data": {
    "input": {
      "type": "email",
      "value": "onur@example.com",
      "country": null
    },
    "name": "onur",
    "gender": "male",
    "country": "TR",
    "confidence": 0.9,
    "confidence_kind": "observed_frequency",
    "sample_count": 100,
    "source": "dataset",
    "result_status": "identified",
    "reason": null,
    "country_source": "dataset",
    "match": {
      "name": "onur",
      "method": "normalized",
      "scope": "global",
      "country": null
    }
  },
  "meta": {
    "request_id": "11111111-1111-4111-8111-111111111111",
    "duration_ms": 12,
    "access": {
      "mode": "ip_trial",
      "reason": "api_key_missing"
    },
    "usage": {
      "charged_credits": 1,
      "remaining_credits": 9,
      "billing_status": "confirmed",
      "resets_at": "2026-09-26T12:00:00.000Z",
      "limit": 10,
      "period_seconds": 86400
    }
  }
}

Example counts, scores and balances illustrate the format. Live values may differ. A prediction is not verified identity.

What can an email address tell you?

Send a syntactically valid email address with type: email. GenderAPI looks for a name signal in the part before @, then returns a structured inference. It does not verify the mailbox, identify its owner or establish that person's gender identity.

Dataset matching derives candidate tokens and can try bounded substrings. Nickname mode keeps token matching but does not add those substring candidates. If multiple records match, the candidate with the largest stored total is selected; it is not guaranteed to be the intended first name. Inspect data.name and data.match before using an extracted name in a contact record.

Example inputHow to interpret it
alice.smith@example.comThe local part provides possible name tokens. A match is possible, but the input alone does not guarantee which candidate wins.
alice+news@example.comDataset lookup strips the plus-addressing suffix for its candidates. AI is instructed to ignore it, but receives the submitted value.
support@example.comA role address provides no reliable personal-name signal. Do not assign the whole shared inbox a person's gender.
prenses@example.comOptional forceToGenderize permits an attempt from nickname meaning if the dataset is unresolved. A real name is not required in that mode.
not-an-emailInvalid syntax returns a validation error rather than a successful unknown result.

Send an email lookup from your application

This example uses your existing Bearer API key and explicit fallback. Replace the sample address with a permitted input. Supply country only when you have relevant context; the email domain does not establish a person's location. Without country, omit that field.

Run the example on your server with GENDERAPI_API_KEY set. POST keeps the email value out of the request URL. The live result can differ from the illustrative response in the demo.

Choose your language. Set up your API key, then run the example on your server.

Every prediction request is a new billable operation, including retries. These examples do not automatically retry. Check billing status before sending another request.

Before you run: access and error handling

Run these examples on your server. Set GENDERAPI_API_KEY in the process environment to your existing API key. Confirm meta.access.mode is api_key: an unrecognized key can fall back to the IP trial.

HTTP 4xx and 5xx JSON responses preserve the error body and return a nonzero exit status. Check code, action and meta.usage.billing_status before retrying.

Error and retry guide →
cURL
curl --silent --show-error --fail-with-body --max-time 30 \
  --request POST 'https://api.genderapi.io/api/v2/gender' \
  --header "Authorization: Bearer ${GENDERAPI_API_KEY:?Set GENDERAPI_API_KEY}" \
  --header 'Content-Type: application/json' \
  --data '{
  "type": "email",
  "value": "alice.smith@example.com",
  "country": "US",
  "options": {
    "ai_mode": "fallback"
  }
}'

cURL 7.76+ in a POSIX shell. Run in your terminal. Runtime documentation

JavaScript / Node.js
// Server-side Node.js. Save as example.mjs.
const apiKey = process.env.GENDERAPI_API_KEY;
if (!apiKey) throw new Error("Set GENDERAPI_API_KEY");
const body = {
  "type": "email",
  "value": "alice.smith@example.com",
  "country": "US",
  "options": {
    "ai_mode": "fallback"
  }
};

const response = await fetch("https://api.genderapi.io/api/v2/gender", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${apiKey}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(body),
  signal: AbortSignal.timeout(30_000),
  redirect: "error",
});
const raw = await response.text();
if (!(response.headers.get("content-type") ?? "").includes("json")) {
  throw new Error(`HTTP ${response.status}: expected JSON; request ID ${response.headers.get("x-request-id")}`);
}
const result = JSON.parse(raw); // Also accepts application/problem+json.
if (!response.ok) {
  console.error(`HTTP ${response.status}`, result);
  process.exitCode = 1; // A retry is a new operation; inspect billing first.
} else {
  console.log(JSON.stringify(result, null, 2));
  // For batches, inspect every item: HTTP 200 may contain item errors.
}

Node.js 22+; built-in fetch. Save as example.mjs and run node example.mjs. Runtime documentation

Python
import json
import os
import sys
import urllib.error
import urllib.request

api_key = os.environ.get("GENDERAPI_API_KEY")
if not api_key:
    raise RuntimeError("Set GENDERAPI_API_KEY")
body = json.loads("{\"type\":\"email\",\"value\":\"alice.smith@example.com\",\"country\":\"US\",\"options\":{\"ai_mode\":\"fallback\"}}")

class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None

request = urllib.request.Request(
    "https://api.genderapi.io/api/v2/gender",
    method="POST",
    data=json.dumps(body).encode("utf-8"),
    headers={
        "Authorization": "Bearer " + api_key,
        "Content-Type": "application/json",
    },
)
opener = urllib.request.build_opener(NoRedirect())
try:
    response = opener.open(request, timeout=30)
except urllib.error.HTTPError as error:
    response = error  # Keep the Problem Details body on non-2xx responses.
with response:
    status = response.status
    raw = response.read().decode("utf-8")
    if "json" not in response.headers.get("Content-Type", ""):
        raise RuntimeError(f"HTTP {status}: expected a JSON response")
    result = json.loads(raw)
print(json.dumps(result, indent=2), file=sys.stderr if status >= 300 else sys.stdout)
if not 200 <= status < 300:
    sys.exit(1)  # Inspect code, action and billing before retrying.
# For batches, inspect every item even when HTTP status is 200.

Python 3.10+; standard library. Save as example.py and run python3 example.py. Runtime documentation

PHP
<?php
$apiKey = getenv('GENDERAPI_API_KEY');
if (!$apiKey) {
    throw new RuntimeException('Set GENDERAPI_API_KEY');
}
$body = json_decode('{"type":"email","value":"alice.smith@example.com","country":"US","options":{"ai_mode":"fallback"}}', true, 512, JSON_THROW_ON_ERROR);

$ch = curl_init('https://api.genderapi.io/api/v2/gender');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => false,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode($body, JSON_THROW_ON_ERROR),
]);
$raw = curl_exec($ch);
if ($raw === false) {
    throw new RuntimeException(curl_error($ch));
}
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE) ?: '';
curl_close($ch);
if (strpos($contentType, 'json') === false) {
    throw new RuntimeException("HTTP $status: expected a JSON response");
}
$result = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
$output = json_encode($result, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR) . PHP_EOL;
if ($status < 200 || $status >= 300) {
    fwrite(STDERR, $output); // Preserve the Problem Details body.
    exit(1);
}
echo $output;
// For batches, inspect every item; HTTP 200 can contain item errors.

PHP 8+ with the cURL extension. Save as example.php and run php example.php. Runtime documentation

Java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;

public class GenderApiExample {
    private static String requiredEnv(String name) {
        String value = System.getenv(name);
        if (value == null || value.isBlank()) throw new IllegalStateException("Set " + name);
        return value;
    }

    public static void main(String[] args) throws Exception {
        String apiKey = requiredEnv("GENDERAPI_API_KEY");
        String body = "{\"type\":\"email\",\"value\":\"alice.smith@example.com\",\"country\":\"US\",\"options\":{\"ai_mode\":\"fallback\"}}";
        HttpClient client = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(10))
            .followRedirects(HttpClient.Redirect.NEVER)
            .build();
        HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.genderapi.io/api/v2/gender"))
            .timeout(Duration.ofSeconds(30))
            .header("Authorization", "Bearer " + apiKey)
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
            .build();
        HttpResponse<String> response = client.send(request,
            HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
        if (!response.headers().firstValue("content-type").orElse("").contains("json")) {
            throw new IllegalStateException("HTTP " + response.statusCode() + ": expected JSON");
        }
        // JSON text; parse with your application's JSON library when integrating.
        if (response.statusCode() < 200 || response.statusCode() >= 300) {
            System.err.println(response.body()); // Includes Problem Details.
            System.exit(1);
        }
        System.out.println(response.body());
        // For batches, inspect each item's data or error, including on HTTP 200.
    }
}

Java 17+; standard HTTP client. Save as GenderApiExample.java and run java GenderApiExample.java. Runtime documentation

C# / .NET
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

string RequiredEnv(string name) =>
    !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(name))
        ? Environment.GetEnvironmentVariable(name)!
        : throw new InvalidOperationException($"Set {name}");

var apiKey = RequiredEnv("GENDERAPI_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(30) };
using var request = new HttpRequestMessage(HttpMethod.Post, "https://api.genderapi.io/api/v2/gender");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
request.Content = new StringContent("{\"type\":\"email\",\"value\":\"alice.smith@example.com\",\"country\":\"US\",\"options\":{\"ai_mode\":\"fallback\"}}", Encoding.UTF8, "application/json");

using var response = await client.SendAsync(request);
var raw = await response.Content.ReadAsStringAsync();
if (!(response.Content.Headers.ContentType?.MediaType?.Contains("json") ?? false))
    throw new InvalidOperationException($"HTTP {(int)response.StatusCode}: expected JSON");
using var result = JsonDocument.Parse(raw);
if (!response.IsSuccessStatusCode)
{
    Console.Error.WriteLine(result.RootElement); // Preserve Problem Details.
    Environment.ExitCode = 1;
}
else
{
    Console.WriteLine(result.RootElement);
    // For batches, inspect every item even on HTTP 200.
}

.NET 8+ console application. Use as Program.cs in a console project, then run dotnet run. Runtime documentation

Go
package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "strings"
    "time"
)

func requiredEnv(name string) string {
    value := os.Getenv(name)
    if value == "" { panic("Set " + name) }
    return value
}

func run() error {
    apiKey := requiredEnv("GENDERAPI_API_KEY")
    body := "{\"type\":\"email\",\"value\":\"alice.smith@example.com\",\"country\":\"US\",\"options\":{\"ai_mode\":\"fallback\"}}"
    request, err := http.NewRequest("POST", "https://api.genderapi.io/api/v2/gender", strings.NewReader(body))
    if err != nil { return err }
    request.Header.Set("Authorization", "Bearer " + apiKey)
    request.Header.Set("Content-Type", "application/json")
    client := &http.Client{
        Timeout: 30 * time.Second,
        CheckRedirect: func(req *http.Request, via []*http.Request) error {
            return http.ErrUseLastResponse
        },
    }
    response, err := client.Do(request)
    if err != nil { return err }
    defer response.Body.Close()
    raw, err := io.ReadAll(response.Body)
    if err != nil { return err }
    if !strings.Contains(response.Header.Get("Content-Type"), "json") || !json.Valid(raw) {
        return fmt.Errorf("HTTP %d: expected a JSON response", response.StatusCode)
    }
    if response.StatusCode < 200 || response.StatusCode >= 300 {
        return fmt.Errorf("HTTP %d: %s", response.StatusCode, raw)
    }
    fmt.Println(string(raw))
    // For batches, inspect every item even on HTTP 200.
    return nil
}

func main() {
    if err := run(); err != nil {
        fmt.Fprintln(os.Stderr, err) // Keep Problem Details for error handling.
        os.Exit(1)
    }
}

Go 1.22+; standard library. Save as main.go and run go run main.go. Runtime documentation

Keep the result and its evidence together

An inferred association is not a person's self-declared gender. Keep the original input, the returned evidence and any information provided by the person separate. An unknown result is a valid outcome; it should not become a guessed category in your application.

FieldHow to use it
data.gender / data.result_statusUse male or female only when identified. Preserve native JSON null when the result is unknown.
data.name / data.matchInspect the returned name and selected candidate. A substring match does not prove that the input belongs to someone with that given name.
data.confidence / data.confidence_kindConfidence is on a 0–1 scale or null. observed_frequency comes from stored counts; model_reported is an AI score. Evaluate thresholds separately for each kind.
data.source / data.sample_countDistinguish dataset, ai and none. AI results have no stored sample count. A sample count is not a measured accuracy score.
meta.access.modeConfirm api_key for an account integration. Missing or unrecognized keys can instead use the shared IP trial.
meta.usageRead charged_credits and billing_status. A successful unknown is billable. A lost response does not establish that the request was free.

Choose when to use AI

forceToGenderize is optional for names, email addresses and usernames. With it enabled, omit ai_mode or use fallback; off and always conflict with it and return 422. A positive starting balance is enough to begin a request even if its final charge takes the balance below zero.

Nickname mode can return a gender with name: null. It can also return an unknown result. Neither ordinary fallback nor nickname inference guarantees a correct or non-null answer.

Request optionBehaviorCredits for a successful lookup
options.ai_mode: offUse the dataset only.1, including an unknown result
options.ai_mode: fallbackTry the dataset, then ordinary AI when no gender is returned. This is the single-request default.1 total, including AI fallback
options.ai_mode: alwaysAsk AI directly.2
forceToGenderize: trueTry the dataset first, then allow AI to interpret a personal nickname or alias even without a real given name.1 for a resolved dataset result; 2 total if AI is used

Process an email list in batches

Use POST /api/v2/gender/batch with an items array of type: email entries. Registered API-key access supports up to 50 items; the IP trial supports up to 10. Preserve a unique id for each item so results can be joined back to the original row. The response also preserves input order and reports index.

Batch items default to dataset-only mode. Set options.ai_mode: fallback on each item that needs ordinary AI fallback. Two successful fallback items cost 2 credits total, including unknown results; one HTTP request is not one prediction credit.

Inspect every item even when the batch returns HTTP 200. Save successful and unknown outcomes, then review errors and confirmed billing before submitting failed items again. Do not resend an entire successful batch after a timeout without checking the billing outcome.

Understand what you send

The live demo sends the entered value to GenderAPI only when you submit it. If AI is invoked, the submitted type, value and country context are sent to the configured inference service. For dataset-only prediction, use options.ai_mode: off without forceToGenderize in your integration.

The Privacy Policy, processing agreement and subprocessor register describe the published handling terms. A result is an inferred association; let information supplied by the person take precedence. Do not use it as the basis for consequential decisions about an individual.

Email lookup FAQ

Can the API extract a first name from an email?

It can return a candidate name when the local part contains a usable signal. Token and substring matches are possible, so inspect data.match. The result does not prove the mailbox owner's given name.

Will info@ or support@ return a gender?

Shared role addresses generally provide no personal-name evidence. An unknown result is valid. AI or nickname mode should not be treated as verification of the people using a shared inbox.

Does an unknown email result use credits?

Yes. A successful ordinary email lookup costs 1 credit, including fallback and unknown results. Forced nickname inference costs 1 for a resolved dataset result or 2 total if AI is used. Errors have separate billing outcomes in meta.usage.

Is the email domain removed before AI processing?

No such removal is promised. Dataset candidates come from the local part, while AI receives the submitted value and is instructed to ignore the domain and plus-addressing suffix. Use dataset-only mode if that is required by your workflow.

Continue testing

Use your GenderAPI key

Paste the API key from your GenderAPI account to continue testing after the public daily limit.

Stored only for this browser tab. It is cleared when the tab is closed.