Primal WebSocket API Documentation

Overview

Primal Server exposes a WebSocket-based API for Nostr clients, inspired by and extending the NIP-01 protocol. While it reuses some NIP-01 verbs like REQ and CLOSE, it significantly extends the protocol with custom filters and cache endpoints designed for efficient content discovery and social features.

Connection

Connect to the WebSocket endpoint at:

ws://127.0.0.1:8801

For production environments, use:

wss://cache.primal.net/v1

Message Format

All messages are JSON arrays sent over WebSocket connections.

---

Client-to-Server Messages

1. REQ - Request/Subscribe

Used to request cached data or events and subscribe to updates.

Basic Format:

["REQ", <subscription_id>, <filters>]

Comparison to NIP-01:
- Similar: Uses the same REQ verb and subscription_id concept
- Different: Primal cache servers do NOT accept standard NIP-01 filters (ids, authors, kinds, etc.)
- Different: Instead, Primal uses a special cache field for accessing pre-computed data through named endpoints

#### Primal Cache Filter Format

IMPORTANT: Primal's cache server does NOT accept regular NIP-01 filters. All requests must use the cache field format:

{
  "cache": ["<endpoint_name>", {<parameters>}]
}

Note: Standard NIP-01 filters like ids, authors, kinds, #e, #p, since, until, and limit are NOT supported by Primal's cache server. Each cache endpoint has its own specific parameters (documented below).

