The name field is where verification goes wrong in public. Block a disposable email and nobody notices. Tell a customer their surname is offensive, or greet them as "Dear Garcia" because you split their name in the wrong place, and it ends up in a screenshot. This lesson covers four APIs that each do one narrow job well, and the rules for keeping each in its lane.
- Human Name Parser splits a full name into title, first, middle, last, nickname and suffix.
- Gibberish Detector scores text for keyboard-mash randomness.
- Username Profanity says whether a handle is offensive, including disguised spellings.
- Profanity Filter masks offensive words in free text.
All four cost 2 credits a call, so cost is not the constraint here. Judgment is.
01Parse names, but keep what the person typed
Most forms ask for "full name" in one box because two boxes annoy people and fail for many cultures. Then the CRM wants first and last for a mail merge. The parser bridges that.
curl "https://api.apiverve.com/v1/humannameparser?name=MR.+MARTINO+PETROS+DE+FLORENCE+%28TINO%29+Jr." \
-H "x-api-key: YOUR_API_KEY"Title Mr., first Martino, middle Petros, last de Florence, nick Tino, suffix Jr.
That example shows what the parser is good at: it recognises the title and the suffix, pulls the nickname out of the parentheses, keeps the particle de attached to the surname, and fixes the shouting capitals. It also reads Smith, John Q. correctly as last name Smith, first John, middle Q.. The response adds initials on every plan and a premium formalName such as Mr. de Florence, Martino P. Jr..
Now the cases that should change how you use it.
Dr. Maria del Carmen Lopez-Garcia PhD parses to first Maria, middle del Carmen, last Lopez-garcia. Two things went wrong. del Carmen is part of a compound given name, not a middle name in the English sense, and the second half of the hyphenated surname lost its capital letter. The initials come back MDL, which is not what she would write.
Cher parses to first "" and last Cher. A single name is put in last, so a template that says "Hi {first}" prints "Hi ". Plenty of real people have one name, and plenty more give only one to a form.
asdf qwerty parses cleanly to first Asdf, last Qwerty, initials AQ. The parser structures whatever it is given. It does not judge whether the input is a name.
The rules that follow from those three:
- Store the name exactly as typed and use that for anything the person sees, including the greeting on their account page. Parsed parts are for sorting, searching and mail merge.
- Never write parsed parts back over the original. The casing fix that helps
MARTINObreaksLopez-Garcia,McDonaldandvan der Berg. - Fall back when
firstis empty. Greet with the full name, or ask once: "What should we call you?" That one optional field beats any parser for greeting people correctly.
Your welcome email uses Hi {parsed.first}. A user signs up as Cher. What does the parser return, and what is the fix?
02Catch keyboard mash with the gibberish detector
Junk signups type whatever is fastest: asdf, jkdfhgkjdfhg, qwerty. Gibberish Detector is a POST that takes text and returns isGibberish, a score from 0 to 1, a confidence percentage and a confidenceLevel of high, medium or low.
curl -X POST "https://api.apiverve.com/v1/gibberishdetector" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"jkdfhgkjdfhg"}'isGibberish true, score 1, confidenceLevel high.
Because it is a POST, the text goes in a JSON body rather than the query string:
curl -X POST 'https://api.apiverve.com/v1/gibberishdetector' -H "x-api-key: $APIVERVE_API_KEY" -H 'content-type: application/json' -d '{"text": "Zbigniew Brzezinski"}'It works by comparing letter pairs and vowel ratios to normal text. That is exactly what makes it risky on names, because many real names have unusual letter pairs. Here are real results:
| Input | isGibberish | score | confidenceLevel |
|---|---|---|---|
jkdfhgkjdfhg | true | 1 | high |
asdfghjkl qwerty | true | 0.889 | high |
Acme Logistics Ltd | false | 0.583 | medium |
Zbigniew Brzezinski | false | 0.535 | medium |
I would like a demo of your product for my team next week. | false | 0.475 | low |
Siobhan Nguyen | false | 0.275 | low |
Xi Li | false | 0.317 | low |
Read the middle of that table carefully. A plain English sentence scores 0.475 and a company name 0.583, both higher than you might expect, and the Polish name Zbigniew Brzezinski scores 0.535. If you write your own rule like score > 0.5 means junk, you block a company and a statesman's name. The API's own isGibberish was false for all three. So:
- Gate on
isGibberish, never on your own threshold overscore. - Use it for review, not rejection, on name fields. A real name flagged as gibberish is a much worse outcome than a junk account that got through, which you can clean up later.
- It is more reliable on longer free text such as a "how will you use the product" box, where real answers are sentences and junk is one mashed word. That field is often the better place to run it than the name itself.
Very short inputs carry too little signal either way. Two or three letters can be a real name (Xi Li, Bo, Al), so skip the check below about four characters.
03Offensive usernames: check the handle, not the human
Public handles are a different problem. A username is chosen, shown to other users, and often designed to slip past simple word lists. Username Profanity takes username and returns isProfane.
curl "https://api.apiverve.com/v1/usernameprofanity?username=sh1thead99" \
-H "x-api-key: YOUR_API_KEY"isProfane true: digit-for-letter substitutions are caught.
It catches common disguises: b00bs and sh1thead99 both come back true. It also avoids the classic substring trap. scunthorpe_fan, dickens_reader and classic_gamer all come back false, although each contains a string that a naive substring search would flag. Blocking those is the failure that makes a filter look stupid, and it hits real place names and surnames.
Reject a profane username at the form with a neutral message ("That username isn't available, please choose another"). Do not quote the word back, and do not explain the rule in detail; a precise explanation is a guide for getting round it.
04Profanity in free text, and why never on names
Profanity Filter is a POST that takes text and an optional single-character mask and returns filteredText, isProfane, the mask used and trimmed. A premium profaneWords count is included on plans that have it.
curl -X POST "https://api.apiverve.com/v1/profanityfilter" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"This is a damn good product but the setup was shit.","mask":"*"}'filteredText masks each profane word letter for letter.
The mask replaces each letter of a profane word, so damn becomes **** and b00bs with mask: "#" becomes #####. The masked text keeps its length and word positions, so it stays readable. Use it for content other people will read: reviews, comments, display bios.
Now run it on a name. Dick Van Dyke comes back isProfane: true with filteredText: "**** Van ****". Both of the actor's names are ordinary English names and surnames, and both are on the word list. That is not a bug in the filter. A list of offensive words used in prose inevitably contains real names, and the same holds for surnames in many languages. Text filters and name fields do not mix. If you need to screen names at all, use the username check on a handle, or send anything suspicious to a human. Never mask or reject a person's legal name automatically.
A product manager wants Profanity Filter run on the full-name field at signup, rejecting any name where isProfane is true. What is the strongest objection?
05One signup screen, four checks
Here is the profile check you can run on a signup that has a full name, a public username and a short "what will you build" answer. It returns parsed name parts for your CRM and a list of flags. Only an offensive username blocks the form. Everything else becomes review.
const BASE = 'https://api.apiverve.com/v1';
const headers = { 'x-api-key': process.env.APIVERVE_API_KEY };
async function call(api, params, method = 'GET') {
const init = { method, headers: { ...headers }, signal: AbortSignal.timeout(3000) };
let url = `${BASE}/${api}`;
if (method === 'GET') url += `?${new URLSearchParams(params)}`;
else {
init.headers['Content-Type'] = 'application/json';
init.body = JSON.stringify(params);
}
const res = await fetch(url, init);
const body = await res.json();
if (!res.ok || body.status !== 'ok') throw new Error(body.error || `HTTP ${res.status}`);
return body.data;
}
export async function profileCheck({ fullName, username, useCase }) {
const [name, handle, junk] = await Promise.all([
call('humannameparser', { name: fullName }),
call('usernameprofanity', { username }),
useCase && useCase.trim().length >= 4 ? call('gibberishdetector', { text: useCase }, 'POST') : null,
]);
const flags = [];
if (!name.parsed.first) flags.push('single_name');
if (junk?.isGibberish) flags.push('gibberish_use_case');
return {
blockUsername: handle.isProfane,
review: flags.length > 0,
flags,
displayName: fullName.trim(),
crm: { first: name.parsed.first, last: name.parsed.last },
};
}import os
from concurrent.futures import ThreadPoolExecutor
import requests
BASE = "https://api.apiverve.com/v1"
HEADERS = {"x-api-key": os.environ["APIVERVE_API_KEY"]}
def call(api: str, params: dict, method: str = "GET") -> dict:
if method == "GET":
r = requests.get(f"{BASE}/{api}", params=params, headers=HEADERS, timeout=3)
else:
r = requests.post(f"{BASE}/{api}", json=params, headers=HEADERS, timeout=3)
r.raise_for_status()
body = r.json()
if body.get("status") != "ok":
raise RuntimeError(body.get("error"))
return body["data"]
def profile_check(full_name: str, username: str, use_case: str = "") -> dict:
with ThreadPoolExecutor(max_workers=3) as pool:
name_f = pool.submit(call, "humannameparser", {"name": full_name})
handle_f = pool.submit(call, "usernameprofanity", {"username": username})
junk_f = (pool.submit(call, "gibberishdetector", {"text": use_case}, "POST")
if len(use_case.strip()) >= 4 else None)
name, handle = name_f.result(), handle_f.result()
junk = junk_f.result() if junk_f else None
flags = []
if not name["parsed"]["first"]:
flags.append("single_name")
if junk and junk["isGibberish"]:
flags.append("gibberish_use_case")
return {
"blockUsername": handle["isProfane"],
"review": bool(flags),
"flags": flags,
"displayName": full_name.strip(),
"crm": {"first": name["parsed"]["first"], "last": name["parsed"]["last"]},
}The three calls run in parallel because none depends on another, so the check adds one round trip, not three. Notice what is not in it: no gibberish check on the name, no profanity filter on the name, and displayName is the original string. single_name is a review flag only so your CRM export knows the first-name column is empty, not because anything is wrong with the person.
If any call fails, decide in advance which way to fall. For a free tier, let the signup through and flag it for later review; a name check is never worth a lost customer. Combine these flags with the email and phone decisions from the earlier lessons rather than letting any single one decide alone.
06Next
You can now verify every field a person types into a form. The next course moves from what people type to where they connect from: IP reputation, VPN and Tor detection, and bot signals. Continue to Network and Fraud Signals.