API reference

The full surface is specified in OpenAPI 3.1. Runnable versions of every sample on this page live in the examples repo.

Fetch a profile

One URL, four representations. The Accept header picks the format: HTML for browsers, JSON for integrations, Markdown for terminals and LLMs, JSON-LD (schema.org/Person) for knowledge graphs, or a signed JWS bundle you can verify offline. In a browser, ?accept=json overrides the header.

curl -s -H 'accept: application/json'      https://username.md/v1/users/chris/profile
curl -s -H 'accept: text/markdown'         https://username.md/v1/users/chris/profile
curl -s -H 'accept: application/ld+json'   https://username.md/v1/users/chris/profile
curl -s -H 'accept: application/jose+json' https://username.md/v1/users/shannon/profile

Verify a signed response

Every API response is signed with the platform's Ed25519 key (RFC 9421 HTTP Message Signatures + RFC 9530 Content-Digest). Check three response headers and you can prove a payload came from username.md — even if it reached you through caches, proxies, or another agent. The verification key is published at /.well-known/http-message-signatures-directory.

# See the signature headers on any response
curl -sI -H 'accept: application/json' https://username.md/v1/users/chris/profile \
  | grep -iE 'content-digest|signature'

# Content-Digest:  sha-256=:<base64(sha256(body))>:
# Signature-Input: sig1=("content-digest" "@authority");created=...;keyid="platform-ed25519";alg="ed25519"
# Signature:       sig1=:<base64(ed25519 signature)>:
Complete, runnable verifiers: verify_signature.py · verify-signature.mjs · verify/main.go

Read verified claims

A claim is a statement about a handle — "owns this domain", "controls this GitHub account", "this Bluesky identity is mine" — signed as a W3C Verifiable Credential with the handle's own Ed25519 key. The bundle includes the public key, so you can verify every claim without trusting the API.

curl -s https://username.md/v1/users/chris/claims
# → { "did": "...", "public_jwk": {...}, "claims": [ { "type": "atprotoHandle", "jws": "...", ... } ] }
Full version with the DNS-TXT domain-proof flow: claims_and_domains.py

Resolve identity documents

Every handle publishes the documents agents use to find and verify it: a did:web document (a public key record served as plain JSON), an ATProto/Bluesky DID, and per-handle llms.txt instructions for AI agents.

# did:web document (public keys + service endpoints)
curl -s https://username.md/users/chris/.well-known/did.json

# ATProto DID — what Bluesky queries to resolve @chris.username.md
curl -s https://username.md/users/chris/.well-known/atproto-did

# Bluesky binding status + the signed atprotoHandle credential
curl -s https://username.md/v1/users/chris/atproto

# Instructions for AI agents interacting with this handle
curl -s https://username.md/users/chris/llms.txt
All endpoints and schemas: browsable reference · openapi.yaml

Resolve gamer presence

In development · founding access. The presence records (_playing SRV / _presence TXT) are the gamer-tier surface described on the gamers page. Until they're announced in llms.txt, lookups return empty answers — write your integration to treat "no records" as "not published" (never as an error) and fall back to the profile API, which is live today. These samples run as-is: they degrade gracefully to the fallback.

"Is /u/chris on right now, and where do I join?" Presence publishes as DNS SRV/TXT under the handle — the same service-discovery machinery game servers already use, pointed at a player. Anything that can do a DNS lookup — a Discord bot, an LFG site, a squadmate's agent — can answer it with no API keys and no polling five launchers. Short TTLs apply, but DNS caches are still DNS caches; the click-through join link routes through the live edge instead.

# In game right now? SRV answers with the join target
dig +short SRV _playing._tcp.chris.username.md
# → 10 0 25565 mc.chris.username.md.        (Minecraft clients join this natively)

# Structured presence: live=, game=, join=, party=, next=
dig +short TXT _presence.chris.username.md

# Fallback that always answers today: who is /u/chris, and what do they play?
curl -s -H 'accept: application/json' https://username.md/v1/users/chris/profile
Runnable versions with the full fallback chain: squad_check.py · squad-check.mjs · curl README · what it's for: username.md for gamers

Import from Keybase

Bring your Keybase identity over in two calls: preview what would change, then apply. Your verified social proofs become profile links, your PGP key is served at /keybase/pgp.asc, and a third call re-verifies the proofs under this platform's own keys — each one becomes a signed keybaseProof Verifiable Credential your agents can check offline. Imports are additive: nothing you already set is overwritten unless you say so.

# 1. Preview — what would an import change? (writes nothing)
curl -s -X POST https://username.md/v1/users/you/import/keybase/preview \
  -H "authorization: Bearer $USERNAME_MD_TOKEN" -H 'content-type: application/json' \
  -d '{"keybase_username":"yourkeybasename"}'

# 2. Apply — profile fields, verified proofs as links, PGP key
curl -s -X POST https://username.md/v1/users/you/import/keybase \
  -H "authorization: Bearer $USERNAME_MD_TOKEN" -H 'content-type: application/json' \
  -d '{"keybase_username":"yourkeybasename"}'

# 3. Re-verify the proofs under username.md's own keys → signed VCs
curl -s -X POST https://username.md/v1/users/you/keybase/verify \
  -H "authorization: Bearer $USERNAME_MD_TOKEN"

# Public, no auth: import status + your PGP key
curl -s https://username.md/v1/users/you/keybase
curl -s https://username.md/v1/users/you/keybase/pgp.asc

Own your handle: write operations

Write operations need your API token — you get it when you claim a handle, and can regenerate it in the control panel. Send it as Authorization: Bearer <token> and keep it out of your shell history.
Prefer talking to an AI? Your agent or chatbot can run these updates for you: give it SKILL.md (it documents the write endpoints and the etiquette) plus an API key, then just say "update my tagline". Mint the agent its own key in the panel so you can revoke it independently.
# Update profile fields (partial — omitted fields stay unchanged)
curl -s -X PATCH https://username.md/v1/users/you/profile \
  -H "authorization: Bearer $USERNAME_MD_TOKEN" -H 'content-type: application/json' \
  -d '{"name":"Your Name","tagline":"builder","links":[{"label":"GitHub","href":"https://github.com/you"}]}'

# Set your profile theme
curl -s -X PUT https://username.md/v1/users/you/preferences \
  -H "authorization: Bearer $USERNAME_MD_TOKEN" -H 'content-type: application/json' \
  -d '{"theme":"terminal","accent":"#7ee787"}'

# Prove you own a domain (publish the returned TXT record, then /check)
curl -s -X POST https://username.md/v1/users/you/verifications/dns-txt \
  -H "authorization: Bearer $USERNAME_MD_TOKEN" -H 'content-type: application/json' \
  -d '{"domain":"yourdomain.com"}'

# Bind your Bluesky identity → @you.username.md
curl -s -X PUT https://username.md/v1/users/you/atproto \
  -H "authorization: Bearer $USERNAME_MD_TOKEN" -H 'content-type: application/json' \
  -d '{"did":"did:plc:your24charplcidentifier"}'
Every owner endpoint with payload shapes: curl README · browsable reference