If you need to query events using standard NIP-01 filters, you should connect to regular Nostr relays (like wss://relay.primal.net) instead of the cache server

{
  "cache": ["<endpoint_name>", {<parameters>}]
}

Cache Endpoints

All cache endpoints are accessed via the cache filter field. Below is a comprehensive list:

#### Network & Statistics

net_stats - Network statistics (async)

["REQ", "sub_id", {"cache": ["net_stats"]}]

Returns global network statistics including user counts, post counts, zap totals, etc.

nostr_stats - Nostr network stats

["REQ", "sub_id", {"cache": ["nostr_stats"]}]

server_name - Get server name

["REQ", "sub_id", {"cache": ["server_name"]}]

#### Feed Endpoints

feed - User feed

["REQ", "sub_id", {
"cache": ["feed", {
"pubkey": "<pubkey>",
"notes": "follows|authored|bookmarks|user_media_thumbnails",
"include_replies": true|false,
"limit": 20,
"since": <timestamp>,
"until": <timestamp>,
"offset": 0,
"user_pubkey": "<optional_viewer_pubkey>"
}]
}]

Parameters:
- pubkey: Target user's public key (hex)
- notes: Feed type
- "follows": Posts from users this pubkey follows
- "authored": Posts created by this pubkey
- "replies": Only replies by this pubkey
- "bookmarks": Bookmarked posts
- "user_media_thumbnails": Posts with media
- include_replies: Include reply posts (default: false)
- limit: Max results (≤1000)
- since/until: Unix timestamp range
- offset: Pagination offset
- user_pubkey: Viewer's pubkey for personalization

Returns:
- Kind 1: Text notes
- Kind 6: Reposts
- Kind 9735: Zap receipts
- Kind 0: User metadata for all mentioned users
- Kind 10000100: EVENT_STATS (engagement metrics per event)
- Kind 10000107: REFERENCED_EVENT (full quoted/reposted events)
- Kind 10000108: USER_SCORES (user scoring data)
- Kind 10000113: RANGE (pagination metadata with event IDs)
- Kind 10000128: LINK_METADATA (URL preview data)
- Kind 10000129: ZAP_EVENT (detailed zap information)
- Kind 10000133: USER_FOLLOWER_COUNTS
- Kind 10000141: EVENT_RELAYS (where events were seen)
- Kind 10000158: USER_PRIMAL_NAMES
- Kind 10000168: MEMBERSHIP_LEGEND_CUSTOMIZATION
- Kind 10000169: MEMBERSHIP_COHORTS
- Kind 10063: User relay lists

feed_2 - Extended feed endpoint

["REQ", "sub_id", {
"cache": ["feed_2", {
"pubkey": "<pubkey>",
"notes": "follows|authored",
"kinds": [1, 6],
"since": <timestamp>,
"until": <timestamp>,
"limit": 20,
"offset": 0,
"order": "asc|desc",
"user_pubkey": "<viewer_pubkey>"
}]
}]

Additional parameters:
- kinds: Filter by event kinds
- order: Sort order (asc/desc)

thread_view - Conversation thread

["REQ", "sub_id", {
"cache": ["thread_view", {
"event_id": "<event_id>",
"limit": 20,
"since": <timestamp>,
"until": <timestamp>,
"offset": 0,
"user_pubkey": "<viewer_pubkey>"
}]
}]

Returns the specified event along with its replies and parent posts.

Returns:
- Kind 1: The target event, parent posts, and reply posts
- Kind 0: User metadata for all participants in the thread
- Kind 10000100: EVENT_STATS for each event
- Kind 10000107: REFERENCED_EVENT (quoted posts)
- Kind 10000108: USER_SCORES
- Kind 10000119: MEDIA_METADATA
- Kind 10000129: ZAP_EVENT (zaps on thread events)
- Kind 10000133: USER_FOLLOWER_COUNTS
- Kind 10000158: USER_PRIMAL_NAMES

#### User Profile & Social Graph

user_profile - User profile information

["REQ", "sub_id", {
"cache": ["user_profile", {
"pubkey": "<pubkey>",
"user_pubkey": "<viewer_pubkey>"
}]
}]

Returns:
- Kind 0: User metadata event
- Kind 10000105: USER_PROFILE with follower/note counts
- Kind 10000119: MEDIA_METADATA for profile images
- Kind 10000169: MEMBERSHIP_COHORTS (premium/legend status)
- Kind 10063: User relay list (NIP-65)

user_infos - Batch user information

["REQ", "sub_id", {
"cache": ["user_infos", {
"pubkeys": ["<pubkey1>", "<pubkey2>", ...]
}]
}]

Returns multiple event types for each user:
- Kind 0: User metadata
- Kind 10000108: USER_SCORES (follower-based scores)
- Kind 10000133: USER_FOLLOWER_COUNTS (actual follower counts)
- Kind 10000119: MEDIA_METADATA (profile image variants)
- Kind 10000158: USER_PRIMAL_NAMES (verified Primal usernames)
- Kind 10000169: MEMBERSHIP_COHORTS (premium status)
- Kind 10063: User relay list

contact_list - User's contact list

["REQ", "sub_id", {
"cache": ["contact_list", {
"pubkey": "<pubkey>",
"extended_response": true|false
}]
}]

is_user_following - Check if user follows another

["REQ", "sub_id", {
"cache": ["is_user_following", {
"pubkey": "<follower_pubkey>",
"user_pubkey": "<target_pubkey>"
}]
}]

user_followers - Get user followers

["REQ", "sub_id", {
"cache": ["user_followers", {
"pubkey": "<pubkey>",
"limit": 200
}]
}]

mutual_follows - Mutual followers between users

["REQ", "sub_id", {
"cache": ["mutual_follows", {
"pubkey": "<pubkey>",
"user_pubkey": "<viewer_pubkey>"
}]
}]

user_profile_followed_by - Who follows this user

["REQ", "sub_id", {
"cache": ["user_profile_followed_by", {
"pubkey": "<pubkey>"
}]
}]

#### Events & Actions

events - Get specific events

["REQ", "sub_id", {
"cache": ["events", {
"event_ids": ["<event_id1>", "<event_id2>", ...],
"extended_response": true|false,
"user_pubkey": "<viewer_pubkey>"
}]
}]

event_actions - Event interactions (likes, reposts, etc.)

["REQ", "sub_id", {
"cache": ["event_actions", {
"event_id": "<event_id>",
"kind": <action_kind>,
"limit": 100,
"offset": 0
}]
}]

Or for addressable events:

{
"cache": ["event_actions", {
"pubkey": "<pubkey>",
"identifier": "<d_tag_value>",
"kind": <action_kind>,
"limit": 100,
"offset": 0
}]
}

Action kind values:
- 7 - Likes/reactions
- 1 - Replies (text notes that reference the event)
- 6 - Reposts/quotes
- 9735 - Zap receipts

Returns:
- Events of the specified kind that reference the target event
- Kind 0: User metadata for all users who performed the action
- Additional metadata as needed

#### Zaps

zaps_feed - Zap feed

["REQ", "sub_id", {
"cache": ["zaps_feed", {
"pubkey": "<pubkey>",
"limit": 20,
"offset": 0
}]
}]

user_zaps - Zaps received by user

["REQ", "sub_id", {
"cache": ["user_zaps", {
"pubkey": "<pubkey>",
"limit": 20,
"offset": 0
}]
}]

user_zaps_by_satszapped - Zaps sorted by amount

["REQ", "sub_id", {
"cache": ["user_zaps_by_satszapped", {
"pubkey": "<pubkey>",
"limit": 20,
"offset": 0
}]
}]

user_zaps_sent - Zaps sent by user

["REQ", "sub_id", {
"cache": ["user_zaps_sent", {
"pubkey": "<pubkey>",
"limit": 20,
"offset": 0
}]
}]

event_zaps_by_satszapped - Zaps on specific event

["REQ", "sub_id", {
"cache": ["event_zaps_by_satszapped", {
"event_id": "<event_id>",
"limit": 20,
"offset": 0,
"user_pubkey": "<viewer_pubkey>"
}]
}]

Or for addressable events:

{
"cache": ["event_zaps_by_satszapped", {
"pubkey": "<pubkey>",
"identifier": "<d_tag_value>",
"limit": 20,
"offset": 0,
"user_pubkey": "<viewer_pubkey>"
}]
}

invoices_to_zap_receipts - Convert invoices to zap receipts

["REQ", "sub_id", {
"cache": ["invoices_to_zap_receipts", {
"invoices": ["<invoice1>", "<invoice2>", ...]
}]
}]

#### Direct Messages

get_directmsg_contacts - Get DM contacts

["REQ", "sub_id", {
"cache": ["get_directmsg_contacts", {
"receiver_pubkey": "<pubkey>",
"user_pubkey": "<viewer_pubkey>"
}]
}]

get_directmsgs - Get direct messages

["REQ", "sub_id", {
"cache": ["get_directmsgs", {
"receiver": "<receiver_pubkey>",
"sender": "<sender_pubkey>",
"user_pubkey": "<viewer_pubkey>",
"limit": 20,
"since": <timestamp>,
"until": <timestamp>
}]
}]

directmsg_count - DM count (async)

["REQ", "sub_id", {
"cache": ["directmsg_count", {
"receiver_pubkey": "<pubkey>"
}]
}]

directmsg_count_2 - Extended DM count (async)

["REQ", "sub_id", {
"cache": ["directmsg_count_2", {
"receiver_pubkey": "<pubkey>"
}]
}]

reset_directmsg_count - Reset DM counter

["REQ", "sub_id", {
"cache": ["reset_directmsg_count", {
"receiver_pubkey": "<pubkey>",
"sender_pubkey": "<sender_pubkey>"
}]
}]

reset_directmsg_counts - Reset all DM counters

["REQ", "sub_id", {
"cache": ["reset_directmsg_counts", {
"receiver_pubkey": "<pubkey>"
}]
}]

#### Content Moderation

mutelist - Get mute list

["REQ", "sub_id", {
"cache": ["mutelist", {
"pubkey": "<pubkey>"
}]
}]

mutelists - Get multiple mute lists

["REQ", "sub_id", {
"cache": ["mutelists", {
"pubkeys": ["<pubkey1>", "<pubkey2>", ...]
}]
}]

allowlist - Get allow list

["REQ", "sub_id", {
"cache": ["allowlist", {
"pubkey": "<pubkey>"
}]
}]

is_hidden_by_content_moderation - Check if content is hidden

["REQ", "sub_id", {
"cache": ["is_hidden_by_content_moderation", {
"pubkey": "<viewer_pubkey>",
"event_id": "<event_id>"
}]
}]

#### Explore & Discovery

explore_zaps - Trending zaps

["REQ", "sub_id", {
"cache": ["explore_zaps", {
"limit": 20,
"user_pubkey": "<viewer_pubkey>"
}]
}]

explore_people - Discover users

["REQ", "sub_id", {
"cache": ["explore_people", {
"limit": 20,
"user_pubkey": "<viewer_pubkey>"
}]
}]

explore_media - Trending media

["REQ", "sub_id", {
"cache": ["explore_media", {
"limit": 20,
"user_pubkey": "<viewer_pubkey>"
}]
}]

explore_topics - Trending topics

["REQ", "sub_id", {
"cache": ["explore_topics", {
"limit": 20,
"user_pubkey": "<viewer_pubkey>"
}]
}]

scored - Get scored/trending content

["REQ", "sub_id", {
"cache": ["scored", {
"selector": "trending_24h|trending_12h|trending_4h|mostzapped_24h|...",
"user_pubkey": "<viewer_pubkey>"
}]
}]

Supported selectors:
- trending_24h, trending_12h, trending_4h, trending_1h - Trending by engagement
- mostzapped_24h, mostzapped_12h, mostzapped_4h, mostzapped_1h - Most zapped
- gm_trending_24h, gm_trending_12h, gm_trending_4h, gm_trending_1h - GM posts
- classic_trending_24h, classic_trending_12h, classic_trending_4h, classic_trending_1h - Classic algorithm

Returns:
- Kind 1: Posts matching the scoring criteria
- Kind 0: User metadata
- Kind 10000100: EVENT_STATS (detailed engagement metrics)
- Kind 10000107: REFERENCED_EVENT
- Kind 10000119: MEDIA_METADATA
- Kind 10000128: LINK_METADATA
- Additional metadata kinds

scored_users - Trending users

["REQ", "sub_id", {
"cache": ["scored_users", {
"limit": 20,
"since": <timestamp>,
"user_pubkey": "<viewer_pubkey>"
}]
}]

scored_users_24h - Trending users (24h)

["REQ", "sub_id", {
"cache": ["scored_users_24h", {
"user_pubkey": "<viewer_pubkey>"
}]
}]

explore_global_trending_24h - Global trending (24h)

["REQ", "sub_id", {
"cache": ["explore_global_trending_24h", {
"user_pubkey": "<viewer_pubkey>"
}]
}]

Returns:
- Kind 1: Trending text notes
- Kind 0: User metadata for post authors
- Kind 9735: Zap receipts on trending posts
- Kind 10000100: EVENT_STATS with engagement metrics
- Kind 10000107: REFERENCED_EVENT (quoted posts)
- Kind 10000119: MEDIA_METADATA (image/video thumbnails)
- Kind 10000128: LINK_METADATA (URL previews)
- Kind 10000129: ZAP_EVENT (zap details)
- Additional metadata kinds

explore_global_mostzapped_4h - Most zapped globally (4h)

["REQ", "sub_id", {
"cache": ["explore_global_mostzapped_4h", {
"user_pubkey": "<viewer_pubkey>"
}]
}]

explore - General explore endpoint

["REQ", "sub_id", {
"cache": ["explore", {
"timeframe": "latest|popular|trending|mostzapped",
"scope": "global|network|tribe|follows",
"user_pubkey": "<viewer_pubkey>",
"limit": 20,
"since": <timestamp>,
"until": <timestamp>
}]
}]

explore_legend_counts - Network statistics for user

["REQ", "sub_id", {
"cache": ["explore_legend_counts", {
"pubkey": "<pubkey>"
}]
}]

#### Search

search - Search events

["REQ", "sub_id", {
"cache": ["search", {
"query": "<search_query>",
"kind": <kind_filter>,
"limit": 20,
"since": <timestamp>,
"until": <timestamp>,
"offset": 0,
"user_pubkey": "<viewer_pubkey>"
}]
}]

Returns:
- Matching events (typically kind 1 text notes)
- Kind 0: User metadata for post authors
- Kind 10000100: EVENT_STATS
- Additional metadata kinds as needed

advanced_search - Advanced search with filters

["REQ", "sub_id", {
"cache": ["advanced_search", {
"query": "<search_query>",
"user_pubkey": "<viewer_pubkey>",
"limit": 20
}]
}]

user_search - Search users

["REQ", "sub_id", {
"cache": ["user_search", {
"query": "<search_query>",
"limit": 20,
"pubkey": "<optional_searcher_pubkey>"
}]
}]

Returns:
- Kind 0 (user metadata for matching users)
- Kind 10000108 (USER_SCORES)
- Kind 10000133 (USER_FOLLOWER_COUNTS)
- Kind 10000158 (USER_PRIMAL_NAMES)

#### Long-form Content (Articles)

long_form_content_feed - Article feed

["REQ", "sub_id", {
"cache": ["long_form_content_feed", {
"pubkey": "<pubkey>",
"limit": 20,
"since": <timestamp>,
"until": <timestamp>,
"offset": 0,
"user_pubkey": "<viewer_pubkey>"
}]
}]

long_form_content_thread_view - Article with comments

["REQ", "sub_id", {
"cache": ["long_form_content_thread_view", {
"event_id": "<article_event_id>",
"limit": 20,
"user_pubkey": "<viewer_pubkey>"
}]
}]

get_recommended_reads - Recommended articles

["REQ", "sub_id", {
"cache": ["get_recommended_reads", {
"pubkey": "<pubkey>",
"limit": 20
}]
}]

get_reads_topics - Article topics

["REQ", "sub_id", {
"cache": ["get_reads_topics"]}]
}]

get_featured_authors - Featured article authors

["REQ", "sub_id", {
"cache": ["get_featured_authors"]}]
}]

articles_stats - Article statistics

["REQ", "sub_id", {
"cache": ["articles_stats", {
"pubkey": "<pubkey>"
}]
}]

drafts - Article drafts

["REQ", "sub_id", {
"cache": ["drafts", {
"pubkey": "<pubkey>"
}]
}]

top_article - Top article

["REQ", "sub_id", {
"cache": ["top_article", {
"pubkey": "<pubkey>"
}]
}]

#### Bookmarks & Highlights

get_bookmarks - Get bookmarks

["REQ", "sub_id", {
"cache": ["get_bookmarks", {
"pubkey": "<pubkey>"
}]
}]

get_highlights - Get highlights

["REQ", "sub_id", {
"cache": ["get_highlights", {
"pubkey": "<pubkey>"
}]
}]

#### Relays

get_user_relays - User relay list

["REQ", "sub_id", {
"cache": ["get_user_relays", {
"pubkey": "<pubkey>"
}]
}]

get_user_relays_2 - Extended relay list

