Skip to content

Public API v0

Every verified record in the registry is available as JSON. No key, no account, no quota to negotiate: one endpoint, open CORS, cache-friendly. There is a per-IP brake against bursts, generous enough that ordinary use never meets it. It is designed to be the seed of an open standard, so the contract is small and stable.

The API ships with the registry platform. The v0 contract documented here is the launch contract; a v1 with list and search endpoints will follow. v0 will keep working unchanged when v1 arrives.

https://platform.thehumanbehind.com/api/v0
GET https://platform.thehumanbehind.com/api/v0/records/{slug}.json
  • slug is the record’s public identifier, the last segment of its page URL (/r/<slug>). It is matched case-insensitively (it is lowercased before lookup).

  • Method GET. No authentication, no API key.
  • A CORS preflight OPTIONS is answered with 204 and the same open-origin headers.

Every response, successful or not, carries the content type and the CORS headers below. The cache header is the one that differs: see the note under the table.

HeaderValue
content-typeapplication/json; charset=utf-8
access-control-allow-origin*
access-control-allow-methodsGET, OPTIONS
cache-controlpublic, max-age=300
  • CORS: access-control-allow-origin: *, so you can call it straight from a browser on any site.

  • Cache: a 200 comes back with cache-control: public, max-age=300 and is edge-cached for five minutes. Perfect freshness is not the point; verifiability is. A 404, on the other hand, comes back with cache-control: no-store, and on purpose: a record that becomes verified must appear the moment it does, not five minutes later, and caching “this does not exist” is how a new record stays invisible to whoever is checking it.

curl https://platform.thehumanbehind.com/api/v0/records/laia.json

The shape of a 200 OK response, application/json:

{
  "registry_code": "THB-2026-00001",
  "slug": "laia",
  "avatar_name": "Laia",
  "type": "avatar",
  "scope": "general",
  "operates_at": null,
  "responsible_name": "AIGiner S.L.",
  "status": "active",
  "verification_level": "verified",
  "verified_at": "2026-10-15T16:12:39.438015+00:00",
  "registered_at": "2026-07-30T23:44:44.547749+00:00",
  "content_hash": "23ec5dae1e4b330a1ebe266db49bf2d0ca88b7bb6fff0e0eeca1106bbbc5fb6d",
  "url": "https://platform.thehumanbehind.com/r/laia",
  "disputed_at": null
}

Worked example, with the exact shape of a real response. The public API only serves verified records, and identity verification is not open yet, so today the endpoint answers 404 for every slug, and its body says exactly why (see Status codes). That is the registry working as designed, not an outage: a free record stays private, and only verification makes it public.

The response contains only public fields. The owner’s email and account data are not part of the API surface, structurally, not just by policy: the API reads from a database view that simply does not contain them.

FieldTypeNotes
registry_codestringThe THB number, e.g. THB-2026-00001.
slugstringThe record’s public identifier.
avatar_namestringThe registered name.
typestring

avatar, voice_clone, agent, image, video or music.

scopestring

general, health, finance, legal, education, public_figure, sport or model.

operates_atstring or nullWhere it operates, or null if not declared.
responsible_namestringThe person who answers for it.
statusstring

Internal lifecycle state, active or unclaimed. What a record’s seal is comes from verification_level (registered or verified), not this field.

verification_levelstringregistered or verified.
verified_atstring or nullISO-8601 UTC date of verification, or null.
registered_atstringISO-8601 UTC registration timestamp.
content_hashstringLowercase hex SHA-256 of the registration snapshot.
urlstringCanonical link to the human-readable record page.
disputed_atstring or null

ISO-8601 UTC date on which someone contested this record, or null if nobody has. A disputed record keeps its number, its date and its hash, and keeps being served here: what it loses is the ability to be used as proof of priority while the dispute lasts. See Claims.

Two fields deserve a note. verification_level tells you which seal the record carries; when verified, verified_at carries the date (see Verification). And content_hash is what makes independent verification possible: recompute it from the very fields in this response using the exact public recipe.

A record is not a single moment. Every change to the eight sealed fields writes a version, and each version keeps its own hash and its own date, both anchored in Bitcoin. The record response carries a summary of that history, and there is a dedicated endpoint for the whole of it:

GET https://platform.thehumanbehind.com/api/v0/records/{slug}/history.json

Same rules as the endpoint above: open CORS, no key, cached five minutes, and a 404 whose body is byte for byte the one you already know, so asking about the history of a private record tells you exactly as little as asking about the record.

