One handle, one lookup. Everything below runs against the live API at
https://username.md — no signup needed for reads.
The full surface is specified in OpenAPI 3.1. Runnable versions of every sample on this page live in the examples repo.
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/chris/profileimport requests
url = "https://username.md/v1/users/chris/profile"
profile = requests.get(url, headers={"Accept": "application/json"}).json()
print(profile["handle"], profile["did"])
markdown = requests.get(url, headers={"Accept": "text/markdown"}).text
person = requests.get(url, headers={"Accept": "application/ld+json"}).json() # schema.org/Personconst url = "https://username.md/v1/users/chris/profile";
const profile = await (await fetch(url, { headers: { accept: "application/json" } })).json();
console.log(profile.handle, profile.did);
const markdown = await (await fetch(url, { headers: { accept: "text/markdown" } })).text();
const person = await (await fetch(url, { headers: { accept: "application/ld+json" } })).json();req, _ := http.NewRequest("GET", "https://username.md/v1/users/chris/profile", nil)
req.Header.Set("Accept", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer resp.Body.Close()
var profile struct {
Handle string `json:"handle"`
Did string `json:"did"`
}
json.NewDecoder(resp.Body).Decode(&profile)
fmt.Println(profile.Handle, profile.Did)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)>:import base64, hashlib, requests
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
host = "username.md"
jwks = requests.get(f"https://{host}/.well-known/http-message-signatures-directory").json()
jwk = next(k for k in jwks["keys"] if k["kid"] == "platform-ed25519")
pub = Ed25519PublicKey.from_public_bytes(base64.urlsafe_b64decode(jwk["x"] + "=="))
resp = requests.get(f"https://{host}/v1/users/chris/profile", headers={"Accept": "application/json"})
digest = resp.headers["Content-Digest"]
assert digest == "sha-256=:" + base64.b64encode(hashlib.sha256(resp.content).digest()).decode() + ":"
params = resp.headers["Signature-Input"].removeprefix("sig1=")
sig = base64.b64decode(resp.headers["Signature"][len("sig1=:"):-1])
base = f'"content-digest": {digest}\n"@authority": {host}\n"@signature-params": {params}'.encode()
pub.verify(sig, base) # raises if forgedimport { createPublicKey, createHash, verify } from "node:crypto";
const host = "username.md";
const jwks = await (await fetch(`https://${host}/.well-known/http-message-signatures-directory`)).json();
const key = createPublicKey({ key: jwks.keys.find(k => k.kid === "platform-ed25519"), format: "jwk" });
const resp = await fetch(`https://${host}/v1/users/chris/profile`, { headers: { accept: "application/json" } });
const body = Buffer.from(await resp.arrayBuffer());
const digest = resp.headers.get("content-digest");
if (digest !== `sha-256=:${createHash("sha256").update(body).digest("base64")}:`) throw new Error("digest mismatch");
const params = resp.headers.get("signature-input").replace(/^sig1=/, "");
const sig = Buffer.from(resp.headers.get("signature").slice("sig1=:".length, -1), "base64");
const base = Buffer.from(`"content-digest": ${digest}\n"@authority": ${host}\n"@signature-params": ${params}`);
if (!verify(null, base, key, sig)) throw new Error("signature FAILED");// Fetch the JWKS, then any response; rebuild the RFC 9421 §2.5 signature base.
sum := sha256.Sum256(body)
expected := "sha-256=:" + base64.StdEncoding.EncodeToString(sum[:]) + ":"
if digest != expected { log.Fatal("digest mismatch") }
params := strings.TrimPrefix(resp.Header.Get("Signature-Input"), "sig1=")
sig, _ := base64.StdEncoding.DecodeString(
strings.TrimSuffix(strings.TrimPrefix(resp.Header.Get("Signature"), "sig1=:"), ":"))
sigBase := fmt.Sprintf("%q: %s\n%q: %s\n%q: %s",
"content-digest", digest, "@authority", "username.md", "@signature-params", params)
if !ed25519.Verify(pub, []byte(sigBase), sig) { log.Fatal("signature FAILED") }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": "...", ... } ] }import base64, requests
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
b64url = lambda s: base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))
bundle = requests.get("https://username.md/v1/users/chris/claims").json()
pub = Ed25519PublicKey.from_public_bytes(b64url(bundle["public_jwk"]["x"]))
for claim in bundle["claims"]:
signing_input, sig = claim["jws"].rsplit(".", 1)
pub.verify(b64url(sig), signing_input.encode()) # raises if forged
print("VALID:", claim["type"])import { createPublicKey, verify } from "node:crypto";
const bundle = await (await fetch("https://username.md/v1/users/chris/claims")).json();
const key = createPublicKey({ key: bundle.public_jwk, format: "jwk" });
for (const claim of bundle.claims) {
const i = claim.jws.lastIndexOf(".");
const ok = verify(null, Buffer.from(claim.jws.slice(0, i)),
key, Buffer.from(claim.jws.slice(i + 1), "base64url"));
console.log(ok ? "VALID:" : "INVALID:", claim.type);
}var bundle struct {
PublicJwk struct{ X string `json:"x"` } `json:"public_jwk"`
Claims []struct{ Type, Jws string } `json:"claims"`
}
json.NewDecoder(resp.Body).Decode(&bundle)
raw, _ := base64.RawURLEncoding.DecodeString(bundle.PublicJwk.X)
pub := ed25519.PublicKey(raw)
for _, c := range bundle.Claims {
i := strings.LastIndex(c.Jws, ".")
sig, _ := base64.RawURLEncoding.DecodeString(c.Jws[i+1:])
fmt.Println(ed25519.Verify(pub, []byte(c.Jws[:i]), sig), c.Type)
}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.txtimport requests
BASE = "https://username.md"
did_doc = requests.get(f"{BASE}/users/chris/.well-known/did.json").json()
atproto = requests.get(f"{BASE}/v1/users/chris/atproto").json()
agent_rules = requests.get(f"{BASE}/users/chris/llms.txt").text
print(did_doc["id"]) # did:web:...
print(atproto["atproto_handle"], atproto["bound"])const BASE = "https://username.md";
const didDoc = await (await fetch(`${BASE}/users/chris/.well-known/did.json`)).json();
const atproto = await (await fetch(`${BASE}/v1/users/chris/atproto`)).json();
const agentRules = await (await fetch(`${BASE}/users/chris/llms.txt`)).text();
console.log(didDoc.id); // did:web:...
console.log(atproto.atproto_handle, atproto.bound);base := "https://username.md"
didDoc, _ := http.Get(base + "/users/chris/.well-known/did.json")
atproto, _ := http.Get(base + "/v1/users/chris/atproto")
agentRules, _ := http.Get(base + "/users/chris/llms.txt")
// decode with encoding/json as in the profile exampleAuthorization: Bearer <token>
and keep it out of your shell history.# 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"}'import os, requests
BASE = "https://username.md"
auth = {"Authorization": f"Bearer {os.environ['USERNAME_MD_TOKEN']}"}
requests.patch(f"{BASE}/v1/users/you/profile", headers=auth,
json={"tagline": "builder", "links": [{"label": "GitHub", "href": "https://github.com/you"}]})
requests.put(f"{BASE}/v1/users/you/preferences", headers=auth,
json={"theme": "terminal", "accent": "#7ee787"})
challenge = requests.post(f"{BASE}/v1/users/you/verifications/dns-txt",
headers=auth, json={"domain": "yourdomain.com"}).json()
print("publish TXT:", challenge["record_name"], "=", challenge["record_value"])const BASE = "https://username.md";
const auth = { authorization: `Bearer ${process.env.USERNAME_MD_TOKEN}`, "content-type": "application/json" };
await fetch(`${BASE}/v1/users/you/profile`, { method: "PATCH", headers: auth,
body: JSON.stringify({ tagline: "builder", links: [{ label: "GitHub", href: "https://github.com/you" }] }) });
await fetch(`${BASE}/v1/users/you/preferences`, { method: "PUT", headers: auth,
body: JSON.stringify({ theme: "terminal", accent: "#7ee787" }) });
const challenge = await (await fetch(`${BASE}/v1/users/you/verifications/dns-txt`, {
method: "POST", headers: auth, body: JSON.stringify({ domain: "yourdomain.com" }) })).json();
console.log("publish TXT:", challenge.record_name, "=", challenge.record_value);body := strings.NewReader(`{"tagline":"builder"}`)
req, _ := http.NewRequest("PATCH", "https://username.md/v1/users/you/profile", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("USERNAME_MD_TOKEN"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer resp.Body.Close()