["REQ", "sub_id", {
"cache": ["get_user_relays_2", {
"pubkey": "<pubkey>"
}]
}]

relays - Popular relays

["REQ", "sub_id", {
"cache": ["relays", {
"limit": 20
}]
}]

get_default_relays - Default relay recommendations

["REQ", "sub_id", {
"cache": ["get_default_relays"]}]
}]

#### Recommendations

get_recommended_users - Recommended users to follow

["REQ", "sub_id", {
"cache": ["get_recommended_users"]}]
}]

get_suggested_users - Suggested user accounts

["REQ", "sub_id", {
"cache": ["get_suggested_users"]}]
}]

#### Feeds & Directives

feed_directive - Custom feed directive

["REQ", "sub_id", {
"cache": ["feed_directive", {
"directive": "<directive_config>"
}]
}]

feed_directive_2 - Extended feed directive

["REQ", "sub_id", {
"cache": ["feed_directive_2", {
"directive": "<directive_config>"
}]
}]

mega_feed_directive - Mega feed endpoint

["REQ", "sub_id", {
"cache": ["mega_feed_directive", {
"spec": "<feed_specification>"
}]
}]

advanced_feed - Advanced feed

["REQ", "sub_id", {
"cache": ["advanced_feed", {
"specification": "<feed_spec>"
}]
}]

get_advanced_feeds - Available advanced feeds

["REQ", "sub_id", {
"cache": ["get_advanced_feeds"]}]
}]

get_home_feeds - Home feed configurations

["REQ", "sub_id", {
"cache": ["get_home_feeds"]}]
}]

get_reads_feeds - Article feed configurations

["REQ", "sub_id", {
"cache": ["get_reads_feeds"]}]
}]

enrich_feed_events - Enrich feed with metadata

["REQ", "sub_id", {
"cache": ["enrich_feed_events", {
"events": [<event_array>]
}]
}]

#### DVM (Data Vending Machines)

get_dvm_feeds - DVM feed list

["REQ", "sub_id", {
"cache": ["get_dvm_feeds"]}]
}]

dvm_feed_info - DVM feed details

["REQ", "sub_id", {
"cache": ["dvm_feed_info", {
"feed_id": "<feed_id>"
}]
}]

get_featured_dvm_feeds - Featured DVM feeds

["REQ", "sub_id", {
"cache": ["get_featured_dvm_feeds"]}]
}]

#### Settings & Configuration

set_app_settings - Set user app settings

["REQ", "sub_id", {
"cache": ["set_app_settings", {
"settings_event": <signed_event>
}]
}]

get_app_settings - Get app settings

["REQ", "sub_id", {
"cache": ["get_app_settings", {
"event_from_user": <signed_event>
}]
}]

get_app_settings_2 - Extended app settings

["REQ", "sub_id", {
"cache": ["get_app_settings_2", {
"event_from_user": <signed_event>
}]
}]

get_default_app_settings - Default app settings

["REQ", "sub_id", {
"cache": ["get_default_app_settings", {
"client": "Primal-Web App"
}]
}]

set_app_subsettings - Set app subsettings

["REQ", "sub_id", {
"cache": ["set_app_subsettings", {
"event_from_user": <signed_event>
}]
}]

get_app_subsettings - Get app subsettings

["REQ", "sub_id", {
"cache": ["get_app_subsettings", {
"event_from_user": <signed_event>
}]
}]

get_default_app_subsettings - Default subsettings

["REQ", "sub_id", {
"cache": ["get_default_app_subsettings", {
"subkey": "user-home-feeds|user-reads-feeds"
}]
}]

client_config - Client configuration

["REQ", "sub_id", {
"cache": ["client_config"]}]
}]

get_app_releases - App release information

["REQ", "sub_id", {
"cache": ["get_app_releases"]}]
}]

#### Notifications

notifications - Get notifications (async)

["REQ", "sub_id", {
"cache": ["notifications", {
"pubkey": "<pubkey>"
}]
}]

notification_counts - Notification counts (async)

["REQ", "sub_id", {
"cache": ["notification_counts", {
"pubkey": "<pubkey>"
}]
}]

notification_counts_2 - Extended notification counts (async)

["REQ", "sub_id", {
"cache": ["notification_counts_2", {
"pubkey": "<pubkey>"
}]
}]

get_notifications - Get notifications with filters

["REQ", "sub_id", {
"cache": ["get_notifications", {
"pubkey": "<pubkey>",
"limit": 1000,
"since": <timestamp>,
"until": <timestamp>,
"offset": 0,
"user_pubkey": "<viewer_pubkey>",
"type": "<notification_type>",
"type_group": "<type_group>"
}]
}]

set_notifications_seen - Mark notifications as seen

["REQ", "sub_id", {
"cache": ["set_notifications_seen", {
"event_from_user": <signed_event>
}]
}]

get_notifications_seen - Get seen notification timestamp

["REQ", "sub_id", {
"cache": ["get_notifications_seen", {
"event_from_user": <signed_event>
}]
}]

#### Push Notifications

update_push_notification_token - Update push token

["REQ", "sub_id", {
"cache": ["update_push_notification_token", {
"event_from_user": <signed_event>
}]
}]

update_push_notification_token_for_nip46 - Update push token for NIP-46

["REQ", "sub_id", {
"cache": ["update_push_notification_token_for_nip46", {
"event_from_user": <signed_event>
}]
}]

events_nip46 - NIP-46 events

["REQ", "sub_id", {
"cache": ["events_nip46", {
"pubkey": "<pubkey>"
}]
}]

#### Hashtags & Trending

trending_hashtags_4h - Trending hashtags (4h)

["REQ", "sub_id", {
"cache": ["trending_hashtags_4h"]}]
}]

trending_hashtags_7d - Trending hashtags (7d)

["REQ", "sub_id", {
"cache": ["trending_hashtags_7d"]}]
}]

trending_images - Trending images

["REQ", "sub_id", {
"cache": ["trending_images", {
"limit": 20
}]
}]

trending_images_4h - Trending images (4h)

["REQ", "sub_id", {
"cache": ["trending_images_4h"]}]
}]

#### Lists & Collections

parameterized_replaceable_list - Get parameterized replaceable list

["REQ", "sub_id", {
"cache": ["parameterized_replaceable_list", {
"pubkey": "<pubkey>",
"identifier": "<d_tag>",
"kind": <kind_number>
}]
}]

parametrized_replaceable_event - Get single parameterized event

["REQ", "sub_id", {
"cache": ["parametrized_replaceable_event", {
"pubkey": "<pubkey>",
"identifier": "<d_tag>",
"kind": <kind_number>
}]
}]

parametrized_replaceable_events - Get multiple parameterized events

["REQ", "sub_id", {
"cache": ["parametrized_replaceable_events", {
"pubkey": "<pubkey>",
"identifiers": ["<d_tag1>", "<d_tag2>", ...],
"kind": <kind_number>
}]
}]

replaceable_event - Get replaceable event

["REQ", "sub_id", {
"cache": ["replaceable_event", {
"pubkey": "<pubkey>",
"kind": <kind_number>
}]
}]

follow_lists - Get follow lists

["REQ", "sub_id", {
"cache": ["follow_lists", {
"pubkey": "<pubkey>"
}]
}]

follow_list - Get specific follow list

["REQ", "sub_id", {
"cache": ["follow_list", {
"pubkey": "<pubkey>",
"identifier": "<d_tag>"
}]
}]

#### Content Filtering

search_filterlist - Search filter list

["REQ", "sub_id", {
"cache": ["search_filterlist", {
"query": "<search_query>"
}]
}]

get_filterlist - Get filter list

["REQ", "sub_id", {
"cache": ["get_filterlist", {
"pubkey": "<pubkey>"
}]
}]

check_filterlist - Check if filtered

["REQ", "sub_id", {
"cache": ["check_filterlist", {
"target": "<pubkey_or_event_id>",
"target_type": "pubkey|event"
}]
}]

#### Reporting

report_user - Report user

["REQ", "sub_id", {
"cache": ["report_user", {
"event_from_user": <signed_event>
}]
}]

report_note - Report note

["REQ", "sub_id", {
"cache": ["report_note", {
"event_from_user": <signed_event>
}]
}]

#### Event Broadcasting

import_events - Import events

["REQ", "sub_id", {
"cache": ["import_events", {
"events": [<event_array>]
}]
}]

broadcast_reply - Broadcast reply

["REQ", "sub_id", {
"cache": ["broadcast_reply", {
"event_from_user": <signed_event>
}]
}]

broadcast_events - Broadcast multiple events

["REQ", "sub_id", {
"cache": ["broadcast_events", {
"events": [<event_array>]
}]
}]

#### Membership Features

membership_media_management_stats - Media storage stats

["REQ", "sub_id", {
"cache": ["membership_media_management_stats", {
"event_from_user": <signed_event>
}]
}]

membership_media_management_uploads - List uploads

["REQ", "sub_id", {
"cache": ["membership_media_management_uploads", {
"event_from_user": <signed_event>
}]
}]

membership_media_management_delete - Delete uploads

["REQ", "sub_id", {
"cache": ["membership_media_management_delete", {
"event_from_user": <signed_event>
}]
}]

membership_recovery_contact_lists - Recover contact lists

["REQ", "sub_id", {
"cache": ["membership_recovery_contact_lists", {
"event_from_user": <signed_event>
}]
}]

