For platforms and integrators
If you run a platform or moderation tool and want to check whether an avatar has a THB record, or surface a “registered/verified” signal next to content, this page is the map to what you can build on today with the public directory and the API.
The public API
Section titled “The public API”The whole surface is a single, open endpoint:
GET /api/v0/records/<slug>.json. No API key, no
account, CORS enabled so you can call it directly from the browser. The
full contract, fields, response headers and error bodies, is documented
once, in Public API; this page does
not repeat it.
One thing to plan for before you build: the endpoint only serves
verified records, and identity verification is not open yet, so
today every slug answers 404 {"error":"not_found"}. That is the
registry working as designed, not an outage: a free record is a
registration and stays private, and only verification makes it public.
Build the integration now if you like, but expect the first 200 OK
once verification is live.
What each level means for moderation
Section titled “What each level means for moderation”| Level | What you can infer |
|---|---|
| No record found | Either nobody has registered that avatar with THB, or its owner registered it privately and never verified it. The API returns the same 404 in both cases, by design, so you cannot use “not found” as a negative signal about the content itself. |
| Verified | A real, identified human declared responsibility for this avatar and passed an identity check. That is a positive accountability signal, it is not a content-safety rating and it says nothing about whether the content itself is appropriate for your platform. |
In short: treat a THB lookup as one input, “is there an accountable, identity-checked person behind this”, alongside whatever content review your own platform already does, not as a substitute for it.
Linking a seal check to a record
Section titled “Linking a seal check to a record”If you display a “Checked with TheHumanBehind” affordance next to
content, link it straight to the record’s page at
/r/<slug> rather than just showing a static badge.
That lets your own users verify the record independently instead of
trusting your integration blindly, the same principle behind
Verifying a seal you saw somewhere.
CORS and fair use
Section titled “CORS and fair use”Responses carry an open access-control-allow-origin: * and
are cached for five minutes at the edge. That is enough for a lookup
triggered per piece of content or per moderation review. It is not
designed for crawling the whole registry: please do not build a bulk
scraper against the single-record endpoint.
Bulk and list access
Section titled “Bulk and list access”There is no list or search endpoint in the current v0
contract, only single-record lookups. If your integration needs bulk
access, a webhook, or a list/search endpoint, get in touch,
that is exactly the kind of need a future v1 is meant
to cover, and v0 will keep working unchanged once it
arrives.
Managing your OWN records: service keys
Section titled “Managing your OWN records: service keys”Everything above is about READING somebody else’s record. If what you need is the opposite, registering and updating records that are yours, from your own system rather than through the form, that is a different door and it needs a key.
A company launching fifty agents should not have to fill in fifty forms.
Create a service key in your panel, under API keys, and use it as
a bearer token against platform.thehumanbehind.com/api/manage/v1/.
- The key is shown once, when you create it. We store only its SHA-256, so we cannot show it to you again, and if you lose it you create another one and revoke the old.
- It travels in the
Authorizationheader, never in the URL. A key in a query string ends up in access logs, inRefererheaders and in browser history. - It reaches your records and nothing else: not the panel, not
billing, not anyone else’s data. Asking for a record that is not yours
answers exactly the same
404as one that does not exist. - It is a bearer credential: whoever holds it, is you. There is no request signature and no time window. Treat it like a password, keep it server-side, and revoke it from the panel the moment you suspect a leak. Revocation takes effect immediately.
- 60 requests per minute per key.
- It carries the permissions you gave it when you created it, and they
cannot be widened afterwards. You choose between read only (look up
your records and the state of their seals) and read and write (that,
plus registering and editing), and separately you can tick read the
instruction text, which is its own permission because your agents’
skill.mdis yours and a leaked key should not be able to pull it. A key without it sees each version’s fingerprint and date, never the text.
Registering a record
Section titled “Registering a record”Idempotency-Key is required on creation, and it is what makes a
retried deployment safe: the same key with the same body returns the
same record instead of registering a second one. Reuse the same value
only with the same body; the same key with a different body is a
409 idempotency_key_reuse.
curl -X POST https://platform.thehumanbehind.com/api/manage/v1/records \
-H "Authorization: Bearer thb_sk_..." \
-H "Idempotency-Key: deploy-2026-08-11-agent-01" \
-H "Content-Type: application/json" \
-d '{"avatarName":"Atlas",
"type":"agent",
"scope":"general",
"responsibleName":"Name Surname",
"declaration":true}'
"declaration": true is not boilerplate. It is the same responsibility
declaration a person accepts with the checkbox on the form: you are
stating that a real, identifiable human answers for this record. The
server will not write it on your behalf, so a request without it is a
400 invalid_form.
Updating an agent’s instructions
Section titled “Updating an agent’s instructions”curl -X PUT https://platform.thehumanbehind.com/api/manage/v1/records/<id>/instructions \
-H "Authorization: Bearer thb_sk_..." \
-H "Content-Type: application/json" \
-d '{"instructions":"# Atlas\n..."}'
Every write that actually changes the text archives the previous one,
whole, with its own fingerprint and date. Writing the same text again
changes nothing and answers created: false with the version already in
force, so a deployment that runs twice does not invent a version.
GET on the same URL returns the history: version, fingerprint and
date, paginated with ?limit and ?offset (the response carries
has_more and next_offset). The TEXT is never in there.
For the text you ask for one specific version, with a key that carries the instructions permission:
curl "https://platform.thehumanbehind.com/api/manage/v1/records/<id>/instructions?include=text&version=3" \
-H "Authorization: Bearer thb_sk_..."
Without version it answers 400 version_required, and without that
permission it answers the same 401 as an unknown key. There is no way
to ask for all of them at once, deliberately: instructions are their
author’s intellectual property, so showing them is a decision rather
than a default, and draining them in bulk is not something that should
fit in one request.
The rest of the surface
Section titled “The rest of the surface”| Request | What it does |
|---|---|
GET /api/manage/v1/whoami |
Checks the key and reports its scopes, how many records the account has and its ceiling. Start here. |
GET /api/manage/v1/records |
Lists your records, paginated with ?limit and ?cursor. The cursor it returns (next_cursor) is opaque: send it back as it came, do not build one. |
GET and PATCH on /api/manage/v1/records/<id> |
Reads or edits one of yours. |
Errors are stable: 400 (invalid_json, invalid_form,
idempotency_key_required, reserved_name, immutable_field), 401 unauthorized (the same body for a missing, malformed, unknown, revoked
or expired key, and for one without the right scope), 404 not_found,
409 (idempotency_key_reuse, idempotency_in_flight,
verified_identity_locked, constraint_violation, record_removed),
413 too_large, 429 (rate_limited, quota_exceeded,
lifetime_quota_exceeded), 500 server_error.
Three of those are worth a sentence, because they are the ones that stop
a loop rather than one request. immutable_field is a PATCH touching
something that never changes (the number, the address, the registration
date). record_removed is a write against a record that has been taken
down: the proof is kept, the record does not accept changes any more.
And lifetime_quota_exceeded is not the per-minute limit but the
account’s ceiling on how many records it may ever register: retrying
will not clear it, talk to us instead.
There is no CORS on this tree, deliberately: a secret key has no business being in a frontend, and without the headers a browser cannot send it cross-origin at all.
Two rules worth knowing before you build. Registering is subject to the
same per-account ceiling as the form, so check whoami first and talk
to us if your import does not fit. And a record that has been
verified cannot have its name, type, scope or responsible changed
any more, by any route: that is what the seal attests to, and letting it
change afterwards would be a bait-and-switch on everyone who trusted it.