SelfBadge
Look up verified people on SelfBadge and tell them apart from namesakes. Read-only, no sign-up needed.
Gehosteter MCP-Server
npx add-mcp 'https://selfbadge.com/mcp'Installiert in Claude Code, Codex, Cursor und mehr
Dokumentation
SelfBadge holds verified, structured profiles of people. You can read them three ways: the JSON-LD in every profile page, the public lookup API, and the MCP server for AI agents. All three return only what the person made public, and mark every fact with how it was verified.
Background: the reference library explains the data model, including how to read a SelfBadge profile,verification levels and stable identifiers.
Lookup API
Base address: https://selfbadge.com/api/v1. Answers are JSON (UTF-8), open to any origin (CORS).
The whole API is described as OpenAPI 3.1 at https://selfbadge.com/openapi.json: every call, parameter, answer and error, for code generators and AI agents. Agents can also start with agents.md.
| Call | What it returns |
|---|---|
GET /api/v1/people/<handle> | One profile: every public fact with its verification level and source, the checks that verified them, and the schema.org JSON-LD. |
GET /api/v1/people?id=<SelfBadge id> | The same, by the stable id (https://selfbadge.com/<handle>#person), which survives a handle change. |
GET /api/v1/search?name=&job_title=&organization=&location= | People by name, every public profile (claimed or seeded), each with the hints that tell namesakes apart (job title, organization, location, description). Hints you pass rank the best match first. |
GET /api/v1/organizations/<slug> | An organization's public facts and JSON-LD, with its team members and founders on SelfBadge. |
Add ?format=jsonld (or Accept: application/ld+json) to a people call to get only the JSON-LD. Errors look like {"error":{"code":"not_found","message":"..."}}.
Example:
curl "https://selfbadge.com/api/v1/search?name=Noa%20Bergman&organization=Harbor%20Ventures"
Verification levels
- 0 Seeded: from an open public source (such as Wikidata), not claimed by the person. Unconfirmed.
- 1 Self-declared: added by the person, not independently checked.
- 2 Account-verified: proven by signing in to the account, a work-email code, a DNS record or a link-back.
- 3 Document-verified and 4 ID-verified.
What is searchable
Name search covers every public profile. Profiles we created from public sources and the person has not claimed yet are marked seeded, with a note: treat their facts as unconfirmed (level 0). Claimed profiles come before seeded ones when both match equally.
Limits and API keys
Without a key: 120 calls an hour per network address. With a free API key: 5000 calls an hour per key. Every answer carries X-RateLimit-Limit and X-RateLimit-Remaining; over the limit you get 429 with Retry-After.
Get a key: sign in, then open Account > API keys. Send it as Authorization: Bearer sbk_... (or X-API-Key). Keys are shown once; we keep only a hash. You can revoke a key at any time.
- Sign in to SelfBadge with LinkedIn or Google.
- Open Account > API keys, name the key (for example "research agent") and choose Create.
- Copy the key now: it starts with
sbk_and is shown only once. - Send it with every API call and in your MCP client's settings, as shown below.
The same limits cover the API and the MCP server together. A wrong or revoked key gets 401; repeated wrong keys from one address are slowed down. MCP errors keep the JSON-RPC shape: a bad tool call comes back as a tool result with isError: true and a message that says what to fix.
MCP server for AI agents
Address: https://selfbadge.com/mcp (Model Context Protocol, Streamable HTTP, no session). It has the same limits as the API, and takes the same key.
The server is listed as com.selfbadge/selfbadge in the MCP Registry, and its server card is at /.well-known/mcp/server-card.json. It needs no sign-in; a key only raises your limit (see Limits and API keys).
Connect it
Claude (claude.ai and Claude Desktop)
- Open Settings, then Connectors, and choose Add custom connector.
- Name it SelfBadge and paste the address below. Leave the OAuth fields empty: SelfBadge needs no sign-in.
- In a chat, turn SelfBadge on from the tools menu and ask, for example: Which Noa Bergman on SelfBadge works at Harbor Ventures?
https://selfbadge.com/mcp
Custom connectors cannot send a key, so Claude uses the anonymous limit. On Team and Enterprise plans an owner adds the connector for the organization first.
Claude Code
- Run this in a terminal (add the header line for your key, or leave it out).
claude mcp add --transport http selfbadge https://selfbadge.com/mcp \
--header "Authorization: Bearer sbk_your_key_here"
ChatGPT
- Turn on developer mode: Settings, Apps and connectors, Advanced settings (on plans where it is offered).
- Choose Create, name it SelfBadge, paste the address below and set authentication to No authentication.
- Start a chat in developer mode and pick SelfBadge from the tools.
https://selfbadge.com/mcp
ChatGPT cannot send a key either, so the anonymous limit applies.
Cursor
- Add this to ~/.cursor/mcp.json (all projects) or.cursor/mcp.json (one project), then reload Cursor.
{
"mcpServers": {
"selfbadge": {
"url": "https://selfbadge.com/mcp",
"headers": {
"Authorization": "Bearer sbk_your_key_here"
}
}
}
}
VS Code (GitHub Copilot agent mode)
- Add this to.vscode/mcp.json in your project, or run MCP: Add Server from the command palette and choose HTTP.
{
"servers": {
"selfbadge": {
"type": "http",
"url": "https://selfbadge.com/mcp",
"headers": {
"Authorization": "Bearer sbk_your_key_here"
}
}
}
}
Any other MCP client
- Clients that take a JSON list of servers use this shape. Remove the headers to stay anonymous.
{
"mcpServers": {
"selfbadge": {
"type": "http",
"url": "https://selfbadge.com/mcp",
"headers": {
"Authorization": "Bearer sbk_your_key_here"
}
}
}
}
Tools
find_person: Search SelfBadge for people with a given name. Use this to identify a person correctly and tell them apart from namesakes: each candidate comes with their job title, organization, location and a short description. Pass what you already know (job title, organization, location) so the best match comes first, then call get_person with the chosen id for the full, verified profile. It returns public SelfBadge profiles, each marked claimed or created from public sources (seeded: level 0, unconfirmed until the person claims it). An empty result means SelfBadge has no public profile with that name, not that the person does not exist.get_person: Get one person's SelfBadge profile: every public fact (name, roles, organizations, education, accounts, published work and more) with its verification level and source, plus schema.org JSON-LD. Use it after find_person, or when you already have a SelfBadge handle, profile address or id. Profiles created from public sources and not yet claimed are marked seeded; treat their facts as unconfirmed.get_organization: Get an organization's SelfBadge page: its public facts, schema.org JSON-LD, and the people on SelfBadge linked to it (team members who accepted their organization's invitation, and founders). Use it to check whether someone works at or founded an organization.
Partner API
For organizations that already know a person (a recruiter, a professional body, a conference, a service that confirms identities) and want to give them a SelfBadge. It needs a partner client from SelfBadge: write to [email protected]. The lookup API above stays open, with optional keys. Every call, field, example and error is in /openapi.json (security scheme partnerOAuth), and the @selfbadge/sdk package wraps them.
- Sign-in: OAuth 2.0 client credentials.
POST https://selfbadge.com/oauth/tokenwithgrant_type=client_credentialsand your client id and secret gives a bearer token for one hour. Scopes:profiles:read,profiles:write,claim-links:write,profiles:update,claims:attest,plans:write. Metadata:/.well-known/oauth-authorization-serverand/.well-known/oauth-protected-resource. - Capabilities: attested claims, updating facts, plans, revocations and muted emails are switched on for your client by SelfBadge. Without one, the call answers
403 capability_not_enabledand nothing else changes.
Endpoints
POST /api/partners/v1/profiles: a draft profile (never public until the person's claim). A profile with the same name and organization comes back as a possible match instead of a duplicate.GET /api/partners/v1/profiles/<handle>: the status, the name's verification level and method, published and indexable, Pro, the IndexNow and Bing dates, the last 30 days of search engine and AI reads, and your own facts with their ids.PATCH /api/partners/v1/profiles/<handle>: add facts, or change or remove the facts you added. Verified facts, the person's own facts and facts from public sources are refused, each with its field.DELETE /api/partners/v1/profiles/<handle>: a draft nobody has claimed.POST /api/partners/v1/claim-links: a signed claim link (30 days, one email, used once).POST /api/partners/v1/claims: claim a profile for a person whose identity you confirmed.POST /api/partners/v1/plans: Pro for an order (1, 3 or 12 months), extending the current end date.POST /api/partners/v1/revocations: a refund or a withdrawal.GET /api/partners/v1/profiles/<handle>?view=public: the whole public page of a profile you created or manage (scopeprofiles:read_public).POST /api/partners/v1/profiles/<handle>/edit-link: a one-time link (15 minutes) that takes the profile's owner to the editor. It never signs anyone in.POST /api/partners/v1/preview: the same body as a draft; nothing is created. A signed address (30 minutes) shows the profile as it would look, marked "Preview, not live yet".
A draft
POST https://selfbadge.com/api/partners/v1/profiles
Authorization: Bearer sbt_...
Idempotency-Key: 8f14e45f-ceea-467a-9a36-dedd4bea2543
{
"name": "Lena Marlowe",
"email": "[email protected]",
"other_names": [
"Lena M. Marlowe"
],
"job_title": "Head of Research",
"organization": "Northwind Labs",
"city": "Lisbon, Portugal",
"same_as": [
"https://www.linkedin.com/in/lena-marlowe-example"
],
"facts": [
{
"property": "knowsLanguage",
"value": {
"code": "pt",
"level": "Fluent"
}
},
{
"property": "alumniOf",
"organization": "University of Porto",
"value": {
"credential": "MSc, Data Science"
}
}
]
}
201 Created
{
"result": "created",
"profile": {
"handle": "lena-marlowe",
"status": "draft",
"created_at": "2026-11-16T09:30:00.000Z",
"expires_at": "2027-01-15T09:30:00.000Z",
"url": null
},
"claim_link": null
}
Facts come from the editor's closed lists, with an organization where the property names one, a source_url and public or private. Never birth dates, gender, nationality, home addresses or phone numbers. The not_confused_with text (120 characters) needs an active Pro plan from you for that person.
An attested claim
POST https://selfbadge.com/api/partners/v1/claims
{
"handle": "lena-marlowe",
"email": "[email protected]",
"method": "id_check",
"evidence": {
"verification_session_id": "vs_1QxExample000000",
"verified_name": "Lena Marlowe"
},
"declaration": {
"text": "I confirm I am this person and that the facts are about me",
"accepted_at": "2026-11-16T09:38:00Z",
"ip": "203.0.113.24"
}
}
201 Created
{
"result": "published",
"handle": "lena-marlowe",
"url": "https://selfbadge.com/lena-marlowe",
"verification": {
"level": 4,
"method": "id_check"
}
}
Methods: linkedin and facebook (account_id, profile_url, the name the provider returned; level 2, and the matching account link is verified too), work_email (email, name; level 2, always reviewed), website (url where the code was found, name; level 2, always reviewed), manual (reviewer_id and reviewer_name of a named person on your staff, note, name; level 3; an id that reads as an automatic process is refused) and id_check (the Stripe Identity verification_session_id and verified_name; level 4). Google sign-in is never accepted. partner_declaration (declared_at, declared_name, email, name_rule; a capability SelfBadge switches on) is the person's own declaration: it verifies nothing, so the name and every fact stay self-declared (level 1), labelled "Declared by the person at" your name. SelfBadge makes an account for the owner's email; the person signs in later with LinkedIn or Google using that email.
It is published at once when the method confirms the name (linkedin, facebook, id_check or manual), the attested name matches the profile's name or one of its other names (accents, case and initials ignored; a name in another script only through the other names), the profile is your own draft and that email's account has no profile yet. A work email or a website code proves an address, not a name, so work_email and website always go to review (reason method_needs_review), whatever name you send. Otherwise too a person at SelfBadge reviews it with your evidence:
202 Accepted
{
"result": "review",
"handle": "miriam-adler-stone",
"claim_id": "3a5c7e9b-2d4f-4a6b-8c0e-1f3a5c7e9b2d",
"reasons": [
"public_source"
]
}
The evidence and the declaration stay private: never on the page, in the JSON-LD or in .json.
Updates, status, plans and revocations
PATCH https://selfbadge.com/api/partners/v1/profiles/lena-marlowe
{
"change": [
{
"id": "6f1c2e8a-0d4b-4c55-9f3e-2b7a1c9d8e01",
"value": "Director of Research"
}
],
"add": [
{
"property": "award",
"value": {
"name": "Iberian Data Award",
"year": "2025"
}
}
],
"not_confused_with": "Lena Marlowe, the illustrator from Porto"
}
GET https://selfbadge.com/api/partners/v1/profiles/lena-marlowe
{
"handle": "lena-marlowe",
"status": "published",
"url": "https://selfbadge.com/lena-marlowe",
"verification": {
"level": 4,
"method": "id_check"
},
"published": true,
"indexable": true,
"pro_until": "2027-02-16T09:40:00.000Z",
"indexnow_at": "2026-11-16T09:40:01.000Z",
"bing_at": "2026-11-17T06:12:00.000Z",
"crawlers_30d": {
"Googlebot": 3,
"Bingbot": 2,
"GPTBot": 1
},
"facts": [
{
"id": "6f1c2e8a-0d4b-4c55-9f3e-2b7a1c9d8e01",
"property": "jobTitle",
"value": "Head of Research",
"visibility": "public",
"source_url": "https://northwind.example",
"verification_level": 1
}
]
}
POST https://selfbadge.com/api/partners/v1/plans
{
"handle": "lena-marlowe",
"months": 3,
"order_id": "pm_order_10482"
}
201 Created
{
"result": "created",
"order_id": "pm_order_10482",
"handle": "lena-marlowe",
"pro_until": "2027-02-16T09:40:00.000Z"
}
POST https://selfbadge.com/api/partners/v1/revocations
{
"order_id": "pm_order_10482",
"reason": "refund"
}
200 OK
{
"result": "revoked",
"scope": "profile",
"order_id": "pm_order_10482",
"pro_ended": 1,
"profile": "removed"
}
scope is profile by default (a refund, or the withdrawal of the first purchase): your Pro ends at once. A profile you created is removed (410 Gone, out of search engines' and AI services' sources); a profile from public sources claimed through you goes back to unclaimed with its public-source facts. No partner can claim it again. Pro bought through a partner follows that partner's refund terms.
POST https://selfbadge.com/api/partners/v1/revocations
{
"order_id": "pm_order_10517",
"reason": "withdrawal",
"scope": "order"
}
200 OK
{
"result": "revoked",
"scope": "order",
"order_id": "pm_order_10517",
"pro_ended": 1,
"profile": "none",
"pro_until": "2027-01-24T09:30:00.000Z"
}
scope: "order" (for example the withdrawal of a later renewal) cancels only that order's Pro. Your later orders for the person move earlier by the time it had not used; pro_until is the end of what remains, or null when Pro has ended. The profile, the claim, the verifications and the facts stay. A published "not to be confused with" stays; adding or changing it needs Pro again. The same order again answers already_revoked; an order you never sent answers 404.
Errors and idempotency
400 Bad Request
{
"error": "invalid_fields",
"message": "facts[0].value.code: Choose a language from the list.",
"fields": {
"facts[0].value.code": "Choose a language from the list.",
"email": "Give the person's email address (it stays private)."
}
}
Every error is error (a stable code), message (plain text) and, when fields are at fault, fields with every problem at once. Send an Idempotency-Key header on every create call: the same key and body within 24 hours answers the first answer again (Idempotency-Replayed: true); the same key with another body answers 409 idempotency_key_reused. Keys are kept per client. Plans are also idempotent by order_id.
Webhooks
Events go to the address SelfBadge sets for your client: review_needed, review_decided (approved or rejected, with the reason) and published; then, for a profile you created or manage, flagged, disputed, restored and removed, with {handle, reason_code, at} only (never who reported it or what they wrote), and owner_changed with {handle, at} when the person moved the profile to the Google or LinkedIn account they sign in with (a code sent to the declared email; never an email in the event). verification_changed carries {handle, level, method, fact_ids, at} when public facts gain, lose or change their verification, at most once a minute per profile; GET /profiles/{handle} gives the name's current verification, claimed_via, all_facts and completeness. Each has SelfBadge-Event, SelfBadge-Delivery and SelfBadge-Signature: t=<unix seconds>,v1=<hex>, the HMAC-SHA256 of <t>.<raw body> with your webhook secret. Answer 2xx; anything else is tried again after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours.
{
"id": "evt_8c1f0d3e5a7b49c2a0e6f4d2b8c1a3e5",
"event": "review_decided",
"created_at": "2026-11-16T10:02:00.000Z",
"data": {
"handle": "miriam-adler-stone",
"claim_id": "3a5c7e9b-2d4f-4a6b-8c0e-1f3a5c7e9b2d",
"decision": "approved",
"reason": null
}
}
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody: the request body exactly as received, before JSON.parse.
function fromSelfBadge(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const want = createHmac("sha256", secret).update(\`${t}.${rawBody}\`).digest("hex");
return v1.length === want.length && timingSafeEqual(Buffer.from(v1), Buffer.from(want));
}
Test mode
A test client gets the same answers and webhooks (marked "test": true), but nothing it sends is ever public: its drafts are deleted after 24 hours, its claims make no account and touch no profile, its plans never give Pro, and a claim that needs review is decided at once (test_outcome: approved or rejected).
Using the data
Use it to identify people correctly and to tell them apart from namesakes. Do not use it to build profiles of people for marketing, to contact them in bulk, or to re-publish facts without their verification status. Respect a person's changes: re-read a profile rather than keeping old copies. The full terms are in our Terms of Service.
Questions: [email protected].