membership_recover_contact_list - Recover specific contact list

["REQ", "sub_id", {
"cache": ["membership_recover_contact_list", {
"event_from_user": <signed_event>
}]
}]

membership_content_stats - Content backup stats

["REQ", "sub_id", {
"cache": ["membership_content_stats", {
"event_from_user": <signed_event>
}]
}]

membership_content_backup - Backup content

["REQ", "sub_id", {
"cache": ["membership_content_backup", {
"event_from_user": <signed_event>
}]
}]

membership_content_rebroadcast_start - Start rebroadcast

["REQ", "sub_id", {
"cache": ["membership_content_rebroadcast_start", {
"event_from_user": <signed_event>
}]
}]

membership_content_rebroadcast_cancel - Cancel rebroadcast

["REQ", "sub_id", {
"cache": ["membership_content_rebroadcast_cancel", {
"event_from_user": <signed_event>
}]
}]

membership_content_rebroadcast_status - Rebroadcast status

["REQ", "sub_id", {
"cache": ["membership_content_rebroadcast_status", {
"event_from_user": <signed_event>
}]
}]

rebroadcasting_status - Get rebroadcast status (async)

["REQ", "sub_id", {
"cache": ["rebroadcasting_status", {
"pubkey": "<pubkey>"
}]
}]

membership_legends_leaderboard - Legends leaderboard

["REQ", "sub_id", {
"cache": ["membership_legends_leaderboard"]}]
}]

membership_premium_leaderboard - Premium leaderboard

["REQ", "sub_id", {
"cache": ["membership_premium_leaderboard"]}]
}]

creator_paid_tiers - Creator subscription tiers

["REQ", "sub_id", {
"cache": ["creator_paid_tiers", {
"pubkey": "<creator_pubkey>"
}]
}]

#### Media Management

upload - Upload media

["REQ", "sub_id", {
"cache": ["upload", {
"event_from_user": <signed_event_with_base64_data>
}]
}]

upload_chunk - Upload media chunk

["REQ", "sub_id", {
"cache": ["upload_chunk", {
"event_from_user": <signed_event>
}]
}]

upload_complete - Complete chunked upload

["REQ", "sub_id", {
"cache": ["upload_complete", {
"event_from_user": <signed_event>
}]
}]

upload_cancel - Cancel upload

["REQ", "sub_id", {
"cache": ["upload_cancel", {
"event_from_user": <signed_event>
}]
}]

get_media_metadata - Get media metadata

["REQ", "sub_id", {
"cache": ["get_media_metadata", {
"url": "<media_url>"
}]
}]

get_recommended_blossom_servers - Blossom server recommendations

["REQ", "sub_id", {
"cache": ["get_recommended_blossom_servers"]}]
}]

#### Utilities

user_of_ln_address - Get pubkey from Lightning address

["REQ", "sub_id", {
"cache": ["user_of_ln_address", {
"ln_address": "<[email protected]>"
}]
}]

nip19_decode - Decode NIP-19 identifiers

["REQ", "sub_id", {
"cache": ["nip19_decode", {
"identifier": "<npub_or_note...>"
}]
}]

set_last_time_user_was_online - Update online status

["REQ", "sub_id", {
"cache": ["set_last_time_user_was_online", {
"pubkey": "<pubkey>"
}]
}]

trusted_users - Get trusted users

["REQ", "sub_id", {
"cache": ["trusted_users", {
"pubkey": "<pubkey>"
}]
}]

note_mentions - Get note mentions

["REQ", "sub_id", {
"cache": ["note_mentions", {
"event_id": "<event_id>"
}]
}]

note_mentions_count - Count note mentions

["REQ", "sub_id", {
"cache": ["note_mentions_count", {
"event_id": "<event_id>"
}]
}]

find_reposts - Find reposts of event

["REQ", "sub_id", {
"cache": ["find_reposts", {
"event_id": "<event_id>"
}]
}]

user_profile_scored_content - User's top content

["REQ", "sub_id", {
"cache": ["user_profile_scored_content", {
"pubkey": "<pubkey>",
"limit": 5,
"user_pubkey": "<viewer_pubkey>"
}]
}]

user_profile_scored_media_thumbnails - User's top media

["REQ", "sub_id", {
"cache": ["user_profile_scored_media_thumbnails", {
"pubkey": "<pubkey>",
"limit": 5,
"user_pubkey": "<viewer_pubkey>"
}]
}]

2. CLOSE - Close Subscription

Exactly as in NIP-01:

["CLOSE", <subscription_id>]

Closes an active subscription and stops receiving updates for that subscription ID.

---

Server-to-Client Messages

Primal extends NIP-01 response messages with many custom event kinds for structured data.

Standard NIP-01 Responses

EVENT - Event data

["EVENT", <subscription_id>, <event_json>]

EOSE - End of stored events

["EOSE", <subscription_id>]

OK - Event acceptance status

["OK", <event_id>, <true|false>, <message>]

CLOSED - Subscription closed

["CLOSED", <subscription_id>, <message>]

NOTICE - Human-readable notice

["NOTICE", <message>]

Primal Custom Event Kinds

Primal returns special event kinds (10000+) for structured responses:

#### Statistics & Metadata

| Kind | Name | Description |
|------|------|-------------|
| 10000100 | EVENT_STATS | Event statistics (likes, replies, reposts, zaps, score) |
| 10000101 | NET_STATS | Network statistics |
| 10000102 | EXPLORE_LEGEND_COUNTS | User network statistics |
| 10000105 | USER_PROFILE | User profile information |
| 10000108 | USER_SCORES | User scoring data |
| 10000113 | RANGE | Paginated result range metadata |
| 10000115 | EVENT_ACTIONS_COUNT | User's actions on event (liked, reposted, etc.) |
| 10000133 | USER_FOLLOWER_COUNTS | Follower counts for users |
| 10000136 | NOSTR_STATS | Nostr network statistics |
| 10000172 | USER_TRUSTRANKS | User trust ranks |
| 10000174 | ARTICLES_STATS | Article statistics |

#### Direct Messages

| Kind | Name | Description |
|------|------|-------------|
| 10000117 | DIRECTMSG_COUNT | Direct message count (deprecated) |
| 10000118 | DIRECTMSG_COUNTS | Direct message counts by sender |
| 10000134 | DIRECTMSG_COUNT_2 | Extended DM count |

#### Content & Events

| Kind | Name | Description |
|------|------|-------------|
| 10000107 | REFERENCED_EVENT | Referenced/quoted event |
| 10000122 | EVENT_IDS | List of event IDs |
| 10000123 | PARTIAL_RESPONSE | Partial response data |
| 10000127 | EVENT_IMPORT_STATUS | Event import status |
| 10000129 | ZAP_EVENT | Zap event data |
| 10000144 | LONG_FORM_METADATA | Article metadata (word count, etc.) |

#### User Info & Social

| Kind | Name | Description |
|------|------|-------------|
| 10000125 | IS_USER_FOLLOWING | Following status result |
| 10000138 | USER_PUBKEY | User public key mapping |
| 10000157 | USER_FOLLOWER_COUNT_INCREASES | Follower count changes |
| 10000158 | USER_PRIMAL_NAMES | Primal verified names |
| 10000200 | RECOMMENDED_USERS | Recommended users |

#### Relays

| Kind | Name | Description |
|------|------|-------------|
| 10000109 | RELAYS | Relay list with usage stats |
| 10000124 | DEFAULT_RELAYS | Default relay recommendations |
| 10000139 | USER_RELAYS | User's relay list |
| 10000141 | EVENT_RELAYS | Relays where events were seen |

#### Notifications

| Kind | Name | Description |
|------|------|-------------|
| 10000110 | NOTIFICATION | Notification item |
| 10000111 | NOTIFICATIONS_SEEN_UNTIL | Last seen notification timestamp |
| 10000112 | NOTIFICATIONS_SUMMARY | Notification summary (deprecated) |
| 10000132 | NOTIFICATIONS_SUMMARY_2 | Extended notification summary |

#### Content Moderation

| Kind | Name | Description |
|------|------|-------------|
| 10000126 | FILTERLIST | Content filter list |
| 10000130 | FILTERLISTED | Filter status result |
| 10000131 | FILTERING_REASON | Why content was filtered |
| 10000137 | IS_HIDDEN_BY_CONTENT_MODERATION | Content moderation status |

#### Media

| Kind | Name | Description |
|------|------|-------------|
| 10000114 | MEDIA_MAPPING | Media URL mappings |
| 10000119 | MEDIA_METADATA | Media metadata (dimensions, type, etc.) |
| 10000120 | UPLOAD | Upload event |
| 10000121 | UPLOADED | Upload complete with URL |
| 10000135 | UPLOAD_CHUNK | Chunked upload event |
| 10000142 | UPLOADED_2 | Extended upload response with SHA256 |
| 10000175 | RECOMMENDED_BLOSSOM_SERVERS | Blossom server list |
| 10000176 | MEDIA_METADATA_BY_HASH | Media metadata by hash |

#### Hashtags & Links

| Kind | Name | Description |
|------|------|-------------|
| 10000116 | HASHTAGS | Hashtag list (deprecated) |
| 10000160 | HASHTAGS_2 | Extended hashtag data |
| 10000128 | LINK_METADATA | URL metadata/preview |

#### Settings & Config

