nostr-profile
Give your AI agent a face. Publish, read, and update Nostr kind 0 metadata — name, bio, avatar, NIP-05 verification, Lightning address. Built on NostrKey.
Quickstart
import asyncio, os
from nostrkey import Identity
from nostr_profile import Profile, publish_profile, get_profile
identity = Identity.from_nsec(os.environ["NOSTR_NSEC"])
relay = "wss://relay.nostrkeep.com"
async def main():
profile = Profile(
name="Johnny5",
about="An OpenClaw AI companion",
nip05="johnny5@example.com",
)
event_id = await publish_profile(identity, profile, relay)
print(f"Published: {event_id}")
p = await get_profile(identity.public_key_hex, relay)
if p:
print(f"{p.name}: {p.about}")
asyncio.run(main())
Profile Fields
| Field | Type | Required | Description |
|---|---|---|---|
name | str | Yes | Display name (max 100 chars) |
about | str | No | Bio/description (max 2000 chars) |
picture | str | No | Avatar URL (HTTPS) |
banner | str | No | Banner image URL (HTTPS) |
nip05 | str | No | NIP-05 verification (user@domain.tld) |
lud16 | str | No | Lightning address (user@domain.tld) |
website | str | No | Website URL (HTTPS) |
API
publish_profile(identity, profile, relay_url) → str
Publishes a complete kind 0 profile to the relay. Returns the event ID.
update_profile(identity, relay_url, **fields) → str
Updates specific fields without clobbering existing ones. Fetches the current profile, merges changes, and republishes.
# Only changes about — everything else stays
await update_profile(identity, relay, about="Updated bio for Q2")
get_profile(pubkey_hex, relay_url) → Profile | None
Fetches the latest kind 0 event for the given public key. Returns a Profile or None if not found.
Profile.diff(other) → dict
Returns a dictionary of fields that differ between two Profile objects.
Return Types
| Function | Returns | Description |
|---|---|---|
publish_profile(identity, profile, relay_url) | str | Event ID of published profile |
update_profile(identity, relay_url, **fields) | str | Event ID of updated profile |
get_profile(pubkey_hex, relay_url) | Profile | None | Profile if found |
Profile.diff(other) | dict | Changed fields between two profiles |
Security
- Never hardcode an nsec — load from environment variable or encrypted file
- URLs must be HTTP/HTTPS, capped at 2048 chars
- Name max 100 chars, about max 2000
- NIP-05 and lud16 validated as user@domain.tld
- from_metadata tolerant of malformed relay data (drops bad fields silently)
- Relay queries capped at 100 events
- No telemetry — no network calls except to the relay you configure
Configuration
| Variable | Required | Description |
|---|---|---|
NOSTR_NSEC | Yes | Agent's nsec private key (bech32 or hex) |
NOSTR_RELAY | No | Relay URL (default: wss://relay.nostrkeep.com) |
| Field | Max Length |
|---|---|
name | 100 |
about | 2000 |
picture | 2048 |
banner | 2048 |
website | 2048 |
nip05 | 500 |
lud16 | 500 |
Nostr NIPs Used
| NIP | Purpose |
|---|---|
| NIP-01 | Kind 0 metadata event structure |
| NIP-05 | DNS-based verification (user@domain.tld) |
Part of the NSE Ecosystem
nostr-profile uses NostrKey for identity. It sits alongside sense-memory, NostrCalendar, NostrSocial, and social-alignment in the NSE sovereign identity stack.
The keypair is who you are. The profile is how the world sees you.