API reference v0 · private preview
Three endpoints, one deterministic engine
Every response is computed from a pinned IPD-IMGT/HLA release and carries that release in the payload. Every example on this page is real output from the engine at release 3.65.0 — not illustrative JSON.
Base URL, auth and versioning
| Base URL | https://api.hlaverify.com |
| Auth | X-API-Key: <your key> on every request |
| Content type | application/json for POST bodies |
| Release pinning | Per key. The active release is returned as release in every response. |
| Interactive docs | OpenAPI at /docs on your deployment |
api.hlaverify.com is provisioned per pilot rather than open to self-serve signup — request access and we will issue a key, or run the container yourself.POST /v1/verify
Takes arbitrary free text — a typing report, an EHR fragment, a note, a model's answer — and returns a verdict for every allele-shaped token it contains.
Body: {"text": string}, up to 200,000 characters.
$ curl -s https://api.hlaverify.com/v1/verify \
-H 'content-type: application/json' -H "x-api-key: $KEY" \
-d '{"text": "Donor typing: A*0101, B*15:504:01, DRB1*14:06, DQB1*05:03:01G.
Assistant suggested DQB1*05:03:26:99."}'
{
"release": "3.65.0",
"clean": false,
"counts": { "valid": 2, "deleted": 1, "group": 1,
"fabricated_group": 0, "hallucinated": 1 },
"tokens": [
{ "token": "A*0101",
"status": "deleted",
"note": "was assigned once, no longer current — see successor",
"successor": "A*01:01:01:01",
"current_2field": "A*01:01",
"g_group": "AMBIGUOUS",
"flags": ["deprecated_name"] },
{ "token": "B*15:504:01",
"status": "valid",
"current_2field": "B*15:504",
"g_group": "NONE" },
{ "token": "DQB1*05:03:01G",
"status": "group",
"note": "a G/P group name in this release" },
{ "token": "DQB1*05:03:26:99",
"status": "hallucinated",
"note": "no such name in any release back to 1.05.0 — fabricated" },
{ "token": "DRB1*14:06",
"status": "valid",
"current_2field": "DRB1*14:06",
"g_group": "AMBIGUOUS" }
],
"attribution": "Computed from IPD-IMGT/HLA …"
}
Guardrail pattern: gate on clean. It is true only when hallucinated, fabricated_group and deleted are all zero. Tokens are returned sorted by name.
POST /v1/normalize
Takes a list of reported typing strings from any era and returns each one resolved to its current name, its 2-field reduction, its G group and its flags. This is the batch workhorse: a LIMS column in, a clean column out.
Body: {"typings": string[]}, up to 5,000 entries per call.
$ curl -s https://api.hlaverify.com/v1/normalize \
-H 'content-type: application/json' -H "x-api-key: $KEY" \
-d '{"typings": ["A*0101", "B*15:504:01", "DRB1*1406", "C*07:01:01:01"]}'
{
"release": "3.65.0",
"rows": [
{ "reported": "A*0101", "current_name": "A*01:01",
"allele_2field": "A*01:01", "g_group": "AMBIGUOUS",
"flags": ["deprecated_name"] },
{ "reported": "B*15:504:01", "current_name": "B*15:504:01",
"allele_2field": "B*15:504", "g_group": "NONE", "flags": [] },
{ "reported": "DRB1*1406", "current_name": "DRB1*14:06",
"allele_2field": "DRB1*14:06", "g_group": "AMBIGUOUS",
"flags": ["deprecated_name"] },
{ "reported": "C*07:01:01:01", "current_name": "C*07:01:01:01",
"allele_2field": "C*07:01", "g_group": "C*07:01:01G", "flags": [] }
]
}
g_group is AMBIGUOUS when a lower-resolution name spans more than one G group, and NONE when the allele belongs to no G group. Unresolvable input returns current_name: "UNRESOLVABLE" rather than a guess.
GET /v1/allele/{name}
The facts for one name: G group, P group, serology, first release, confirmation status, null-allele marking. Lower-resolution names return the size of the set they cover and a sample of members.
$ curl -s "https://api.hlaverify.com/v1/allele/A*01:01:01:01" -H "x-api-key: $KEY"
{
"release": "3.65.0", "name": "A*01:01:01:01",
"status": "assigned", "confirmed": true,
"g_group": "A*01:01:01G", "p_group": "A*01:01P",
"first_release": "3.00.0",
"serology": { "unambiguous": ["1"] }
}
$ curl -s "https://api.hlaverify.com/v1/allele/A*01:01" -H "x-api-key: $KEY"
{
"release": "3.65.0", "name": "A*01:01", "status": "valid_prefix",
"members_count": 303,
"members_sample": ["A*01:01:01:01", "A*01:01:01:02N", "A*01:01:01:03", "…"]
}
Deleted names return status: "deleted" with a successor. Unassigned but parseable names return 404; unparseable names return 404 with a parse message. Names are URL-path segments, so * and : may need encoding depending on your client.
GET /healthz
Liveness plus the release the process is currently pinned to. Unauthenticated. Use it to assert in your own CI that the release under you has not moved.
{ "ok": true, "release": "3.65.0", "uptime_s": 4192 }
Errors
| Status | When |
|---|---|
401 | Missing or unrecognized X-API-Key |
404 | The name is not assigned in the pinned release, or is not a parseable HLA allele name |
422 | Body failed validation — text over 200,000 characters, over 5,000 typings, wrong shape |
429 | Rate limit for the key exceeded |
Note the deliberate asymmetry: /v1/verify never fails on bad allele names — a fabricated name is a result, not an error. Only /v1/allele/{name} 404s, because there is nothing to report.
Self-hosting
The service ships as a container. For pilots that cannot send anything outside their own network, run it yourself; the reference files are fetched from the official source at startup and MD5-verified, and no request content leaves the process.
# build and run the verification service locally
$ docker build -t hla-verify -f sci_envs/service/Dockerfile .
$ docker run -p 8000:8000 \
-e HLA_VERIFY_TAG=v3.65.0-alpha \
-e HLA_VERIFY_API_KEYS="key-one,key-two" \
hla-verify
# /docs is the OpenAPI UI; / is the interactive demo page
| Environment variable | Effect |
|---|---|
HLA_VERIFY_TAG | The IPD-IMGT/HLA release tag to pin. Default v3.65.0-alpha. |
HLA_VERIFY_API_KEYS | Comma-separated keys. Unset means open — demo mode only, never for a deployment reachable from outside. |