| Kind | Name | Description |
|------|------|-------------|
| 10000103 | PRIMAL_SETTINGS | Primal-specific settings |
| 10000138 | APP_RELEASES | App release information |
| 10000150 | ADVANCED_FEEDS | Available advanced feeds |
| 10000155 | APP_SUBSETTINGS | App subsettings |
| 30078 | APP_SETTINGS | User app settings (NIP-78) |

#### Feeds & Discovery

| Kind | Name | Description |
|------|------|-------------|
| 10000145 | RECOMMENDED_READS | Recommended articles |
| 10000146 | READS_TOPICS | Article topics |
| 10000148 | FEATURED_AUTHORS | Featured article authors |
| 10000151 | HIGHLIGHT_GROUPS | Highlight groupings |
| 10000152 | READS_FEEDS | Article feed configurations |
| 10000153 | HOME_FEEDS | Home feed configurations |

#### DVM & Metadata

| Kind | Name | Description |
|------|------|-------------|
| 10000156 | DVM_FEED_FOLLOWS_ACTIONS | DVM feed follow actions |
| 10000159 | DVM_FEED_METADATA | DVM feed metadata |
| 10000147 | CREATOR_PAID_TIERS | Creator subscription tiers |

#### Membership

| Kind | Name | Description |
|------|------|-------------|
| 10000162 | SHOW_PRIMAL_SUPPORT | Show Primal support status |
| 10000163 | MEMBERSHIP_MEDIA_MANAGEMENT_STATS | Media storage statistics |
| 10000164 | MEMBERSHIP_MEDIA_MANAGEMENT_UPLOADS | Upload list |
| 10000165 | MEMBERSHIP_RECOVERY_CONTACT_LISTS | Contact list backups |
| 10000166 | MEMBERSHIP_CONTENT_STATS | Content backup stats |
| 10000167 | MEMBERSHIP_CONTENT_REBROADCAST_STATUS | Rebroadcast status |
| 10000168 | MEMBERSHIP_LEGEND_CUSTOMIZATION | Legend customization |
| 10000169 | MEMBERSHIP_COHORTS | Membership cohort data |
| 10000170 | MEMBERSHIP_LEGEND_LEADERBOARD | Legend leaderboard |
| 10000171 | MEMBERSHIP_PREMIUM_LEADERBOARD | Premium leaderboard |

#### Utilities

| Kind | Name | Description |
|------|------|-------------|
| 10000140 | TRUSTED_USERS | Trusted user list |
| 10000143 | NOTE_MENTIONS_COUNT | Note mention count |
| 10000149 | EVENT_BROADCAST_RESPONSES | Broadcast response status |
| 10000173 | NIP19_DECODE_RESULT | NIP-19 decode result |
| 10000177 | INVOICES_TO_ZAP_RECEIPTS | Invoice to zap receipt mapping |

---

Comparison to NIP-01

Similarities

1. Core Verbs: Uses REQ and CLOSE verbs exactly as in NIP-01
2. Subscription Model: Maintains the subscription-based architecture
3. Event Format: Standard Nostr events follow NIP-01 structure
4. Response Messages: Uses EVENT, EOSE, OK, CLOSED, NOTICE as in NIP-01

Differences

1. No Standard Filters: Primal cache servers do NOT accept NIP-01 filters (ids, authors, kinds, etc.). All queries must use the cache field with named endpoints
2. Cache-Only API: Major addition of cache filter field for accessing pre-computed data through specific endpoints
3. Custom Event Kinds: 70+ custom event kinds (10000100-10000200 range) for structured responses
4. No EVENT Publishing: Primal cache servers are read-only; clients don't publish events via EVENT messages
5. Endpoint-Specific Parameters: Each cache endpoint has its own parameter schema (e.g., user_pubkey for personalization)
6. Async Endpoints: Some endpoints (net_stats, notifications, etc.) marked as async
7. Pagination: Enhanced pagination with offset parameter and RANGE response kind
8. Content Discovery: Extensive trending/discovery features not in base NIP-01
9. Social Graph Analytics: Built-in follower counts, trust ranks, scoring
10. Media Handling: Integrated media upload and processing
11. Membership Features: Premium features and content moderation
12. Advanced Feeds: Complex feed specifications and directives

Key Architectural Differences

NIP-01 (Basic Relay):
- Generic event storage and retrieval
- Simple filtering on event properties (by ids, authors, kinds, tags, timestamps)
- No pre-computation or caching
- Stateless event serving
- Accepts standard filter objects

Primal Cache Server:
- Pre-computed analytics and feeds
- Named endpoint system (via cache field)
- Does NOT accept standard NIP-01 filters
- Complex social graph queries
- Trending/scoring algorithms
- User-specific personalization
- Media processing pipeline
- Membership/premium features
- Content moderation integration

---

Data Structures

Event Structure

Standard Nostr events follow NIP-01:

{
  "id": "<event_id>",
  "pubkey": "<pubkey>",
  "created_at": <timestamp>,
  "kind": <kind_number>,
  "tags": [["tag_name", "value", ...]],
  "content": "<content>",
  "sig": "<signature>"
}

Custom Event Content Examples

Below are real examples from Primal cache responses with actual data:

EVENT_STATS (kind 10000100) - Engagement metrics for an event:

{
"kind": 10000100,
"content": "{\"event_id\":\"2357e9afd8f28a8df60a9dc059a5588312c492ecdff1a1d1705dca2a588bba29\",\"likes\":15,\"replies\":4,\"mentions\":0,\"reposts\":8,\"zaps\":1,\"satszapped\":100,\"score\":89,\"score24h\":0}"
}

Fields:
- event_id: The event these stats are for
- likes: Number of kind 7 reactions
- replies: Number of kind 1 replies
- mentions: Times this event was mentioned
- reposts: Number of kind 6 reposts
- zaps: Number of zaps received
- satszapped: Total sats received from zaps
- score: Overall engagement score
- score24h: Score within last 24 hours

USER_PROFILE (kind 10000105) - Extended user profile statistics:

{
"kind": 10000105,
"content": "{\"pubkey\":\"0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd\",\"follows_count\":432,\"followers_count\":4959,\"note_count\":4084,\"long_form_note_count\":9,\"reply_count\":2551,\"time_joined\":1711551550,\"relay_count\":4,\"total_zap_count\":2785,\"total_satszapped\":644685,\"media_count\":774,\"content_zap_count\":9}"
}

Fields:
- pubkey: User's public key
- follows_count: Number of users they follow
- followers_count: Number of followers
- note_count: Total posts (kind 1)
- long_form_note_count: Total articles (kind 30023)
- reply_count: Total replies
- time_joined: Unix timestamp of first activity
- relay_count: Number of relays in their list
- total_zap_count: Total zaps received
- total_satszapped: Total sats received
- media_count: Posts with media
- content_zap_count: Zaps on content

USER_FOLLOWER_COUNTS (kind 10000133) - Follower counts for multiple users:

{
"kind": 10000133,
"content": "{\"0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd\":4959}"
}

Maps pubkeys to their follower counts.

USER_SCORES (kind 10000108) - User influence scores:

{
"kind": 10000108,
"content": "{\"0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd\":4959}"
}

Maps pubkeys to their influence scores (typically based on followers).

RANGE (kind 10000113) - Pagination metadata:

{
"kind": 10000113,
"content": "{\"since\":1770407244,\"until\":1770439362,\"order_by\":\"created_at\",\"elements\":[\"11db5f1a2dc5f955447ef7e55a0b41e8c8a272c6ac7a111207b1637cd71a6745\",\"72922d75537b51f0b6d5554a0b0173ad53e7b3791feb5e6f9259be0d4cddee20\"]}"
}

Fields:
- since: Start timestamp of range
- until: End timestamp of range
- order_by: Sort field used
- elements: Array of event IDs in the result set

IS_USER_FOLLOWING (kind 10000125) - Follow status check:

{
"kind": 10000125,
"content": "true"
}

Simple boolean string: "true" or "false".

MEDIA_METADATA (kind 10000119) - Media thumbnails and variants:

{
"kind": 10000119,
"content": "{\"resources\":[{\"url\":\"https://media.ditto.pub/3fd58d3db4fdcf40091d69e881242678c8010acc446c3f0d8e95cc9505b12e7a.jpeg\",\"variants\":[{\"s\":\"m\",\"a\":1,\"w\":300,\"h\":300,\"mt\":\"image/webp\",\"url\":\"https://primal.b-cdn.net/media-cache?s=m&a=1&u=https%3A%2F%2Fmedia.ditto.pub%2F...\"}]}]}"
}

Fields:
- resources: Array of media resources
- url: Original media URL
- variants: Array of processed versions
- s: Size (s=small, m=medium, l=large, o=original)
- a: Aspect ratio preserved (1=yes, 0=no)
- w: Width in pixels
- h: Height in pixels
- mt: MIME type
- url: CDN URL for this variant

LINK_METADATA (kind 10000128) - URL preview data:

{
"kind": 10000128,
"content": "{\"event_id\":\"72922d75537b51f0b6d5554a0b0173ad53e7b3791feb5e6f9259be0d4cddee20\",\"resources\":[{\"url\":\"https://www.youtube.com/watch?v=Q0dfVsLD9Fs\",\"icon_url\":\"\",\"md_image\":\"https://i.ytimg.com/vi/Q0dfVsLD9Fs/mqdefault.jpg\",\"md_title\":\"ROSALÍA - OMEGA (Official Video)\",\"md_description\":\"Watch the official video...\"}]}"
}