The history object inside the record response is the summary, capped at the twenty newest entries. Its two counters are named metadata_versions_included and instruction_versions_included, and the suffix is not decoration: they count what came back in that response, not how many versions the record has. When truncated is true there are more, and url points at the endpoint above, which is the one that paginates.

FieldTypeNotes
versions[].kindstringmetadata (the record’s content) or instructions (an agent’s).
versions[].versionnumber1 is the registration. Numbers are never reused.
versions[].hashed_atstringWhen that version was sealed.
versions[].content_hashstringContent versions only: the hash of THAT version.
versions[].snapshotobject

Content versions only: the exact object that was hashed. This is what lets anyone recompute an old version’s hash without an account and without trusting the row we serve today.

truncated / next_offsetbool / numberPaginate with ?offset= and ?limit=.

Why the instructions have no fingerprint here

Section titled “Why the instructions have no fingerprint here”

For a record of type agent, the history says how many times the instructions changed and when. It does not publish the text, which belongs to whoever wrote it, and it does not publish the text’s SHA-256 either.

That second one is deliberate and worth explaining, because a hash looks like it protects. It only does when its input is hard to guess. Agent instructions are frequently templates that repeat across clients, so publishing the digest would let anyone holding a candidate template confirm that this record uses it: a confirmation oracle, not a safeguard. It is the same failure the registry’s first anchoring recipe had, and the reason it was replaced within a day.

The fingerprint does travel to the record’s owner, in their downloadable proof and in their panel, where it is theirs to share or not.

StatusMeaningBody
200Record found and public.The JSON object above.
404

No public record with that slug: it never existed, or it is not publicly visible (only registered, unpublished or under review). The body is identical in every one of those cases, so the API never reveals whether a private record exists.

{ "error": "not_found", "reason": "not_public_until_verified", "message": "The public API only serves verified records. Identity verification is not open yet.", "docs": "https://thehumanbehind.com/docs/api/" }
429

Too many requests from one IP in a minute. The five-minute cache does not cover a caller who varies the query string, so there is a brake underneath it. Wait and retry.

{"error":"rate_limited"}
500Unexpected server error.{ "error": "server_error" }
204CORS preflight (OPTIONS).Empty.

Unpublished records and the JSON API. The JSON endpoint returns 404 for a record that was unpublished, because it is no longer in the public view. The dated 410 Gone notice, the one that keeps the trace, lives on the human-readable record page /r/<slug>, not on the JSON endpoint. The THB number is never reused either way.

const res = await fetch(
  "https://platform.thehumanbehind.com/api/v0/records/laia.json",
);
const record = await res.json();
console.log(record.registry_code); // "THB-2026-00001"
console.log(record.url);           // link to the human-readable page

The whole point of the API is that you do not have to trust it. Fetch a record and recompute its content_hash yourself with the public recipe. Here it is end to end in Python (standard library only):

import json, hashlib, urllib.request

SLUG = "laia"
url = f"https://platform.thehumanbehind.com/api/v0/records/{SLUG}.json"

# Con User-Agent propio: el de urllib por defecto ("Python-urllib/3.x") lo
# rechaza el Browser Integrity Check de Cloudflare con un 403. Cualquier
# cadena vale; identificarse es lo educado.
req = urllib.request.Request(url, headers={"User-Agent": "thb-verify/1.0"})
rec = json.load(urllib.request.urlopen(req))

# The eight snapshot fields; a missing operates_at becomes "".
fields = ["registry_code", "slug", "avatar_name", "type",
          "scope", "operates_at", "responsible_name", "registered_at"]
snapshot = {k: (rec.get(k) or "") for k in fields}

# The timestamp is canonicalised before hashing: the registry seals
# "...862834Z", while the API returns the raw value "...862834+00:00".
# Skip this line and the digest will not match.
snapshot["registered_at"] = snapshot["registered_at"].replace("+00:00", "Z")

# Canonical form = PostgreSQL jsonb text: keys sorted by (length, then bytes),
# ": " after each key and ", " between pairs.
ordered = dict(sorted(snapshot.items(), key=lambda kv: (len(kv[0]), kv[0])))
canonical = json.dumps(ordered, separators=(", ", ": "), ensure_ascii=False)

digest = hashlib.sha256(canonical.encode("utf-8")).hexdigest()
print(digest == rec["content_hash"])  # True

The v0 in the path is a promise: this contract will not change under you. New capabilities (listing, search, filters) will arrive as v1 on their own paths, and v0 record lookups will keep returning exactly the shape documented here.

The API is free for any reasonable use: embedding a seal check on your site, building a lookup tool, research. Responses are edge-cached; please do not hammer the origin with cache-busting traffic. If you need bulk access or list endpoints, talk to us. That is what v1 is for.