# Primal WebSocket API Documentation ## Overview Primal Server exposes a WebSocket-based API for Nostr clients, inspired by and extending the [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md) 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:** ```json ["REQ", , ] ``` **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: ```json { "cache": ["", {}] } ``` **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 ```json { "cache": ["", {}] } ``` ### Cache Endpoints All cache endpoints are accessed via the `cache` filter field. Below is a comprehensive list: #### Network & Statistics **`net_stats`** - Network statistics (async) ```json ["REQ", "sub_id", {"cache": ["net_stats"]}] ``` Returns global network statistics including user counts, post counts, zap totals, etc. **`nostr_stats`** - Nostr network stats ```json ["REQ", "sub_id", {"cache": ["nostr_stats"]}] ``` **`server_name`** - Get server name ```json ["REQ", "sub_id", {"cache": ["server_name"]}] ``` #### Feed Endpoints **`feed`** - User feed ```json ["REQ", "sub_id", { "cache": ["feed", { "pubkey": "", "notes": "follows|authored|bookmarks|user_media_thumbnails", "include_replies": true|false, "limit": 20, "since": , "until": , "offset": 0, "user_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 ```json ["REQ", "sub_id", { "cache": ["feed_2", { "pubkey": "", "notes": "follows|authored", "kinds": [1, 6], "since": , "until": , "limit": 20, "offset": 0, "order": "asc|desc", "user_pubkey": "" }] }] ``` Additional parameters: - `kinds`: Filter by event kinds - `order`: Sort order (asc/desc) **`thread_view`** - Conversation thread ```json ["REQ", "sub_id", { "cache": ["thread_view", { "event_id": "", "limit": 20, "since": , "until": , "offset": 0, "user_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 ```json ["REQ", "sub_id", { "cache": ["user_profile", { "pubkey": "", "user_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 ```json ["REQ", "sub_id", { "cache": ["user_infos", { "pubkeys": ["", "", ...] }] }] ``` 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 ```json ["REQ", "sub_id", { "cache": ["contact_list", { "pubkey": "", "extended_response": true|false }] }] ``` **`is_user_following`** - Check if user follows another ```json ["REQ", "sub_id", { "cache": ["is_user_following", { "pubkey": "", "user_pubkey": "" }] }] ``` **`user_followers`** - Get user followers ```json ["REQ", "sub_id", { "cache": ["user_followers", { "pubkey": "", "limit": 200 }] }] ``` **`mutual_follows`** - Mutual followers between users ```json ["REQ", "sub_id", { "cache": ["mutual_follows", { "pubkey": "", "user_pubkey": "" }] }] ``` **`user_profile_followed_by`** - Who follows this user ```json ["REQ", "sub_id", { "cache": ["user_profile_followed_by", { "pubkey": "" }] }] ``` #### Events & Actions **`events`** - Get specific events ```json ["REQ", "sub_id", { "cache": ["events", { "event_ids": ["", "", ...], "extended_response": true|false, "user_pubkey": "" }] }] ``` **`event_actions`** - Event interactions (likes, reposts, etc.) ```json ["REQ", "sub_id", { "cache": ["event_actions", { "event_id": "", "kind": , "limit": 100, "offset": 0 }] }] ``` Or for addressable events: ```json { "cache": ["event_actions", { "pubkey": "", "identifier": "", "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 ```json ["REQ", "sub_id", { "cache": ["zaps_feed", { "pubkey": "", "limit": 20, "offset": 0 }] }] ``` **`user_zaps`** - Zaps received by user ```json ["REQ", "sub_id", { "cache": ["user_zaps", { "pubkey": "", "limit": 20, "offset": 0 }] }] ``` **`user_zaps_by_satszapped`** - Zaps sorted by amount ```json ["REQ", "sub_id", { "cache": ["user_zaps_by_satszapped", { "pubkey": "", "limit": 20, "offset": 0 }] }] ``` **`user_zaps_sent`** - Zaps sent by user ```json ["REQ", "sub_id", { "cache": ["user_zaps_sent", { "pubkey": "", "limit": 20, "offset": 0 }] }] ``` **`event_zaps_by_satszapped`** - Zaps on specific event ```json ["REQ", "sub_id", { "cache": ["event_zaps_by_satszapped", { "event_id": "", "limit": 20, "offset": 0, "user_pubkey": "" }] }] ``` Or for addressable events: ```json { "cache": ["event_zaps_by_satszapped", { "pubkey": "", "identifier": "", "limit": 20, "offset": 0, "user_pubkey": "" }] } ``` **`invoices_to_zap_receipts`** - Convert invoices to zap receipts ```json ["REQ", "sub_id", { "cache": ["invoices_to_zap_receipts", { "invoices": ["", "", ...] }] }] ``` #### Direct Messages **`get_directmsg_contacts`** - Get DM contacts ```json ["REQ", "sub_id", { "cache": ["get_directmsg_contacts", { "receiver_pubkey": "", "user_pubkey": "" }] }] ``` **`get_directmsgs`** - Get direct messages ```json ["REQ", "sub_id", { "cache": ["get_directmsgs", { "receiver": "", "sender": "", "user_pubkey": "", "limit": 20, "since": , "until": }] }] ``` **`directmsg_count`** - DM count (async) ```json ["REQ", "sub_id", { "cache": ["directmsg_count", { "receiver_pubkey": "" }] }] ``` **`directmsg_count_2`** - Extended DM count (async) ```json ["REQ", "sub_id", { "cache": ["directmsg_count_2", { "receiver_pubkey": "" }] }] ``` **`reset_directmsg_count`** - Reset DM counter ```json ["REQ", "sub_id", { "cache": ["reset_directmsg_count", { "receiver_pubkey": "", "sender_pubkey": "" }] }] ``` **`reset_directmsg_counts`** - Reset all DM counters ```json ["REQ", "sub_id", { "cache": ["reset_directmsg_counts", { "receiver_pubkey": "" }] }] ``` #### Content Moderation **`mutelist`** - Get mute list ```json ["REQ", "sub_id", { "cache": ["mutelist", { "pubkey": "" }] }] ``` **`mutelists`** - Get multiple mute lists ```json ["REQ", "sub_id", { "cache": ["mutelists", { "pubkeys": ["", "", ...] }] }] ``` **`allowlist`** - Get allow list ```json ["REQ", "sub_id", { "cache": ["allowlist", { "pubkey": "" }] }] ``` **`is_hidden_by_content_moderation`** - Check if content is hidden ```json ["REQ", "sub_id", { "cache": ["is_hidden_by_content_moderation", { "pubkey": "", "event_id": "" }] }] ``` #### Explore & Discovery **`explore_zaps`** - Trending zaps ```json ["REQ", "sub_id", { "cache": ["explore_zaps", { "limit": 20, "user_pubkey": "" }] }] ``` **`explore_people`** - Discover users ```json ["REQ", "sub_id", { "cache": ["explore_people", { "limit": 20, "user_pubkey": "" }] }] ``` **`explore_media`** - Trending media ```json ["REQ", "sub_id", { "cache": ["explore_media", { "limit": 20, "user_pubkey": "" }] }] ``` **`explore_topics`** - Trending topics ```json ["REQ", "sub_id", { "cache": ["explore_topics", { "limit": 20, "user_pubkey": "" }] }] ``` **`scored`** - Get scored/trending content ```json ["REQ", "sub_id", { "cache": ["scored", { "selector": "trending_24h|trending_12h|trending_4h|mostzapped_24h|...", "user_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 ```json ["REQ", "sub_id", { "cache": ["scored_users", { "limit": 20, "since": , "user_pubkey": "" }] }] ``` **`scored_users_24h`** - Trending users (24h) ```json ["REQ", "sub_id", { "cache": ["scored_users_24h", { "user_pubkey": "" }] }] ``` **`explore_global_trending_24h`** - Global trending (24h) ```json ["REQ", "sub_id", { "cache": ["explore_global_trending_24h", { "user_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) ```json ["REQ", "sub_id", { "cache": ["explore_global_mostzapped_4h", { "user_pubkey": "" }] }] ``` **`explore`** - General explore endpoint ```json ["REQ", "sub_id", { "cache": ["explore", { "timeframe": "latest|popular|trending|mostzapped", "scope": "global|network|tribe|follows", "user_pubkey": "", "limit": 20, "since": , "until": }] }] ``` **`explore_legend_counts`** - Network statistics for user ```json ["REQ", "sub_id", { "cache": ["explore_legend_counts", { "pubkey": "" }] }] ``` #### Search **`search`** - Search events ```json ["REQ", "sub_id", { "cache": ["search", { "query": "", "kind": , "limit": 20, "since": , "until": , "offset": 0, "user_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 ```json ["REQ", "sub_id", { "cache": ["advanced_search", { "query": "", "user_pubkey": "", "limit": 20 }] }] ``` **`user_search`** - Search users ```json ["REQ", "sub_id", { "cache": ["user_search", { "query": "", "limit": 20, "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 ```json ["REQ", "sub_id", { "cache": ["long_form_content_feed", { "pubkey": "", "limit": 20, "since": , "until": , "offset": 0, "user_pubkey": "" }] }] ``` **`long_form_content_thread_view`** - Article with comments ```json ["REQ", "sub_id", { "cache": ["long_form_content_thread_view", { "event_id": "", "limit": 20, "user_pubkey": "" }] }] ``` **`get_recommended_reads`** - Recommended articles ```json ["REQ", "sub_id", { "cache": ["get_recommended_reads", { "pubkey": "", "limit": 20 }] }] ``` **`get_reads_topics`** - Article topics ```json ["REQ", "sub_id", { "cache": ["get_reads_topics"]}] }] ``` **`get_featured_authors`** - Featured article authors ```json ["REQ", "sub_id", { "cache": ["get_featured_authors"]}] }] ``` **`articles_stats`** - Article statistics ```json ["REQ", "sub_id", { "cache": ["articles_stats", { "pubkey": "" }] }] ``` **`drafts`** - Article drafts ```json ["REQ", "sub_id", { "cache": ["drafts", { "pubkey": "" }] }] ``` **`top_article`** - Top article ```json ["REQ", "sub_id", { "cache": ["top_article", { "pubkey": "" }] }] ``` #### Bookmarks & Highlights **`get_bookmarks`** - Get bookmarks ```json ["REQ", "sub_id", { "cache": ["get_bookmarks", { "pubkey": "" }] }] ``` **`get_highlights`** - Get highlights ```json ["REQ", "sub_id", { "cache": ["get_highlights", { "pubkey": "" }] }] ``` #### Relays **`get_user_relays`** - User relay list ```json ["REQ", "sub_id", { "cache": ["get_user_relays", { "pubkey": "" }] }] ``` **`get_user_relays_2`** - Extended relay list ```json ["REQ", "sub_id", { "cache": ["get_user_relays_2", { "pubkey": "" }] }] ``` **`relays`** - Popular relays ```json ["REQ", "sub_id", { "cache": ["relays", { "limit": 20 }] }] ``` **`get_default_relays`** - Default relay recommendations ```json ["REQ", "sub_id", { "cache": ["get_default_relays"]}] }] ``` #### Recommendations **`get_recommended_users`** - Recommended users to follow ```json ["REQ", "sub_id", { "cache": ["get_recommended_users"]}] }] ``` **`get_suggested_users`** - Suggested user accounts ```json ["REQ", "sub_id", { "cache": ["get_suggested_users"]}] }] ``` #### Feeds & Directives **`feed_directive`** - Custom feed directive ```json ["REQ", "sub_id", { "cache": ["feed_directive", { "directive": "" }] }] ``` **`feed_directive_2`** - Extended feed directive ```json ["REQ", "sub_id", { "cache": ["feed_directive_2", { "directive": "" }] }] ``` **`mega_feed_directive`** - Mega feed endpoint ```json ["REQ", "sub_id", { "cache": ["mega_feed_directive", { "spec": "" }] }] ``` **`advanced_feed`** - Advanced feed ```json ["REQ", "sub_id", { "cache": ["advanced_feed", { "specification": "" }] }] ``` **`get_advanced_feeds`** - Available advanced feeds ```json ["REQ", "sub_id", { "cache": ["get_advanced_feeds"]}] }] ``` **`get_home_feeds`** - Home feed configurations ```json ["REQ", "sub_id", { "cache": ["get_home_feeds"]}] }] ``` **`get_reads_feeds`** - Article feed configurations ```json ["REQ", "sub_id", { "cache": ["get_reads_feeds"]}] }] ``` **`enrich_feed_events`** - Enrich feed with metadata ```json ["REQ", "sub_id", { "cache": ["enrich_feed_events", { "events": [] }] }] ``` #### DVM (Data Vending Machines) **`get_dvm_feeds`** - DVM feed list ```json ["REQ", "sub_id", { "cache": ["get_dvm_feeds"]}] }] ``` **`dvm_feed_info`** - DVM feed details ```json ["REQ", "sub_id", { "cache": ["dvm_feed_info", { "feed_id": "" }] }] ``` **`get_featured_dvm_feeds`** - Featured DVM feeds ```json ["REQ", "sub_id", { "cache": ["get_featured_dvm_feeds"]}] }] ``` #### Settings & Configuration **`set_app_settings`** - Set user app settings ```json ["REQ", "sub_id", { "cache": ["set_app_settings", { "settings_event": }] }] ``` **`get_app_settings`** - Get app settings ```json ["REQ", "sub_id", { "cache": ["get_app_settings", { "event_from_user": }] }] ``` **`get_app_settings_2`** - Extended app settings ```json ["REQ", "sub_id", { "cache": ["get_app_settings_2", { "event_from_user": }] }] ``` **`get_default_app_settings`** - Default app settings ```json ["REQ", "sub_id", { "cache": ["get_default_app_settings", { "client": "Primal-Web App" }] }] ``` **`set_app_subsettings`** - Set app subsettings ```json ["REQ", "sub_id", { "cache": ["set_app_subsettings", { "event_from_user": }] }] ``` **`get_app_subsettings`** - Get app subsettings ```json ["REQ", "sub_id", { "cache": ["get_app_subsettings", { "event_from_user": }] }] ``` **`get_default_app_subsettings`** - Default subsettings ```json ["REQ", "sub_id", { "cache": ["get_default_app_subsettings", { "subkey": "user-home-feeds|user-reads-feeds" }] }] ``` **`client_config`** - Client configuration ```json ["REQ", "sub_id", { "cache": ["client_config"]}] }] ``` **`get_app_releases`** - App release information ```json ["REQ", "sub_id", { "cache": ["get_app_releases"]}] }] ``` #### Notifications **`notifications`** - Get notifications (async) ```json ["REQ", "sub_id", { "cache": ["notifications", { "pubkey": "" }] }] ``` **`notification_counts`** - Notification counts (async) ```json ["REQ", "sub_id", { "cache": ["notification_counts", { "pubkey": "" }] }] ``` **`notification_counts_2`** - Extended notification counts (async) ```json ["REQ", "sub_id", { "cache": ["notification_counts_2", { "pubkey": "" }] }] ``` **`get_notifications`** - Get notifications with filters ```json ["REQ", "sub_id", { "cache": ["get_notifications", { "pubkey": "", "limit": 1000, "since": , "until": , "offset": 0, "user_pubkey": "", "type": "", "type_group": "" }] }] ``` **`set_notifications_seen`** - Mark notifications as seen ```json ["REQ", "sub_id", { "cache": ["set_notifications_seen", { "event_from_user": }] }] ``` **`get_notifications_seen`** - Get seen notification timestamp ```json ["REQ", "sub_id", { "cache": ["get_notifications_seen", { "event_from_user": }] }] ``` #### Push Notifications **`update_push_notification_token`** - Update push token ```json ["REQ", "sub_id", { "cache": ["update_push_notification_token", { "event_from_user": }] }] ``` **`update_push_notification_token_for_nip46`** - Update push token for NIP-46 ```json ["REQ", "sub_id", { "cache": ["update_push_notification_token_for_nip46", { "event_from_user": }] }] ``` **`events_nip46`** - NIP-46 events ```json ["REQ", "sub_id", { "cache": ["events_nip46", { "pubkey": "" }] }] ``` #### Hashtags & Trending **`trending_hashtags_4h`** - Trending hashtags (4h) ```json ["REQ", "sub_id", { "cache": ["trending_hashtags_4h"]}] }] ``` **`trending_hashtags_7d`** - Trending hashtags (7d) ```json ["REQ", "sub_id", { "cache": ["trending_hashtags_7d"]}] }] ``` **`trending_images`** - Trending images ```json ["REQ", "sub_id", { "cache": ["trending_images", { "limit": 20 }] }] ``` **`trending_images_4h`** - Trending images (4h) ```json ["REQ", "sub_id", { "cache": ["trending_images_4h"]}] }] ``` #### Lists & Collections **`parameterized_replaceable_list`** - Get parameterized replaceable list ```json ["REQ", "sub_id", { "cache": ["parameterized_replaceable_list", { "pubkey": "", "identifier": "", "kind": }] }] ``` **`parametrized_replaceable_event`** - Get single parameterized event ```json ["REQ", "sub_id", { "cache": ["parametrized_replaceable_event", { "pubkey": "", "identifier": "", "kind": }] }] ``` **`parametrized_replaceable_events`** - Get multiple parameterized events ```json ["REQ", "sub_id", { "cache": ["parametrized_replaceable_events", { "pubkey": "", "identifiers": ["", "", ...], "kind": }] }] ``` **`replaceable_event`** - Get replaceable event ```json ["REQ", "sub_id", { "cache": ["replaceable_event", { "pubkey": "", "kind": }] }] ``` **`follow_lists`** - Get follow lists ```json ["REQ", "sub_id", { "cache": ["follow_lists", { "pubkey": "" }] }] ``` **`follow_list`** - Get specific follow list ```json ["REQ", "sub_id", { "cache": ["follow_list", { "pubkey": "", "identifier": "" }] }] ``` #### Content Filtering **`search_filterlist`** - Search filter list ```json ["REQ", "sub_id", { "cache": ["search_filterlist", { "query": "" }] }] ``` **`get_filterlist`** - Get filter list ```json ["REQ", "sub_id", { "cache": ["get_filterlist", { "pubkey": "" }] }] ``` **`check_filterlist`** - Check if filtered ```json ["REQ", "sub_id", { "cache": ["check_filterlist", { "target": "", "target_type": "pubkey|event" }] }] ``` #### Reporting **`report_user`** - Report user ```json ["REQ", "sub_id", { "cache": ["report_user", { "event_from_user": }] }] ``` **`report_note`** - Report note ```json ["REQ", "sub_id", { "cache": ["report_note", { "event_from_user": }] }] ``` #### Event Broadcasting **`import_events`** - Import events ```json ["REQ", "sub_id", { "cache": ["import_events", { "events": [] }] }] ``` **`broadcast_reply`** - Broadcast reply ```json ["REQ", "sub_id", { "cache": ["broadcast_reply", { "event_from_user": }] }] ``` **`broadcast_events`** - Broadcast multiple events ```json ["REQ", "sub_id", { "cache": ["broadcast_events", { "events": [] }] }] ``` #### Membership Features **`membership_media_management_stats`** - Media storage stats ```json ["REQ", "sub_id", { "cache": ["membership_media_management_stats", { "event_from_user": }] }] ``` **`membership_media_management_uploads`** - List uploads ```json ["REQ", "sub_id", { "cache": ["membership_media_management_uploads", { "event_from_user": }] }] ``` **`membership_media_management_delete`** - Delete uploads ```json ["REQ", "sub_id", { "cache": ["membership_media_management_delete", { "event_from_user": }] }] ``` **`membership_recovery_contact_lists`** - Recover contact lists ```json ["REQ", "sub_id", { "cache": ["membership_recovery_contact_lists", { "event_from_user": }] }] ``` **`membership_recover_contact_list`** - Recover specific contact list ```json ["REQ", "sub_id", { "cache": ["membership_recover_contact_list", { "event_from_user": }] }] ``` **`membership_content_stats`** - Content backup stats ```json ["REQ", "sub_id", { "cache": ["membership_content_stats", { "event_from_user": }] }] ``` **`membership_content_backup`** - Backup content ```json ["REQ", "sub_id", { "cache": ["membership_content_backup", { "event_from_user": }] }] ``` **`membership_content_rebroadcast_start`** - Start rebroadcast ```json ["REQ", "sub_id", { "cache": ["membership_content_rebroadcast_start", { "event_from_user": }] }] ``` **`membership_content_rebroadcast_cancel`** - Cancel rebroadcast ```json ["REQ", "sub_id", { "cache": ["membership_content_rebroadcast_cancel", { "event_from_user": }] }] ``` **`membership_content_rebroadcast_status`** - Rebroadcast status ```json ["REQ", "sub_id", { "cache": ["membership_content_rebroadcast_status", { "event_from_user": }] }] ``` **`rebroadcasting_status`** - Get rebroadcast status (async) ```json ["REQ", "sub_id", { "cache": ["rebroadcasting_status", { "pubkey": "" }] }] ``` **`membership_legends_leaderboard`** - Legends leaderboard ```json ["REQ", "sub_id", { "cache": ["membership_legends_leaderboard"]}] }] ``` **`membership_premium_leaderboard`** - Premium leaderboard ```json ["REQ", "sub_id", { "cache": ["membership_premium_leaderboard"]}] }] ``` **`creator_paid_tiers`** - Creator subscription tiers ```json ["REQ", "sub_id", { "cache": ["creator_paid_tiers", { "pubkey": "" }] }] ``` #### Media Management **`upload`** - Upload media ```json ["REQ", "sub_id", { "cache": ["upload", { "event_from_user": }] }] ``` **`upload_chunk`** - Upload media chunk ```json ["REQ", "sub_id", { "cache": ["upload_chunk", { "event_from_user": }] }] ``` **`upload_complete`** - Complete chunked upload ```json ["REQ", "sub_id", { "cache": ["upload_complete", { "event_from_user": }] }] ``` **`upload_cancel`** - Cancel upload ```json ["REQ", "sub_id", { "cache": ["upload_cancel", { "event_from_user": }] }] ``` **`get_media_metadata`** - Get media metadata ```json ["REQ", "sub_id", { "cache": ["get_media_metadata", { "url": "" }] }] ``` **`get_recommended_blossom_servers`** - Blossom server recommendations ```json ["REQ", "sub_id", { "cache": ["get_recommended_blossom_servers"]}] }] ``` #### Utilities **`user_of_ln_address`** - Get pubkey from Lightning address ```json ["REQ", "sub_id", { "cache": ["user_of_ln_address", { "ln_address": "" }] }] ``` **`nip19_decode`** - Decode NIP-19 identifiers ```json ["REQ", "sub_id", { "cache": ["nip19_decode", { "identifier": "" }] }] ``` **`set_last_time_user_was_online`** - Update online status ```json ["REQ", "sub_id", { "cache": ["set_last_time_user_was_online", { "pubkey": "" }] }] ``` **`trusted_users`** - Get trusted users ```json ["REQ", "sub_id", { "cache": ["trusted_users", { "pubkey": "" }] }] ``` **`note_mentions`** - Get note mentions ```json ["REQ", "sub_id", { "cache": ["note_mentions", { "event_id": "" }] }] ``` **`note_mentions_count`** - Count note mentions ```json ["REQ", "sub_id", { "cache": ["note_mentions_count", { "event_id": "" }] }] ``` **`find_reposts`** - Find reposts of event ```json ["REQ", "sub_id", { "cache": ["find_reposts", { "event_id": "" }] }] ``` **`user_profile_scored_content`** - User's top content ```json ["REQ", "sub_id", { "cache": ["user_profile_scored_content", { "pubkey": "", "limit": 5, "user_pubkey": "" }] }] ``` **`user_profile_scored_media_thumbnails`** - User's top media ```json ["REQ", "sub_id", { "cache": ["user_profile_scored_media_thumbnails", { "pubkey": "", "limit": 5, "user_pubkey": "" }] }] ``` ### 2. `CLOSE` - Close Subscription Exactly as in NIP-01: ```json ["CLOSE", ] ``` 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 ```json ["EVENT", , ] ``` **`EOSE`** - End of stored events ```json ["EOSE", ] ``` **`OK`** - Event acceptance status ```json ["OK", , , ] ``` **`CLOSED`** - Subscription closed ```json ["CLOSED", , ] ``` **`NOTICE`** - Human-readable notice ```json ["NOTICE", ] ``` ### 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: ```json { "id": "", "pubkey": "", "created_at": , "kind": , "tags": [["tag_name", "value", ...]], "content": "", "sig": "" } ``` ### Custom Event Content Examples Below are real examples from Primal cache responses with actual data: **EVENT_STATS (kind 10000100)** - Engagement metrics for an event: ```json { "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: ```json { "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: ```json { "kind": 10000133, "content": "{\"0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd\":4959}" } ``` Maps pubkeys to their follower counts. **USER_SCORES (kind 10000108)** - User influence scores: ```json { "kind": 10000108, "content": "{\"0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd\":4959}" } ``` Maps pubkeys to their influence scores (typically based on followers). **RANGE (kind 10000113)** - Pagination metadata: ```json { "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: ```json { "kind": 10000125, "content": "true" } ``` Simple boolean string: `"true"` or `"false"`. **MEDIA_METADATA (kind 10000119)** - Media thumbnails and variants: ```json { "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: ```json { "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: ```json { "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: ```json { "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: ```json { "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: ```json { "kind": 10000158, "content": "{\"0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd\":\"alex\"}" } ``` Maps pubkeys to their verified Primal usernames (name@primal.net). **UPLOADED (kind 10000121)** - Media upload result: ```json { "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:** ```json ["REQ", "test_profile", { "cache": ["user_profile", { "pubkey": "0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd" }] }] ``` **Response:** ```json ["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\":\"alex@ditto.pub\",\"name\":\"Alex Gleason\",\"nip05\":\"alex@gleasonator.dev\",\"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:** ```json ["REQ", "test_infos", { "cache": ["user_infos", { "pubkeys": ["0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd"] }] }] ``` **Response:** ```json ["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:** ```json ["REQ", "test_feed", { "cache": ["feed", { "pubkey": "0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd", "notes": "authored", "limit": 5 }] }] ``` **Response (abbreviated):** ```json ["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:** ```json ["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:** ```json ["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:** ```json ["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:** ```json ["REQ", "test_trending", { "cache": ["explore_global_trending_24h", {}] }] ``` **Response (abbreviated):** ```json ["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:** ```json ["REQ", "test_likes", { "cache": ["event_actions", { "event_id": "df670a0c011959f432b63f0458202f3f985f3fd3765acc788a3276d7229d6850", "kind": 7, "limit": 20 }] }] ``` **Response (abbreviated):** ```json ["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):** ```json ["REQ", "test_scored", { "cache": ["scored", { "selector": "trending_24h" }] }] ``` **Request (mostzapped_4h):** ```json ["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:** ```json ["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:** ```json ["REQ", "test_is_following", { "cache": ["is_user_following", { "pubkey": "82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2", "user_pubkey": "0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd" }] }] ``` **Response:** ```json ["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:** ```json ["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:** ```json ["REQ", "test_default_relays", { "cache": ["get_default_relays"] }] ``` **Response:** ```json ["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:** ```json ["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:** ```json ["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:** ```json ["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:** ```json ["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:** ```json ["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:** ```json ["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:** ```json ["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:** ```json ["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:** ```json ["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 ```bash # 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) ```javascript 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: ```javascript // 1. Get complete profile ["REQ", "profile", {"cache": ["user_profile", {"pubkey": ""}]}] // 2. Get user's posts ["REQ", "posts", {"cache": ["feed", { "pubkey": "", "notes": "authored", "limit": 20 }]}] // 3. Get follower information ["REQ", "followers", {"cache": ["user_followers", { "pubkey": "", "limit": 100 }]}] ``` ### Building a Feed For a personalized feed showing posts from users someone follows: ```javascript ["REQ", "home_feed", {"cache": ["feed", { "pubkey": "", "notes": "follows", "limit": 50, "user_pubkey": "" // For personalization }]}] ``` ### Pagination Use `offset` and `limit` for pagination: ```javascript // Page 1 ["REQ", "page1", {"cache": ["feed", { "pubkey": "", "notes": "authored", "limit": 20, "offset": 0 }]}] // Page 2 ["REQ", "page2", {"cache": ["feed", { "pubkey": "", "notes": "authored", "limit": 20, "offset": 20 }]}] ``` The RANGE event (kind 10000113) in responses contains the actual range of data returned: ```json { "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: ```javascript const socket = new WebSocket('wss://cache.primal.net/v1'); socket.onopen = () => { // Subscribe to notifications socket.send(JSON.stringify([ "REQ", "notifications", {"cache": ["get_notifications", { "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: ```json ["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](https://github.com/nostr-protocol/nips/blob/master/01.md) - [Primal GitHub Repository](https://github.com/PrimalHQ/primal-server) - 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