Fields:
- event_id: Event containing the link
- resources: Array of link previews
- url: The link URL
- icon_url: Site favicon
- md_image: Preview image
- md_title: Page title
- md_description: Page description/summary

ZAP_EVENT (kind 10000129) - Detailed zap information:

{
"kind": 10000129,
"content": "{\"event_id\":\"df670a0c011959f432b63f0458202f3f985f3fd3765acc788a3276d7229d6850\",\"created_at\":1770441947,\"sender\":\"7807080250235b13c8fb67aeaf34ad8beea...\",\"receiver\":\"0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd\",\"amount_sats\":21,\"message\":\"https://m.youtube.com/@walkeramerica\"}"
}

Fields:
- event_id: Event that was zapped
- created_at: When the zap was sent
- sender: Zapper's pubkey
- receiver: Recipient's pubkey
- amount_sats: Zap amount in satoshis
- message: Optional message with the zap

MEMBERSHIP_COHORTS (kind 10000169) - Premium membership status:

{
"kind": 10000169,
"content": "{\"0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd\":{\"tier\":\"premium\",\"cohort_1\":\"Primal OG\",\"cohort_2\":\"2025\",\"expires_on\":1756684799}}"
}

Fields:
- tier: Membership level ("premium", "legend", etc.)
- cohort_1: Primary cohort label
- cohort_2: Secondary cohort (often year joined)
- expires_on: Unix timestamp when membership expires

DEFAULT_RELAYS (kind 10000124) - Recommended relay list:

{
"kind": 10000124,
"content": "[\"wss://relay.primal.net\",\"wss://nos.lol\",\"wss://njump.me\",\"wss://nostr.t-rg.ws\",\"wss://strfry.openhoofd.nl\",\"wss://nostr.decentony.com\"]"
}

Array of WebSocket relay URLs.

USER_PRIMAL_NAMES (kind 10000158) - Verified Primal usernames:

{
"kind": 10000158,
"content": "{\"0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd\":\"alex\"}"
}

Maps pubkeys to their verified Primal usernames ([email protected]).

UPLOADED (kind 10000121) - Media upload result:

{
"kind": 10000121,
"content": "https://media.primal.net/uploads/..."
}

Returns the URL of successfully uploaded media.

---

Usage Examples

All examples below are tested against the production Primal cache server at wss://cache.primal.net/v1.

Example 1: Get User Profile

Retrieves complete profile information for a user including metadata, follower counts, and membership status.

Request:

["REQ", "test_profile", {
"cache": ["user_profile", {
"pubkey": "0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd"
}]
}]

Response:

["EVENT", "test_profile", {
"kind": 0,
"pubkey": "0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd",
"created_at": 1765775146,
"content": "{\"about\":\"I create software that empowers people online.\\r\\n\\r\\nI'm vegan btw.\",\"banner\":\"https://media.ditto.pub/d685e0d805f62946a9ae56810ab0fcf6de2b997a57afcf35e4d3a810c3e2b4eb.jpeg\",\"lud16\":\"[email protected]\",\"name\":\"Alex Gleason\",\"nip05\":\"[email protected]\",\"picture\":\"https://media.ditto.pub/3fd58d3db4fdcf40091d69e881242678c8010acc446c3f0d8e95cc9505b12e7a.jpeg\",\"website\":\"https://alexgleason.me/\"}",
"tags": [],
"sig": "..."
}]

["EVENT", "test_profile", {
"kind": 10000105,
"content": "{\"pubkey\":\"0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd\",\"follows_count\":432,\"followers_count\":4959,\"note_count\":4084,\"long_form_note_count\":7,\"reply_count\":2823,\"time_joined\":1668449729,\"total_zap_count\":1179,\"total_satszapped\":4554165}"
}]

["EVENT", "test_profile", {
"kind": 10000119,
"content": "{\"resources\":[{\"url\":\"https://media.ditto.pub/3fd58d3db4fdcf40091d69e881242678c8010acc446c3f0d8e95cc9505b12e7a.jpeg\",\"variants\":[{\"s\":\"m\",\"a\":1,\"w\":300,\"h\":300,\"mt\":\"image/webp\",\"url\":\"https://primal.b-cdn.net/media-cache?s=m&a=1&u=https%3A%2F%2Fmedia.ditto.pub%2F3fd58d3db4fdcf40091d69e881242678c8010acc446c3f0d8e95cc9505b12e7a.jpeg\"}]}]}"
}]

["EVENT", "test_profile", {
"kind": 10000169,
"content": "{\"0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd\": {\"tier\": \"premium\", \"cohort_1\": \"Primal OG\", \"cohort_2\": \"2025\", \"expires_on\": 1756684799}}"
}]

["EVENT", "test_profile", {
"kind": 10063,
"pubkey": "0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd",
"created_at": 1716232999,
"content": "",
"tags": [["relays", "wss://relay.mostr.pub", "wss://relay.damus.io", ...]],
"sig": "..."
}]

["EOSE", "test_profile"]

Response includes:
- Kind 0: User metadata (NIP-01)
- Kind 10000105: USER_PROFILE with follower/note counts
- Kind 10000119: MEDIA_METADATA with image variants
- Kind 10000169: MEMBERSHIP_COHORTS showing premium status
- Kind 10063: User relay list (NIP-65)

Example 2: Get User Information (Batch)

Retrieves multiple pieces of information for one or more users in a single request.

Request:

["REQ", "test_infos", {
"cache": ["user_infos", {
"pubkeys": ["0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd"]
}]
}]

Response:

["EVENT", "test_infos", {
"kind": 0,
"pubkey": "0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd",
"content": "{\"about\":\"I create software that empowers people online...\",...}",
...
}]

["EVENT", "test_infos", {
"kind": 10000108,
"content": "{\"0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd\": 4959}"
}]

["EVENT", "test_infos", {
"kind": 10000133,
"content": "{\"0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd\": 4959}"
}]

["EOSE", "test_infos"]

Response includes:
- Kind 0: User metadata
- Kind 10000108: USER_SCORES (follower-based scoring)
- Kind 10000133: USER_FOLLOWER_COUNTS
- Kind 10000119: MEDIA_METADATA for profile images
- Kind 10000169: MEMBERSHIP_COHORTS

Example 3: Get User Feed (Authored Posts)

Retrieves posts authored by a specific user, limited to 5 results.

Request:

["REQ", "test_feed", {
"cache": ["feed", {
"pubkey": "0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd",
"notes": "authored",
"limit": 5
}]
}]

Response (abbreviated):

["EVENT", "test_feed", {
"kind": 1,
"pubkey": "0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd",
"created_at": 1770407993,
"content": "https://www.youtube.com/watch?v=Q0dfVsLD9Fs",
"tags": [["proxy", "https://www.youtube.com/watch?v=Q0dfVsLD9Fs", "activitypub"], ["client", "Ditto"]],
"sig": "..."
}]

["EVENT", "test_feed", {
"kind": 6,
"pubkey": "0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd",
"created_at": 1770407784,
"content": "{...}",
"tags": [["e", "2357e9afd8f28a8df60a9dc059a5588312c492ecdff1a1d1705dca2a588bba29"], ...],
"sig": "..."
}]

["EVENT", "test_feed", {
"kind": 1,
"pubkey": "0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd",
"created_at": 1770407734,
"content": "SecretAsian6 absolute bangers https://www.youtube.com/watch?v=-WbjvylJSnA",
...
}]

["EVENT", "test_feed", {
"kind": 10000100,
"content": "{\"event_id\":\"2357e9afd8f28a8df60a9dc059a5588312c492ecdff1a1d1705dca2a588bba29\",\"likes\":15,\"replies\":4,\"mentions\":0,\"reposts\":8,\"zaps\":1,\"satszapped\":100,\"score\":89,\"score24h\":0}"
}]

["EVENT", "test_feed", {
"kind": 10000113,
"content": "{\"since\": 1770407244, \"until\": 1770439362, \"order_by\": \"created_at\", \"elements\": [\"11db5f1a2dc5f955447ef7e55a0b41e8c8a272c6ac7a111207b1637cd71a6745\", ...]}"
}]

["EVENT", "test_feed", {
"kind": 10000128,
"content": "{\"event_id\": \"72922d75537b51f0b6d5554a0b0173ad53e7b3791feb5e6f9259be0d4cddee20\", \"resources\": [{\"url\": \"https://www.youtube.com/watch?v=Q0dfVsLD9Fs\", \"icon_url\": \"\", \"md_image\": \"https://i.ytimg.com/vi/Q0dfVsLD9Fs/mqdefault.jpg\", \"md_title\": \"ROSALÍA - OMEGA (Official Video)\", \"md_description\": \"...\"}]}"
}]

["EVENT", "test_feed", {
"kind": 10000129,
"content": "{\"event_id\": \"df670a0c011959f432b63f0458202f3f985f3fd3765acc788a3276d7229d6850\", \"created_at\": 1770441947, \"sender\": \"7807080250235b13c8fb67aeaf34...\", \"receiver\": \"0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd\", \"amount_sats\": 21, \"message\": \"https://m.youtube.com/@walkeramerica\"}"
}]

["EOSE", "test_feed"]

