# Determine gender from Chinese names

> Try Chinese names with GenderAPI V2. Learn how short tokens, full-name segmentation, original characters and CN context affect dataset and AI results.

Canonical HTML: https://www.genderapi.io/determine-gender-from-chinese-names

Last reviewed: 2026-09-26

## Distinguish Unicode input from name segmentation

You can submit a Chinese name as Unicode text without converting it to Latin letters. The current dataset path looks up normalized tokens; it does not automatically split an uninterrupted Chinese full name into surname and given name. 王静怡 is therefore one candidate, not a guaranteed lookup of 静怡.

For the common Chinese characters in these examples, one- and two-character tokens are skipped by the current dataset candidate filter. A short given name can be valid input yet have no dataset candidate. Ordinary AI fallback can still consider the original submitted value; dataset-only mode preserves an unknown result.

Unicode NFKC normalization does not provide a general simplified-to-traditional conversion or resolve a Latin spelling back to a unique Chinese name. Keep the submitted script and inspect the returned match. Do not infer a person's gender or country from surname position, script or romanization alone.

| Example input | What it represents | What to check |
| --- | --- | --- |
| `王静怡` | Uninterrupted full name | One dataset candidate. The current parser does not split off the surname. |
| `静怡` | Two-character given-name input | Skipped by the dataset candidate-length filter; AI fallback may try the original value. |
| `王 静怡` | Separated short tokens | Adding a space does not fix the length filter. Both example tokens are too short for dataset lookup. |
| `Wang Jingyi` | Latin-script input | Treated as its own tokenized input. It is not guaranteed to resolve to the original characters or the same record. |

## Choose CN context deliberately

The live demo sends country: CN (China). The API does not choose this country from the name's script. In your integration, use known, relevant context or omit country; do not turn this sample value into a default for every record.

For each candidate, the dataset path prefers an existing country-specific record and otherwise considers the global record. It then selects the available candidate with the largest stored total. Inspect data.match.scope and data.match.country to see the actual match scope. An ambiguous country-specific record is not automatically replaced by a global result; fallback may use AI instead.

data.input.country describes the request context. data.country and data.country_source describe a returned association. They are different fields and do not establish citizenship, ethnicity or residence.

- [Country context and matching reference](https://www.genderapi.io/docs/v2/gender-from-name#country-evidence-workflow)

## Send a Chinese name with V2

Run the request on your server with GENDERAPI_API_KEY set to your existing key. The example explicitly enables ordinary AI fallback. It illustrates a request, not a measured prediction or coverage result. Use options.ai_mode: off for dataset-only processing.

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.

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

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.

**cURL**

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

```bash
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": "name",
  "value": "王静怡",
  "country": "CN",
  "options": {
    "ai_mode": "fallback"
  }
}'
```

**JavaScript / Node.js**

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

```javascript
// 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": "name",
  "value": "王静怡",
  "country": "CN",
  "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.
}
```

**Python**

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

```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\":\"name\",\"value\":\"王静怡\",\"country\":\"CN\",\"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.
```

**PHP**

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

```php
<?php
$apiKey = getenv('GENDERAPI_API_KEY');
if (!$apiKey) {
    throw new RuntimeException('Set GENDERAPI_API_KEY');
}
$body = json_decode('{"type":"name","value":"王静怡","country":"CN","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.
```

**Java**

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

```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\":\"name\",\"value\":\"王静怡\",\"country\":\"CN\",\"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.
    }
}
```

**C# / .NET**

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

```csharp
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\":\"name\",\"value\":\"王静怡\",\"country\":\"CN\",\"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.
}
```

**Go**

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

```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\":\"name\",\"value\":\"王静怡\",\"country\":\"CN\",\"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)
    }
}
```

- [Name request parameters](https://www.genderapi.io/docs/v2/gender-from-name)
- [Set up authentication](https://www.genderapi.io/docs/v2/authentication)

## 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.

| Field | How to use it |
| --- | --- |
| `data.gender / data.result_status` | Use male or female only when identified. Preserve native JSON null when the result is unknown. |
| `data.name / data.match` | Inspect 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_kind` | Confidence 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_count` | Distinguish dataset, ai and none. AI results have no stored sample count. A sample count is not a measured accuracy score. |
| `meta.access.mode` | Confirm api_key for an account integration. Missing or unrecognized keys can instead use the shared IP trial. |
| `meta.usage` | Read charged_credits and billing_status. A successful unknown is billable. A lost response does not establish that the request was free. |

- [Response fields and unknown outcomes](https://www.genderapi.io/docs/v2/responses)
- [Accuracy and confidence explained](https://www.genderapi.io/accuracy-methodology)
- [Data sources and the dated database profile](https://www.genderapi.io/data-provenance)

## 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 option | Behavior | Credits for a successful lookup |
| --- | --- | --- |
| options.ai_mode: off | Use the dataset only. | 1, including an unknown result |
| options.ai_mode: fallback | Try the dataset, then ordinary AI when no gender is returned. This is the single-request default. | 1 total, including AI fallback |
| options.ai_mode: always | Ask AI directly. | 2 |
| forceToGenderize: true | Try 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 |

- [AI options and nickname inference](https://www.genderapi.io/docs/v2/ai-options)
- [Credits and usage](https://www.genderapi.io/docs/v2/credits-and-usage)

## Process a name list without losing its context

Use POST /api/v2/gender/batch for up to 50 items with registered API-key access or 10 items in the IP trial. Assign unique item IDs and retain the original value for each row. Each item can have its own country and AI policy; omit a country when it is unknown.

Batch items default to dataset-only mode, unlike the single-request fallback default. Set options.ai_mode: fallback on each item that needs AI. Check every result, even after HTTP 200, preserve unknown outcomes and review item errors and billing before another submission. Each successful ordinary item uses one credit; one batch HTTP request is not one prediction credit.

- [Batch examples and partial failures](https://www.genderapi.io/docs/v2/batch#batch-application-workflow)
- [Handle unknowns in your application](https://www.genderapi.io/docs/v2/responses#application-decisions)

## 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.

- [Privacy Policy](https://www.genderapi.io/privacy-policy)
- [Data Processing Agreement](https://www.genderapi.io/data-processing-agreement)
- [Subprocessor register](https://www.genderapi.io/subprocessors)

## Why can a two-character Chinese name be unknown in dataset-only mode?

The current dataset candidate filter skips tokens whose JavaScript string length is at most two. For the Chinese characters shown here, a two-character name is valid input but does not become a dataset candidate. AI fallback may consider the original input.

## Does V2 automatically separate a Chinese surname from a given name?

The dataset path does not segment an uninterrupted Chinese full name. AI may infer a name from the complete value, but neither path guarantees the intended given name. Check the returned source and match.

## Is accuracy measured for every Chinese name?

No such measurement is published here. Input acceptance, stored counts and a confidence value are not an independent accuracy benchmark. Review the methodology page for the available evidence and the measurements that remain unpublished.

## Does an unknown result use credits?

A successful unknown is billable. Ordinary dataset or fallback mode costs one credit total. With forceToGenderize enabled, a resolved dataset result costs one credit and using AI costs two total, including an unknown AI result. Errors have separate billing rules; inspect meta.usage.