Response includes:
- Kind 1: Text notes
- Kind 6: Reposts
- Kind 9735: Zap receipts (on user's posts)
- Kind 0: User metadata for authors and mentioned users
- Kind 10000100: EVENT_STATS (likes, replies, zaps counts)
- Kind 10000107: REFERENCED_EVENT (quoted/reposted events)
- Kind 10000113: RANGE (pagination metadata)
- Kind 10000128: LINK_METADATA (URL previews)
- Kind 10000129: ZAP_EVENT (zap details)
- Kind 10000141: EVENT_RELAYS (where events were seen)

Example 4: Get Thread View

Retrieves a conversation thread including the main event, replies, and parent posts.

Request:

["REQ", "test_thread", {
"cache": ["thread_view", {
"event_id": "df670a0c011959f432b63f0458202f3f985f3fd3765acc788a3276d7229d6850"
}]
}]

Response:
Returns the target event plus related events (parents, replies, quoted events) along with:
- Kind 1: The note and its replies
- Kind 0: Metadata for all involved users
- Kind 10000100: EVENT_STATS for each event
- Kind 10000107: REFERENCED_EVENT for any quoted posts
- Additional metadata kinds as needed

Example 5: Search for Content

Search for posts containing "aliens".

Request:

["REQ", "test_search", {
"cache": ["search", {
"query": "aliens",
"limit": 10
}]
}]

Response:
Returns matching posts (kind 1) along with associated metadata, user profiles, and engagement statistics.

Example 6: Search for Users

Search for users by username or display name.

Request:

["REQ", "test_user_search", {
"cache": ["user_search", {
"query": "jack",
"limit": 10
}]
}]

Response:
Returns:
- Kind 0: User metadata for matching users
- Kind 10000108: USER_SCORES
- Kind 10000133: USER_FOLLOWER_COUNTS
- Kind 10000158: USER_PRIMAL_NAMES (verified usernames)

Example 7: Get Trending Posts

Retrieve globally trending posts from the last 24 hours.

Request:

["REQ", "test_trending", {
"cache": ["explore_global_trending_24h", {}]
}]

Response (abbreviated):

["EVENT", "test_trending", {
"kind": 1,
"pubkey": "f72127cae7d232d87edcf91f88df0d599b5039bfe5f9ff43f4848a0c39e1673e",
"created_at": 1770459290,
"content": "Securely deploy your own clawbot 🦀 in minutes\nhttps://nowclaw.com\nhttps://blosstr.com/4f63db23b53784787b769ecbb5ac7f33b2d1958015acf77169e1cab01b8207af.mp4",
"tags": [["client", "Damus"]],
"sig": "..."
}]

["EVENT", "test_trending", {
"kind": 10000100,
"content": "{\"event_id\":\"998c0b40473a93bd33e8b83ef49b1b1900194f473f75e8faaab5d8256a95d8b9\",\"likes\":82,\"replies\":50,\"mentions\":0,\"reposts\":102,\"zaps\":5,\"satszapped\":21000,\"score\":3421,\"score24h\":3421}"
}]

["EVENT", "test_trending", {
"kind": 10000119,
"content": "{\"thumbnails\":{\"https://blosstr.com/4f63db23b53784787b769ecbb5ac7f33b2d1958015acf77169e1cab01b8207af.mp4\":\"https://r2.primal.net/cache/a/64/7a/a647a41db4a...\"}}"
}]

["EVENT", "test_trending", {
"kind": 10000128,
"content": "{\"event_id\":\"998c0b40473a93bd33e8b83ef49b1b1900194f473f75e8faaab5d8256a95d8b9\",\"resources\":[{\"url\":\"https://nowclaw.com\",\"mimetype\":\"text/html\",\"md_title\":\"NowClaw - Deploy Your Own Nostr Bot\",\"md_description\":\"...\",\"icon_url\":\"...\"}]}"
}]

["EOSE", "test_trending"]

Example 8: Get Event Actions (Likes)

Retrieve users who liked a specific post.

Request:

["REQ", "test_likes", {
"cache": ["event_actions", {
"event_id": "df670a0c011959f432b63f0458202f3f985f3fd3765acc788a3276d7229d6850",
"kind": 7,
"limit": 20
}]
}]

Response (abbreviated):

["EVENT", "test_likes", {
"kind": 7,
"pubkey": "b1d620b63be952e61a4ecd3d76f9abc635cfb5b3ddade661c53835c77ea4c83c",
"created_at": 1770480478,
"content": "+",
"tags": [
["e", "df670a0c011959f432b63f0458202f3f985f3fd3765acc788a3276d7229d6850"],
["p", "0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd"]
],
"sig": "..."
}]

["EVENT", "test_likes", {
"kind": 7,
"pubkey": "6597a1668c0436f5a7de6dd0da9b355eea0e62693a65c3d90a055eb9e0dd6fe1",
"created_at": 1770449193,
"content": "+",
"tags": [...],
"sig": "..."
}]

["EVENT", "test_likes", {
"kind": 0,
"pubkey": "b1d620b63be952e61a4ecd3d76f9abc635cfb5b3ddade661c53835c77ea4c83c",
"content": "{\"name\":\"User Name\",\"about\":\"...\"}",
...
}]

["EOSE", "test_likes"]

Response includes:
- Kind 7: Like reactions
- Kind 0: User metadata for users who liked the post
- Additional metadata kinds as needed

Other action kinds:
- kind: 1 - Replies to the event
- kind: 6 - Reposts/quotes
- kind: 9735 - Zap receipts

Example 9: Get Scored/Trending Feed

Get posts from various trending/scoring algorithms.

Request (trending_24h):

["REQ", "test_scored", {
"cache": ["scored", {
"selector": "trending_24h"
}]
}]

Request (mostzapped_4h):

["REQ", "test_scored", {
"cache": ["scored", {
"selector": "mostzapped_4h"
}]
}]

Available selectors:
- trending_24h, trending_12h, trending_4h, trending_1h - Trending by engagement
- mostzapped_24h, mostzapped_12h, mostzapped_4h, mostzapped_1h - Most zapped posts
- gm_trending_24h, gm_trending_12h, etc. - GM-specific trending
- classic_trending_24h, etc. - Classic algorithm

Response:
Similar to explore_global_trending_24h, returns posts with full metadata, stats, media info, etc.

Example 10: Get User Followers

Retrieve a list of users who follow a specific account.

Request:

["REQ", "test_followers", {
"cache": ["user_followers", {
"pubkey": "82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2",
"limit": 20
}]
}]

Response:
Returns:
- Kind 0: User metadata for each follower
- Kind 10000108: USER_SCORES
- Kind 10000133: USER_FOLLOWER_COUNTS
- Kind 10000119: MEDIA_METADATA (profile images)
- Kind 10000158: USER_PRIMAL_NAMES
- Kind 10000169: MEMBERSHIP_COHORTS
- Kind 10063: User relay lists

Example 11: Check if User is Following

Check if one user follows another.

Request:

["REQ", "test_is_following", {
"cache": ["is_user_following", {
"pubkey": "82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2",
"user_pubkey": "0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd"
}]
}]

Response:

["EVENT", "test_is_following", {
"kind": 10000125,
"content": "true"
}]

["EOSE", "test_is_following"]

Returns a simple boolean string ("true" or "false") in the content field.

Example 12: Get Specific Events

Retrieve one or more events by their IDs with extended metadata.

Request:

["REQ", "test_events", {
"cache": ["events", {
"event_ids": ["df670a0c011959f432b63f0458202f3f985f3fd3765acc788a3276d7229d6850"],
"extended_response": true
}]
}]

Response:
Returns:
- Kind 1: The requested event(s)
- Kind 0: User metadata for event authors
- Kind 10000100: EVENT_STATS (engagement metrics)
- Kind 10000119: MEDIA_METADATA
- Kind 10000129: ZAP_EVENT (zaps on the event)
- Kind 10000141: EVENT_RELAYS (where event was seen)
- Kind 10063: User relay lists

Example 13: Get Default Relay Recommendations

Retrieve Primal's recommended default relays for new users.

Request:

["REQ", "test_default_relays", {
"cache": ["get_default_relays"]
}]

Response:

["EVENT", "test_default_relays", {
"kind": 10000124,
"content": "[\"wss://relay.primal.net\",\"wss://nos.lol\",\"wss://njump.me\",\"wss://nostr.t-rg.ws\",\"wss://strfry.openhoofd.nl\",\"wss://nostr.decentony.com\"]"
}]

["EOSE", "test_default_relays"]

Example 14: Get User's Mute List

Retrieve a user's mute list for content moderation.

Request:

["REQ", "test_mutelist", {
"cache": ["mutelist", {
"pubkey": "82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2"
}]
}]

Response:
Returns:
- Kind 10000: Mute list event containing muted pubkeys and event IDs
- Kind 0: User metadata for muted users
- Kind 10000108: USER_SCORES for muted users

Example 15: Explore Zaps

Get trending/recent zaps across the network.

Request:

["REQ", "test_explore_zaps", {
"cache": ["explore_zaps", {
"limit": 10
}]
}]

Response (abbreviated):
Returns:
- Kind 9735: Zap receipt events
- Kind 1: The posts that were zapped
- Kind 0: User metadata for zappers and recipients
- Kind 10000100: EVENT_STATS for zapped posts
- Kind 10000129: ZAP_EVENT (detailed zap information)
- Kind 10000119: MEDIA_METADATA
- Kind 10000158: USER_PRIMAL_NAMES
- Kind 10000169: MEMBERSHIP_COHORTS

Example 16: Explore People

Discover trending or recommended users.

Request:

["REQ", "test_explore_people", {
"cache": ["explore_people", {
"limit": 10
}]
}]

Response:
Returns:
- Kind 0: User metadata for recommended users
- Kind 10000108: USER_SCORES
- Kind 10000133: USER_FOLLOWER_COUNTS
- Kind 10000119: MEDIA_METADATA (profile images)
- Kind 10000113: RANGE (pagination metadata)
- Kind 10063: User relay lists

Example 17: Explore Media

Get trending posts with media content (images/videos).

Request:

["REQ", "test_explore_media", {
"cache": ["explore_media", {
"limit": 10
}]
}]

Response:
Returns:
- Kind 1: Posts containing media
- Kind 0: User metadata for post authors
- Kind 10000100: EVENT_STATS
- Kind 10000119: MEDIA_METADATA (thumbnails, dimensions, etc.)
- Kind 10000128: LINK_METADATA (for linked content)
- Kind 10000113: RANGE

Example 18: Get Event Actions (Replies)

Retrieve all replies to a specific event.

Request:

["REQ", "test_replies", {
"cache": ["event_actions", {
"event_id": "df670a0c011959f432b63f0458202f3f985f3fd3765acc788a3276d7229d6850",
"kind": 1,
"limit": 20
}]
}]

Response:
Returns:
- Kind 1: Reply posts that reference the target event
- Kind 0: User metadata for reply authors
- Kind 10000119: MEDIA_METADATA
- Kind 10000133: USER_FOLLOWER_COUNTS
- Kind 10063: User relay lists

Example 19: Get Event Actions (Zaps)

Retrieve all zaps sent to a specific event.

Request:

["REQ", "test_zaps", {
"cache": ["event_actions", {
"event_id": "df670a0c011959f432b63f0458202f3f985f3fd3765acc788a3276d7229d6850",
"kind": 9735,
"limit": 20
}]
}]

Response:
Returns:
- Kind 9735: Zap receipt events
- Kind 0: User metadata for zappers
- Kind 10000108: USER_SCORES
- Kind 10000133: USER_FOLLOWER_COUNTS
- Kind 10000129: ZAP_EVENT (detailed zap info including amount)

Example 20: Get Contact List

Retrieve a user's full contact/follow list.

Request:

["REQ", "test_contacts", {
"cache": ["contact_list", {
"pubkey": "0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd"
}]
}]

Response:
Returns:
- Kind 3: Contact list event with follows
- Kind 0: User metadata for all follows (can be 100s-1000s)
- Kind 10000108: USER_SCORES (aggregated)
- Kind 10000119: MEDIA_METADATA for all follow profiles
- Kind 10000133: USER_FOLLOWER_COUNTS
- Kind 10000158: USER_PRIMAL_NAMES
- Kind 10063: User relay lists for follows

Note: This can return very large responses (1MB+) for users with many follows.

Example 21: Get Follows Feed

Retrieve posts from users that a specific account follows.

Request:

["REQ", "test_feed_follows", {
"cache": ["feed", {
"pubkey": "82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2",
"notes": "follows",
"limit": 10
}]
}]

Response:
Returns:
- Kind 1: Text posts from followed users
- Kind 6: Reposts from followed users
- Kind 9735: Zap receipts
- Kind 0: User metadata for post authors
- Kind 10000100: EVENT_STATS
- Kind 10000113: RANGE (pagination)
- Kind 10000119: MEDIA_METADATA
- Kind 10000128: LINK_METADATA
- Kind 10000129: ZAP_EVENT
- Kind 10000158: USER_PRIMAL_NAMES
- Kind 10000169: MEMBERSHIP_COHORTS

Example 22: Close Subscription

Request:

["CLOSE", "test_profile"]

No response required per NIP-01. The server stops sending events for this subscription ID.

---

Testing the API

You can test the Primal WebSocket API using command-line tools or scripts. Here are a few approaches:

Using wscat

# Install wscat
npm install -g wscat

Connect and send a request

echo '["REQ", "test1", {"cache": ["user_profile", {"pubkey": "0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd"}]}]' | \ wscat -c wss://cache.primal.net/v1

Using WebSocket (Browser/Node.js)

const socket = new WebSocket('wss://cache.primal.net/v1');

socket.onopen = () => {
socket.send(JSON.stringify([
"REQ",
"my_sub",
{"cache": ["user_profile", {"pubkey": "0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd"}]}
]));
};

socket.onmessage = (event) => {
const msg = JSON.parse(event.data);
console.log(msg);

if (msg[0] === 'EOSE') {
socket.close();
}
};

socket.onerror = (error) => {
console.error('WebSocket error:', error);
};

socket.onclose = () => {
console.log('Connection closed');
};

Understanding Response Structure

Primal responses typically include:

1. Main Content Events: The actual data you requested (kind 0, 1, 6, etc.)
2. Metadata Events: Enrichment data about the content (10000xxx kinds)
3. RANGE Event (kind 10000113): Pagination info with since, until, and elements array
4. EOSE: End of stored events marker

Example response flow for user_profile:

EVENT (kind 0) → User metadata
EVENT (kind 10000105) → Profile stats (followers, notes count)
EVENT (kind 10000119) → Media metadata (profile images)
EVENT (kind 10000169) → Membership status
EVENT (kind 10063) → User relay list
EOSE → Done

---

Best Practices

1. Single Connection: Open one WebSocket per relay and reuse for all subscriptions
2. Unique Subscription IDs: Use random, unique strings for each subscription
3. Handle EOSE: Wait for EOSE before considering initial query complete
4. Pagination: Use offset and limit for large result sets
5. User Context: Include user_pubkey when available for personalized results
6. Rate Limiting: Respect server limits (max 1000 for most limit parameters)
7. Time Ranges: Use since/until for efficient time-based queries
8. Event Validation: Validate event signatures and structure
9. Reconnection: Implement exponential backoff for reconnections
10. Error Handling: Handle CLOSED and NOTICE messages appropriately

---

Common Patterns

Fetching User Data

When displaying a user profile, you typically need:

// 1. Get complete profile
["REQ", "profile", {"cache": ["user_profile", {"pubkey": "<pubkey>"}]}]

// 2. Get user's posts
["REQ", "posts", {"cache": ["feed", {
"pubkey": "<pubkey>",
"notes": "authored",
"limit": 20
}]}]

// 3. Get follower information
["REQ", "followers", {"cache": ["user_followers", {
"pubkey": "<pubkey>",
"limit": 100
}]}]

Building a Feed

For a personalized feed showing posts from users someone follows:

["REQ", "home_feed", {"cache": ["feed", {
  "pubkey": "<user_pubkey>",
  "notes": "follows",
  "limit": 50,
  "user_pubkey": "<user_pubkey>"  // For personalization
}]}]

Pagination

Use offset and limit for pagination:

// Page 1
["REQ", "page1", {"cache": ["feed", {
  "pubkey": "<pubkey>",
  "notes": "authored",
  "limit": 20,
  "offset": 0
}]}]

// Page 2
["REQ", "page2", {"cache": ["feed", {
"pubkey": "<pubkey>",
"notes": "authored",
"limit": 20,
"offset": 20
}]}]

The RANGE event (kind 10000113) in responses contains the actual range of data returned:

{
  "kind": 10000113,
  "content": "{\"since\": 1770407244, \"until\": 1770439362, \"order_by\": \"created_at\", \"elements\": [\"event_id1\", \"event_id2\", ...]}"
}

Real-time Updates

Keep a subscription open to receive real-time updates:

const socket = new WebSocket('wss://cache.primal.net/v1');

socket.onopen = () => {
// Subscribe to notifications
socket.send(JSON.stringify([
"REQ",
"notifications",
{"cache": ["get_notifications", {
"pubkey": "<user_pubkey>",
"limit": 100
}]}
]));
};

socket.onmessage = (event) => {
const msg = JSON.parse(event.data);

if (msg[0] === 'EVENT') {
// Handle new notification
console.log('New notification:', msg);
} else if (msg[0] === 'EOSE') {
console.log('Initial notifications loaded');
// Keep connection open - new events will arrive as they happen
}
};

// Close when done:
socket.send(JSON.stringify(["CLOSE", "notifications"]));
socket.close();

---

Error Handling

The server may close subscriptions with CLOSED messages:

["CLOSED", "sub1", "error: could not connect to the database"]
["CLOSED", "sub2", "unsupported: filter contains unknown elements"]
["CLOSED", "sub3", "rate-limited: slow down there chief"]

Common error prefixes:
- error: Server error
- unsupported: Unsupported filter/feature
- rate-limited: Too many requests
- invalid: Invalid parameters
- blocked: Access denied

---

Rate Limits & Constraints

- Max limit parameter: 1000 (most endpoints)
- Max subscription ID length: 64 characters
- Max upload size: Configurable (default varies)
- Connection limits: May vary by server configuration
- Some endpoints have specific lower limits (noted in documentation)

---

Additional Resources

- NIP-01 Specification
- Primal GitHub Repository
- Primal Cache Endpoint: wss://cache.primal.net/v1

---

Appendix: Complete Endpoint Index

See individual endpoint documentation above for:

- 140+ cache endpoints
- 70+ custom event kinds
- Standard NIP-01 message types
- Extended filter parameters
- Response data structures