OpportunitiesEntities
Docs
    Ring Platform

    Decentralized Self-building Future

    Sign In
    Entities
    Opportunities
    Store
    Docs
    Platform Concepts
    RING EconomySonoratek LLCGlobal ImpactAI Meets Web3
    Get Started
    Quick StartCalculatorRoadmap
    Privacy|Contact
    v1.104.25|Sonoratek LLC

    Documentation

    Concepts, value, and typical clone scenarios — less code.

    Welcome to Ring
    Quick Reference
    Getting Started
    Prerequisites
    Installation
    First Success Validation
    Next Steps
    Features
    Multi-Vendor Store
    Inventory & Stock
    Vendor Management
    Commissions & Settlements
    SubscriptionConductor
    PaymentConductor
    Ring Oracle
    Payments Overview
    Public Pools & DAO Jars
    WayForPay Payment Integration
    Wallet & Credit System
    WalletConductor
    Credit Rewards
    Affiliate & Referral Enablement
    Referral Codes (Refcodes)
    NFT Exhibition Marketplace
    Solana NFT Gates
    Token Staking System
    Owner Project Lab
    Entities
    Opportunities
    Real-Time Messaging
    Ring Tasks
    WebRTC Calls & STUNner TURN
    Peer Games
    News Module
    Member Blogs
    Public Profile Pages
    Profile Account Widgets
    Ring File Cabinet
    Username Reservation System
    Scientific Editor
    Notifications
    Push Notifications with FCM (Ring-Powered)
    Email AI-CRM
    Ring Mailer & RingdomX Mail
    Tunnel Protocol
    VideoConductor
    MediaConductor
    Generative Gallery
    Authentication
    Security & Compliance
    Admin console
    Admin Wiki
    Manage via Telegram
    Locale System
    Mobile Experience
    Performance Optimization Patterns
    Examples
    Quick Start
    Basic Setup
    White Label
    Custom Branding
    Web3 Integration
    Real World
    Advanced Features
    Customization
    Quick Start — Your First Ring Clone
    Customization Guide
    Vertical Presets (SSOT)
    Ringization playbook
    Branding
    Themes
    Features
    Localization
    Token Economics Setup
    Payment Gateway Integration
    Reference Ring deployments
    Project configuration
    Public environment variables
    Order Lab secrets
    WalletConnect Project ID (Reown Cloud)
    Supported services
    NODUS wiki (project knowledge)
    Configuration playbook
    Web3
    Token launch jurisdictions
    Wallet
    Wallet Security Tips
    Integrations
    Ethereum wallets (Wagmi v3)
    RingFileBase (object storage API)
    Ring CDN (RingFileBase edge)
    Deployment
    Self-hosted deployment
    Vercel Deployment
    Docker
    Environment Configuration
    Monitoring & Analytics
    Performance Optimization
    Backup & Recovery
    Architecture
    Data Model
    Security
    Real Time
    Discovery Mutation Sync
    PaymentConductor architecture
    WalletConductor architecture
    News Kingdom architecture
    Backend Services
    Firebase Integration
    Development
    Ring MCP Server
    OSS vs enterprise

    Quick entry (CTOs · auditors · agents)

    Welcome — mission & audiences
    Quick Reference
    Getting started
    Architecture & Auth.js
    Backend modes & databases (DB_BACKEND_MODE)
    Self-hosted
    Ring MCP Tools
    Ring MCP Server
    Token economics
    Token launch jurisdictions
    Deploy (Docker · k8s)
    Security & compliance reads
    ringdom.org — LegioX homebase
    Source — MIT license (GitHub)

    Documentation

    Concepts, value, and typical clone scenarios — less code.

    Welcome to Ring
    Quick Reference
    Getting Started
    Prerequisites
    Installation
    First Success Validation
    Next Steps
    Features
    Multi-Vendor Store
    Inventory & Stock
    Vendor Management
    Commissions & Settlements
    SubscriptionConductor
    PaymentConductor
    Ring Oracle
    Payments Overview
    Public Pools & DAO Jars
    WayForPay Payment Integration
    Wallet & Credit System
    WalletConductor
    Credit Rewards
    Affiliate & Referral Enablement
    Referral Codes (Refcodes)
    NFT Exhibition Marketplace
    Solana NFT Gates
    Token Staking System
    Owner Project Lab
    Entities
    Opportunities
    Real-Time Messaging
    Ring Tasks
    WebRTC Calls & STUNner TURN
    Peer Games
    News Module
    Member Blogs
    Public Profile Pages
    Profile Account Widgets
    Ring File Cabinet
    Username Reservation System
    Scientific Editor
    Notifications
    Push Notifications with FCM (Ring-Powered)
    Email AI-CRM
    Ring Mailer & RingdomX Mail
    Tunnel Protocol
    VideoConductor
    MediaConductor
    Generative Gallery
    Authentication
    Security & Compliance
    Admin console
    Admin Wiki
    Manage via Telegram
    Locale System
    Mobile Experience
    Performance Optimization Patterns
    Examples
    Quick Start
    Basic Setup
    White Label
    Custom Branding
    Web3 Integration
    Real World
    Advanced Features
    Customization
    Quick Start — Your First Ring Clone
    Customization Guide
    Vertical Presets (SSOT)
    Ringization playbook
    Branding
    Themes
    Features
    Localization
    Token Economics Setup
    Payment Gateway Integration
    Reference Ring deployments
    Project configuration
    Public environment variables
    Order Lab secrets
    WalletConnect Project ID (Reown Cloud)
    Supported services
    NODUS wiki (project knowledge)
    Configuration playbook
    Web3
    Token launch jurisdictions
    Wallet
    Wallet Security Tips
    Integrations
    Ethereum wallets (Wagmi v3)
    RingFileBase (object storage API)
    Ring CDN (RingFileBase edge)
    Deployment
    Self-hosted deployment
    Vercel Deployment
    Docker
    Environment Configuration
    Monitoring & Analytics
    Performance Optimization
    Backup & Recovery
    Architecture
    Data Model
    Security
    Real Time
    Discovery Mutation Sync
    PaymentConductor architecture
    WalletConductor architecture
    News Kingdom architecture
    Backend Services
    Firebase Integration
    Development
    Ring MCP Server
    OSS vs enterprise

    Quick entry (CTOs · auditors · agents)

    Welcome — mission & audiences
    Quick Reference
    Getting started
    Architecture & Auth.js
    Backend modes & databases (DB_BACKEND_MODE)
    Self-hosted
    Ring MCP Tools
    Ring MCP Server
    Token economics
    Token launch jurisdictions
    Deploy (Docker · k8s)
    Security & compliance reads
    ringdom.org — LegioX homebase
    Source — MIT license (GitHub)

    Documentation

    Concepts, value, and typical clone scenarios — less code.

    Welcome to Ring
    Quick Reference
    Getting Started
    Prerequisites
    Installation
    First Success Validation
    Next Steps
    Features
    Multi-Vendor Store
    Inventory & Stock
    Vendor Management
    Commissions & Settlements
    SubscriptionConductor
    PaymentConductor
    Ring Oracle
    Payments Overview
    Public Pools & DAO Jars
    WayForPay Payment Integration
    Wallet & Credit System
    WalletConductor
    Credit Rewards
    Affiliate & Referral Enablement
    Referral Codes (Refcodes)
    NFT Exhibition Marketplace
    Solana NFT Gates
    Token Staking System
    Owner Project Lab
    Entities
    Opportunities
    Real-Time Messaging
    Ring Tasks
    WebRTC Calls & STUNner TURN
    Peer Games
    News Module
    Member Blogs
    Public Profile Pages
    Profile Account Widgets
    Ring File Cabinet
    Username Reservation System
    Scientific Editor
    Notifications
    Push Notifications with FCM (Ring-Powered)
    Email AI-CRM
    Ring Mailer & RingdomX Mail
    Tunnel Protocol
    VideoConductor
    MediaConductor
    Generative Gallery
    Authentication
    Security & Compliance
    Admin console
    Admin Wiki
    Manage via Telegram
    Locale System
    Mobile Experience
    Performance Optimization Patterns
    Examples
    Quick Start
    Basic Setup
    White Label
    Custom Branding
    Web3 Integration
    Real World
    Advanced Features
    Customization
    Quick Start — Your First Ring Clone
    Customization Guide
    Vertical Presets (SSOT)
    Ringization playbook
    Branding
    Themes
    Features
    Localization
    Token Economics Setup
    Payment Gateway Integration
    Reference Ring deployments
    Project configuration
    Public environment variables
    Order Lab secrets
    WalletConnect Project ID (Reown Cloud)
    Supported services
    NODUS wiki (project knowledge)
    Configuration playbook
    Web3
    Token launch jurisdictions
    Wallet
    Wallet Security Tips
    Integrations
    Ethereum wallets (Wagmi v3)
    RingFileBase (object storage API)
    Ring CDN (RingFileBase edge)
    Deployment
    Self-hosted deployment
    Vercel Deployment
    Docker
    Environment Configuration
    Monitoring & Analytics
    Performance Optimization
    Backup & Recovery
    Architecture
    Data Model
    Security
    Real Time
    Discovery Mutation Sync
    PaymentConductor architecture
    WalletConductor architecture
    News Kingdom architecture
    Backend Services
    Firebase Integration
    Development
    Ring MCP Server
    OSS vs enterprise

    Quick entry (CTOs · auditors · agents)

    Welcome — mission & audiences
    Quick Reference
    Getting started
    Architecture & Auth.js
    Backend modes & databases (DB_BACKEND_MODE)
    Self-hosted
    Ring MCP Tools
    Ring MCP Server
    Token economics
    Token launch jurisdictions
    Deploy (Docker · k8s)
    Security & compliance reads
    ringdom.org — LegioX homebase
    Source — MIT license (GitHub)
    Ring Logo

    Loading documentation...

    Preparing Ring content

    Ring Logo

    Loading documentation...

    Preparing Ring content

    Ring Logo

    Loading documentation...

    Preparing Ring content

    Opportunities

    Ring Opportunities is the supply ↔ demand loop for each clone: members post offers and requests (plus specialized types), Matcher scores interested users, and notifications invite them to connect — without waiting for someone to search the feed.

    The browse list at /opportunities is a named feed: each card shows a public-safe poster, human tags, and Like / Save / Hide / Contact. Live inserts still arrive over Tunnel; the browse pane no longer shows a websocket status bar.

    Use Founder / Developer tabs in the docs sidebar to filter this page. audience frontmatter controls in-page blocks; sidebar visibility is controlled by lib/docs/audience-curated-docs.ts.

    Marketing KPIs are not product telemetry

    Older copy claimed fixed accuracy %, hire-cost savings, and “15k+ users.” Those figures are deprecated. Live numbers come from Admin → Matcher analytics and clone traffic. Matcher thresholds are config (ring-config.json → matcher, overridable in platform settings) — not kingdom-wide SLAs.

    Traditional boards vs Ring

    PreviousRing equivalent
    Post job → hope someone searchesPost opportunity → Matcher scores candidates → notify with short explanations
    Offers only (employer → talent)Dual-nature: offer and request plus specialized types
    One listing taxonomyWhite-label opportunities.enabledTypes gate (16 defaults in Layer1 / org config)
    Manual enrichmentOptional LLM auto-fill on create (OpportunityAutoFillService)
    Opaque rankingEight scored factors (MatchFactors in lib/ai/types.ts) + configurable scoreThreshold
    Creator UUID on cardsPublic-safe name / avatar; username only when publicProfile is true
    sourceMessageId: chipsHidden machine tags; owner-only From chat when provenance exists
    “Live Updates Active / via websocket” on browseSilent useRealtimeOpportunities; the status bar remains on detail only
    Separate funding jarsCollective / builder jars via Public Pools (donation path live; on-chain escrow gated)

    Matching loop (shared)

    Opportunity create → match → notify

    Default Matcher install values (ring-config.json → matcher): scoreThreshold 0.7, maxMatches 10, autoApprove false, autoApproveMinScore 0.7, llmConfidenceGate 0.8. Env can override auto-approve via MATCHER_AUTO_APPROVE / MATCHER_AUTO_APPROVE_MIN_SCORE (see features/admin/platform-settings/matcher-config.ts). Cap matches with MAX_MATCHES_PER_OPPORTUNITY.

    Scoring factors, LLM vs heuristic paths, and explanation length live on AI Matcher. Thresholds above are the install defaults — not a published accuracy KPI.

    Why this matters for your clone

    Opportunities turn your Ring into a local marketplace of intent: people publish what they can provide and what they need. Matcher pushes relevant invitations instead of relying on feed scroll. Specialized types (jobs, collective orders, scheduled services, program/investment → CRM, Ring customization quests) are gated per clone so you ship only the loops your vertical needs.

    Entities

    Link opportunities to verified organizations via contactInfo.linkedEntity / organizationId.

    Notifications

    Match invites are real notifications — not marketing email blasts.

    Messaging

    Contact opens /messages?user=<createdBy>. Chat-task conversions keep a From chat deep link for the owner.

    Implementation map

    ConcernVerified path
    Types / SerializedOpportunityfeatures/opportunities/types/index.ts (creator?, viewer?, likes?)
    Enabled-type gatefeatures/opportunities/lib/opportunity-enabled-types.ts ← ring-config.json opportunities.enabledTypes
    Role create matrixfeatures/opportunities/lib/opportunity-permissions.ts (MEMBER_OFFER_TYPES, REQUEST_TYPES, SUBSCRIBER_TYPES, confidential)
    Create + AI pipelinefeatures/opportunities/services/create-opportunity.ts
    List / searchget-opportunities.ts, search-opportunities.ts — hidden exclusion + attachOpportunityFeedFields
    Creator summariesget-opportunity-creators.ts (name / displayName / username-if-public / avatar)

    Recommended path

    Use the ring-ai-matcher-specialist (or Cursor skill of the same family) with a pause-for-approval prompt:

    “Audit this clone’s Opportunities loop: list opportunities.enabledTypes and matcher from ring-config.json, confirm LLM env is set, dry-run create of one offer and one request, report MatchFactors and whether autoApprove would promote pending→active. Open /opportunities as two users and confirm cards show names (not UUIDs), Hide excludes on reload, and Contact lands on /messages?user=. Do not change platform_settings until I approve.”

    Inputs checklist: clone key, admin access, LLM credentials present, sample entity id for organizationId.

    Manual path

    1. 1

      Set opportunities.enabledTypes and matcher.* in ring-config.json; redeploy or reload config snapshot as your clone does for ring-config.

    2. 2

      In Admin → Matcher / AI settings, leave auto-approve off; set scoreThreshold / maxMatches to match your vertical tolerance.

    3. 3

      Create a test opportunity from the type selector UI (or POST /api/opportunities); open My Opportunities at /opportunities/my.

    4. 4

      Confirm a matched user received a notification and that discovery lists refresh (Tunnel / soft refresh) without inventing a search-index reindex step. Confirm the browse list has no websocket status bar.

    Frequently asked questions

    Impact

    Will Matcher auto-publish every new listing?

    No. Default matcher.autoApprove is false. When enabled, maybeAutoApproveOpportunity still requires score and LLM confidence gates (autoApproveMinScore, llmConfidenceGate).

    Do I get a kingdom-wide “95% match accuracy”?

    No. Treat historical marketing percentages as deprecated. Use Admin Matcher analytics for your tenant.

    Do posters stay anonymous on the feed?

    No. Authenticated viewers see name/avatar. Username is included only when publicProfile is true. There is no postAnonymously flag.

    Migration

    We still have Firestore converters — are they required?

    Only when DB_BACKEND_MODE=firebase-full. PostgreSQL clones use opportunity-db-mapper.ts and SerializedOpportunity.

    Where did /opportunities/my-opportunities go?

    Removed. Canonical dashboard is /opportunities/my via ROUTES.MY_OPPORTUNITIES.

    Why did sourceMessageId: chips disappear?

    They were machine provenance, not user tags. New conversions write metadata.source; the card hides legacy encoded tags and shows From chat to the owner when a conversation id exists.

    Ops

    Why did nobody get notified?

    Check LLM availability, Matcher score threshold, maxMatches, and entity block filters in matcher-notification-filter. Notifications go through features/notifications/services/notification-service.

    Why does Hide come back after I log in as someone else?

    The cursor-feed cache is keyed per viewer. Hide is stored on user_content_interactions for that user; the next getOpportunitiesForRole with that viewerUserId applies not-in.

    How do collective-order money and jars relate?

    Opportunity specializations can carry JSONB metadata (e.g. collective order slots). Community funding jars and builder payout SSOT are documented under Public Pools — do not invent PSP “donation product” APIs as the jar path.

    Related documentation

    Related documentation

    Opportunities API

    Next-step: REST route contract, list enrichment fields, and search vs browse pagination.

    Discovery Mutation Sync

    Depends-on: how syncOpportunityDiscovery invalidates cache and publishes Tunnel events.

    Ring Tasks

    Same-workflow: convertTaskToOpportunity writes metadata.source for the From chat chip.

    Real-Time Messaging

    Same-workflow: Contact uses /messages?user=; From chat uses /messages?c=.

    1. Docs
    2. /Features
    3. /Opportunities

    Updated Sep 6, 202610 min listen

    Opportunities

    Ring Opportunities is the supply ↔ demand loop for each clone: members post offers and requests (plus specialized types), Matcher scores interested users, and notifications invite them to connect — without waiting for someone to search the feed.

    The browse list at /opportunities is a named feed: each card shows a public-safe poster, human tags, and Like / Save / Hide / Contact. Live inserts still arrive over Tunnel; the browse pane no longer shows a websocket status bar.

    Use Founder / Developer tabs in the docs sidebar to filter this page. audience frontmatter controls in-page blocks; sidebar visibility is controlled by lib/docs/audience-curated-docs.ts.

    Marketing KPIs are not product telemetry

    Older copy claimed fixed accuracy %, hire-cost savings, and “15k+ users.” Those figures are deprecated. Live numbers come from Admin → Matcher analytics and clone traffic. Matcher thresholds are config (ring-config.json → matcher, overridable in platform settings) — not kingdom-wide SLAs.

    Traditional boards vs Ring

    PreviousRing equivalent
    Post job → hope someone searchesPost opportunity → Matcher scores candidates → notify with short explanations
    Offers only (employer → talent)Dual-nature: offer and request plus specialized types
    One listing taxonomyWhite-label opportunities.enabledTypes gate (16 defaults in Layer1 / org config)
    Manual enrichmentOptional LLM auto-fill on create (OpportunityAutoFillService)
    Opaque rankingEight scored factors (MatchFactors in lib/ai/types.ts) + configurable scoreThreshold
    Creator UUID on cardsPublic-safe name / avatar; username only when publicProfile is true
    sourceMessageId: chipsHidden machine tags; owner-only From chat when provenance exists
    “Live Updates Active / via websocket” on browseSilent useRealtimeOpportunities; the status bar remains on detail only
    Separate funding jarsCollective / builder jars via Public Pools (donation path live; on-chain escrow gated)

    Matching loop (shared)

    Opportunity create → match → notify

    Default Matcher install values (ring-config.json → matcher): scoreThreshold 0.7, maxMatches 10, autoApprove false, autoApproveMinScore 0.7, llmConfidenceGate 0.8. Env can override auto-approve via MATCHER_AUTO_APPROVE / MATCHER_AUTO_APPROVE_MIN_SCORE (see features/admin/platform-settings/matcher-config.ts). Cap matches with MAX_MATCHES_PER_OPPORTUNITY.

    Scoring factors, LLM vs heuristic paths, and explanation length live on AI Matcher. Thresholds above are the install defaults — not a published accuracy KPI.

    Why this matters for your clone

    Opportunities turn your Ring into a local marketplace of intent: people publish what they can provide and what they need. Matcher pushes relevant invitations instead of relying on feed scroll. Specialized types (jobs, collective orders, scheduled services, program/investment → CRM, Ring customization quests) are gated per clone so you ship only the loops your vertical needs.

    Entities

    Link opportunities to verified organizations via contactInfo.linkedEntity / organizationId.

    Notifications

    Match invites are real notifications — not marketing email blasts.

    Messaging

    Contact opens /messages?user=<createdBy>. Chat-task conversions keep a From chat deep link for the owner.

    Implementation map

    ConcernVerified path
    Types / SerializedOpportunityfeatures/opportunities/types/index.ts (creator?, viewer?, likes?)
    Enabled-type gatefeatures/opportunities/lib/opportunity-enabled-types.ts ← ring-config.json opportunities.enabledTypes
    Role create matrixfeatures/opportunities/lib/opportunity-permissions.ts (MEMBER_OFFER_TYPES, REQUEST_TYPES, SUBSCRIBER_TYPES, confidential)
    Create + AI pipelinefeatures/opportunities/services/create-opportunity.ts
    List / searchget-opportunities.ts, search-opportunities.ts — hidden exclusion + attachOpportunityFeedFields
    Creator summariesget-opportunity-creators.ts (name / displayName / username-if-public / avatar)

    Recommended path

    Use the ring-ai-matcher-specialist (or Cursor skill of the same family) with a pause-for-approval prompt:

    “Audit this clone’s Opportunities loop: list opportunities.enabledTypes and matcher from ring-config.json, confirm LLM env is set, dry-run create of one offer and one request, report MatchFactors and whether autoApprove would promote pending→active. Open /opportunities as two users and confirm cards show names (not UUIDs), Hide excludes on reload, and Contact lands on /messages?user=. Do not change platform_settings until I approve.”

    Inputs checklist: clone key, admin access, LLM credentials present, sample entity id for organizationId.

    Manual path

    1. 1

      Set opportunities.enabledTypes and matcher.* in ring-config.json; redeploy or reload config snapshot as your clone does for ring-config.

    2. 2

      In Admin → Matcher / AI settings, leave auto-approve off; set scoreThreshold / maxMatches to match your vertical tolerance.

    3. 3

      Create a test opportunity from the type selector UI (or POST /api/opportunities); open My Opportunities at /opportunities/my.

    4. 4

      Confirm a matched user received a notification and that discovery lists refresh (Tunnel / soft refresh) without inventing a search-index reindex step. Confirm the browse list has no websocket status bar.

    Frequently asked questions

    Impact

    Will Matcher auto-publish every new listing?

    No. Default matcher.autoApprove is false. When enabled, maybeAutoApproveOpportunity still requires score and LLM confidence gates (autoApproveMinScore, llmConfidenceGate).

    Do I get a kingdom-wide “95% match accuracy”?

    No. Treat historical marketing percentages as deprecated. Use Admin Matcher analytics for your tenant.

    Do posters stay anonymous on the feed?

    No. Authenticated viewers see name/avatar. Username is included only when publicProfile is true. There is no postAnonymously flag.

    Migration

    We still have Firestore converters — are they required?

    Only when DB_BACKEND_MODE=firebase-full. PostgreSQL clones use opportunity-db-mapper.ts and SerializedOpportunity.

    Where did /opportunities/my-opportunities go?

    Removed. Canonical dashboard is /opportunities/my via ROUTES.MY_OPPORTUNITIES.

    Why did sourceMessageId: chips disappear?

    They were machine provenance, not user tags. New conversions write metadata.source; the card hides legacy encoded tags and shows From chat to the owner when a conversation id exists.

    Ops

    Why did nobody get notified?

    Check LLM availability, Matcher score threshold, maxMatches, and entity block filters in matcher-notification-filter. Notifications go through features/notifications/services/notification-service.

    Why does Hide come back after I log in as someone else?

    The cursor-feed cache is keyed per viewer. Hide is stored on user_content_interactions for that user; the next getOpportunitiesForRole with that viewerUserId applies not-in.

    How do collective-order money and jars relate?

    Opportunity specializations can carry JSONB metadata (e.g. collective order slots). Community funding jars and builder payout SSOT are documented under Public Pools — do not invent PSP “donation product” APIs as the jar path.

    Related documentation

    Related documentation

    Opportunities API

    Next-step: REST route contract, list enrichment fields, and search vs browse pagination.

    Discovery Mutation Sync

    Depends-on: how syncOpportunityDiscovery invalidates cache and publishes Tunnel events.

    Ring Tasks

    Same-workflow: convertTaskToOpportunity writes metadata.source for the From chat chip.

    Real-Time Messaging

    Same-workflow: Contact uses /messages?user=; From chat uses /messages?c=.

    1. Docs
    2. /Features
    3. /Opportunities

    Updated Sep 6, 202610 min listen

    Opportunities

    Ring Opportunities is the supply ↔ demand loop for each clone: members post offers and requests (plus specialized types), Matcher scores interested users, and notifications invite them to connect — without waiting for someone to search the feed.

    The browse list at /opportunities is a named feed: each card shows a public-safe poster, human tags, and Like / Save / Hide / Contact. Live inserts still arrive over Tunnel; the browse pane no longer shows a websocket status bar.

    Use Founder / Developer tabs in the docs sidebar to filter this page. audience frontmatter controls in-page blocks; sidebar visibility is controlled by lib/docs/audience-curated-docs.ts.

    Marketing KPIs are not product telemetry

    Older copy claimed fixed accuracy %, hire-cost savings, and “15k+ users.” Those figures are deprecated. Live numbers come from Admin → Matcher analytics and clone traffic. Matcher thresholds are config (ring-config.json → matcher, overridable in platform settings) — not kingdom-wide SLAs.

    Traditional boards vs Ring

    PreviousRing equivalent
    Post job → hope someone searchesPost opportunity → Matcher scores candidates → notify with short explanations
    Offers only (employer → talent)Dual-nature: offer and request plus specialized types
    One listing taxonomyWhite-label opportunities.enabledTypes gate (16 defaults in Layer1 / org config)
    Manual enrichmentOptional LLM auto-fill on create (OpportunityAutoFillService)
    Opaque rankingEight scored factors (MatchFactors in lib/ai/types.ts) + configurable scoreThreshold
    Creator UUID on cardsPublic-safe name / avatar; username only when publicProfile is true
    sourceMessageId: chipsHidden machine tags; owner-only From chat when provenance exists
    “Live Updates Active / via websocket” on browseSilent useRealtimeOpportunities; the status bar remains on detail only
    Separate funding jarsCollective / builder jars via Public Pools (donation path live; on-chain escrow gated)

    Matching loop (shared)

    Opportunity create → match → notify

    Default Matcher install values (ring-config.json → matcher): scoreThreshold 0.7, maxMatches 10, autoApprove false, autoApproveMinScore 0.7, llmConfidenceGate 0.8. Env can override auto-approve via MATCHER_AUTO_APPROVE / MATCHER_AUTO_APPROVE_MIN_SCORE (see features/admin/platform-settings/matcher-config.ts). Cap matches with MAX_MATCHES_PER_OPPORTUNITY.

    Scoring factors, LLM vs heuristic paths, and explanation length live on AI Matcher. Thresholds above are the install defaults — not a published accuracy KPI.

    Why this matters for your clone

    Opportunities turn your Ring into a local marketplace of intent: people publish what they can provide and what they need. Matcher pushes relevant invitations instead of relying on feed scroll. Specialized types (jobs, collective orders, scheduled services, program/investment → CRM, Ring customization quests) are gated per clone so you ship only the loops your vertical needs.

    Entities

    Link opportunities to verified organizations via contactInfo.linkedEntity / organizationId.

    Notifications

    Match invites are real notifications — not marketing email blasts.

    Messaging

    Contact opens /messages?user=<createdBy>. Chat-task conversions keep a From chat deep link for the owner.

    Implementation map

    ConcernVerified path
    Types / SerializedOpportunityfeatures/opportunities/types/index.ts (creator?, viewer?, likes?)
    Enabled-type gatefeatures/opportunities/lib/opportunity-enabled-types.ts ← ring-config.json opportunities.enabledTypes
    Role create matrixfeatures/opportunities/lib/opportunity-permissions.ts (MEMBER_OFFER_TYPES, REQUEST_TYPES, SUBSCRIBER_TYPES, confidential)
    Create + AI pipelinefeatures/opportunities/services/create-opportunity.ts
    List / searchget-opportunities.ts, search-opportunities.ts — hidden exclusion + attachOpportunityFeedFields
    Creator summariesget-opportunity-creators.ts (name / displayName / username-if-public / avatar)

    Recommended path

    Use the ring-ai-matcher-specialist (or Cursor skill of the same family) with a pause-for-approval prompt:

    “Audit this clone’s Opportunities loop: list opportunities.enabledTypes and matcher from ring-config.json, confirm LLM env is set, dry-run create of one offer and one request, report MatchFactors and whether autoApprove would promote pending→active. Open /opportunities as two users and confirm cards show names (not UUIDs), Hide excludes on reload, and Contact lands on /messages?user=. Do not change platform_settings until I approve.”

    Inputs checklist: clone key, admin access, LLM credentials present, sample entity id for organizationId.

    Manual path

    1. 1

      Set opportunities.enabledTypes and matcher.* in ring-config.json; redeploy or reload config snapshot as your clone does for ring-config.

    2. 2

      In Admin → Matcher / AI settings, leave auto-approve off; set scoreThreshold / maxMatches to match your vertical tolerance.

    3. 3

      Create a test opportunity from the type selector UI (or POST /api/opportunities); open My Opportunities at /opportunities/my.

    4. 4

      Confirm a matched user received a notification and that discovery lists refresh (Tunnel / soft refresh) without inventing a search-index reindex step. Confirm the browse list has no websocket status bar.

    Frequently asked questions

    Impact

    Will Matcher auto-publish every new listing?

    No. Default matcher.autoApprove is false. When enabled, maybeAutoApproveOpportunity still requires score and LLM confidence gates (autoApproveMinScore, llmConfidenceGate).

    Do I get a kingdom-wide “95% match accuracy”?

    No. Treat historical marketing percentages as deprecated. Use Admin Matcher analytics for your tenant.

    Do posters stay anonymous on the feed?

    No. Authenticated viewers see name/avatar. Username is included only when publicProfile is true. There is no postAnonymously flag.

    Migration

    We still have Firestore converters — are they required?

    Only when DB_BACKEND_MODE=firebase-full. PostgreSQL clones use opportunity-db-mapper.ts and SerializedOpportunity.

    Where did /opportunities/my-opportunities go?

    Removed. Canonical dashboard is /opportunities/my via ROUTES.MY_OPPORTUNITIES.

    Why did sourceMessageId: chips disappear?

    They were machine provenance, not user tags. New conversions write metadata.source; the card hides legacy encoded tags and shows From chat to the owner when a conversation id exists.

    Ops

    Why did nobody get notified?

    Check LLM availability, Matcher score threshold, maxMatches, and entity block filters in matcher-notification-filter. Notifications go through features/notifications/services/notification-service.

    Why does Hide come back after I log in as someone else?

    The cursor-feed cache is keyed per viewer. Hide is stored on user_content_interactions for that user; the next getOpportunitiesForRole with that viewerUserId applies not-in.

    How do collective-order money and jars relate?

    Opportunity specializations can carry JSONB metadata (e.g. collective order slots). Community funding jars and builder payout SSOT are documented under Public Pools — do not invent PSP “donation product” APIs as the jar path.

    Related documentation

    Related documentation

    Opportunities API

    Next-step: REST route contract, list enrichment fields, and search vs browse pagination.

    Discovery Mutation Sync

    Depends-on: how syncOpportunityDiscovery invalidates cache and publishes Tunnel events.

    Ring Tasks

    Same-workflow: convertTaskToOpportunity writes metadata.source for the From chat chip.

    Real-Time Messaging

    Same-workflow: Contact uses /messages?user=; From chat uses /messages?c=.

    1. Docs
    2. /Features
    3. /Opportunities

    Updated Sep 6, 202610 min listen

    Public Pools

    When a related jar fills, builders receive net native token (role fee from publicPools.platformFeePercentByRole).

    Browse feed (/opportunities)

    Authenticated members see compact cards: type chip, title, two-line description, poster byline, relative posted time, human tags, like count, and icon actions.

    ActionWhoWhat happens
    LikeSigned-in viewerToggles the likes collection; heart fills from opportunity.viewer.liked
    SaveSigned-in viewerToggles user_content_interactions (save); hydrates from viewer.saved
    HideSigned-in viewerMarks not_interested; in-session Hidden · Undo; next list query excludes the id
    ContactSubscriber+; hidden on own postsNavigates immediately to /messages?user=<createdBy>; Matcher contact_intent is best-effort and must not gate navigation
    From chatOwner only, when the listing came from a taskOpens /messages?c=<conversationId>

    There is no anonymous-posting flag. Authenticated viewers see the poster name/avatar. Username appears only on public profiles. Empty name renders Member vs Private User.

    Right-rail query params (q, types, location, …) filter the current page of cards in the browser. GET /api/opportunities still paginates with limit / startAfter only — it is not a full-text search endpoint. Use Opportunities API search for q=.

    My Opportunities at /opportunities/my is the owner dashboard (Edit / Delete / View). Saved and Applied tab counts are hardcoded 0 today — do not treat them as live metrics.

    Typical clone scenarios

    1. Talent / partnership ring — enable job, offer, request, partnership; keep Matcher auto-approve off until you trust scoring.
    2. Commerce vertical — enable collective_order, scheduled_services, asset_rental, bounty, tender with Wallet / PaymentConductor rails.
    3. Ringdom settler / builder ring — enable ring_customization and program so customization quests and institution programs land in CRM.

    Operator checklist

    1. Confirm feature flag / whitelabel includes opportunities.
    2. Set opportunities.enabledTypes in ring-config.json (do not assume every OpportunityType union member is UI-visible).
    3. Configure LLM (LLM_PROVIDER, OPENAI_API_KEY or ANTHROPIC_API_KEY, LLM_MODEL) before expecting auto-fill / LLM explanations.
    4. Tune Matcher in Admin → Matcher / Platform Settings → AI → Matcher (threshold, max matches, auto-approve).
    5. Teach members My Opportunities at /opportunities/my (ROUTES.MY_OPPORTUNITIES) — legacy /opportunities/my-opportunities is removed.
    6. Confirm browse cards show names (not UUIDs) and that Hide survives a refresh for that signed-in user.
    Viewer like / save / hide
    get-viewer-interactions.ts ← likes + user_content_interactions
    Tag / chat provenancelib/opportunity-tags.ts (splitOpportunityTags)
    Feed card / listopportunity-feed-card.tsx, opportunity-list.tsx
    Interactionsapp/_actions/opportunity-interactions.ts
    Matcher corelib/ai/matcher.ts + OpportunityMatchingService
    Auto-fillfeatures/opportunities/services/auto-fill-service.ts
    Auto-approvefeatures/opportunities/services/auto-approval-service.ts
    PG JSONB ↔ clientfeatures/opportunities/lib/opportunity-db-mapper.ts
    Post-mutation syncfeatures/opportunities/lib/opportunity-mutation-sync.ts → syncOpportunityDiscovery
    Program → CRMfeatures/opportunities/lib/program-crm-ingest.ts
    UI Server Actionsapp/_actions/opportunities.ts
    Firestore legacylib/converters/opportunity-converter.ts when DB_BACKEND_MODE=firebase-full only

    Database: default production clones use DB_BACKEND_MODE=k8s-postgres-fcm (also supabase-fcm). Services return SerializedOpportunity (ISO date strings). Do not invent DATABASE_MODE=firebase_only.

    Browse list (verified)

    1. 1

      (protected)/opportunities/page.tsx and GET /api/opportunities pass viewerUserId: session.user.id into getOpportunitiesForRole. Hidden ids are loaded first, then the query uses DatabaseService operator not-in on opportunities.id. PostgreSQLAdapter maps opportunities / likes id to the SQL column (not data->>'id').

    2. 2

      attachOpportunityFeedFields adds creator, viewer, and likes. Cards hydrate useOptimistic from opportunity.viewer (the previous constant-base optimistic state never showed saved/liked). After Like / Save / Hide, onInteractionChange patches the cursor-feed item so session flags stay consistent.

    3. 3

      useCursorFeed persists 24h in localStorage. The list fingerprint is …|viewer=<userId> so one account cannot inherit another’s like/save/hide. Reloads overlay server initialItems onto cached rows by id (hooks/use-cursor-feed.ts).

    4. 4

      Browse useRealtimeOpportunities({ autoConnect: true }) stays for silent tunnel subscribe. Create snippets include creator { id, name, avatar } so live-inserted cards are not “Private User”. Detail (opportunity-details.tsx) still renders the Live Updates bar.

    convertTaskToOpportunity writes metadata.source: { kind: 'chat_task', messageId, conversationId } and tags: []. splitOpportunityTags still parses legacy sourceMessageId: / sourceConversationId: / chat_task tags at render. There is no backfill job.

    Visibility & status

    • Visibility: public | subscriber | member | confidential
    • Status: draft | pending | active | closed | expired | archived
    • Confidential create/edit requires confidential-capable roles (canCreateOpportunityConfidential / canEditOpportunity)

    Role gates (create)

    BucketTypes (examples)Minimum role
    Member offersoffer, job, partnership, volunteer, mentorship, resource, event, ring_customization, program, collective_order, tender, asset_rentalmember privileges
    Requestsrequestsubscriber+
    Subscriber specialscv, scheduled_services, bountysubscriber+
    Other union memberse.g. future-facing types not in enabled/permission setsadmin+ (and must be enabled)

    Route contracts, create body, and list JSON live on Opportunities API. Like / Save / Hide / Contact are Server Actions (app/_actions/opportunity-interactions.ts), not REST. MCP GET /api/mcp/v1/opportunities currently does not pass viewerUserId.

    After mutations, syncOpportunityDiscovery invalidates cache tags, revalidatePath for list/detail/my, and publishes Tunnel opportunity:*. That does not bust client useCursorFeed — overlay + viewer fingerprint are the browse freshness path.

    LLM / Matcher env (template)

    Keys documented in env.local.template: LLM_PROVIDER, OPENAI_API_KEY, ANTHROPIC_API_KEY, LLM_MODEL, MAX_MATCHES_PER_OPPORTUNITY, MATCHING_MAX_TOKENS. Without a working provider, Matcher falls back to non-LLM / tag-style explanations with lower confidence.

    Entities

    Same-workflow: organization linkage and verified posters for opportunities.

    Notifications

    Same-workflow: match invitations are delivered as platform notifications.

    Public Pools & DAO Jars

    See-also: collective / builder jar payouts tied to opportunity owners.

    AI Matcher

    Deep-dive: MatchFactors, LLM vs heuristic scoring, and explanation length.

    Admin console

    Deep-dive: Admin Matcher tab and platform AI settings for thresholds.

    Public Pools

    When a related jar fills, builders receive net native token (role fee from publicPools.platformFeePercentByRole).

    Browse feed (/opportunities)

    Authenticated members see compact cards: type chip, title, two-line description, poster byline, relative posted time, human tags, like count, and icon actions.

    ActionWhoWhat happens
    LikeSigned-in viewerToggles the likes collection; heart fills from opportunity.viewer.liked
    SaveSigned-in viewerToggles user_content_interactions (save); hydrates from viewer.saved
    HideSigned-in viewerMarks not_interested; in-session Hidden · Undo; next list query excludes the id
    ContactSubscriber+; hidden on own postsNavigates immediately to /messages?user=<createdBy>; Matcher contact_intent is best-effort and must not gate navigation
    From chatOwner only, when the listing came from a taskOpens /messages?c=<conversationId>

    There is no anonymous-posting flag. Authenticated viewers see the poster name/avatar. Username appears only on public profiles. Empty name renders Member vs Private User.

    Right-rail query params (q, types, location, …) filter the current page of cards in the browser. GET /api/opportunities still paginates with limit / startAfter only — it is not a full-text search endpoint. Use Opportunities API search for q=.

    My Opportunities at /opportunities/my is the owner dashboard (Edit / Delete / View). Saved and Applied tab counts are hardcoded 0 today — do not treat them as live metrics.

    Typical clone scenarios

    1. Talent / partnership ring — enable job, offer, request, partnership; keep Matcher auto-approve off until you trust scoring.
    2. Commerce vertical — enable collective_order, scheduled_services, asset_rental, bounty, tender with Wallet / PaymentConductor rails.
    3. Ringdom settler / builder ring — enable ring_customization and program so customization quests and institution programs land in CRM.

    Operator checklist

    1. Confirm feature flag / whitelabel includes opportunities.
    2. Set opportunities.enabledTypes in ring-config.json (do not assume every OpportunityType union member is UI-visible).
    3. Configure LLM (LLM_PROVIDER, OPENAI_API_KEY or ANTHROPIC_API_KEY, LLM_MODEL) before expecting auto-fill / LLM explanations.
    4. Tune Matcher in Admin → Matcher / Platform Settings → AI → Matcher (threshold, max matches, auto-approve).
    5. Teach members My Opportunities at /opportunities/my (ROUTES.MY_OPPORTUNITIES) — legacy /opportunities/my-opportunities is removed.
    6. Confirm browse cards show names (not UUIDs) and that Hide survives a refresh for that signed-in user.
    Viewer like / save / hide
    get-viewer-interactions.ts ← likes + user_content_interactions
    Tag / chat provenancelib/opportunity-tags.ts (splitOpportunityTags)
    Feed card / listopportunity-feed-card.tsx, opportunity-list.tsx
    Interactionsapp/_actions/opportunity-interactions.ts
    Matcher corelib/ai/matcher.ts + OpportunityMatchingService
    Auto-fillfeatures/opportunities/services/auto-fill-service.ts
    Auto-approvefeatures/opportunities/services/auto-approval-service.ts
    PG JSONB ↔ clientfeatures/opportunities/lib/opportunity-db-mapper.ts
    Post-mutation syncfeatures/opportunities/lib/opportunity-mutation-sync.ts → syncOpportunityDiscovery
    Program → CRMfeatures/opportunities/lib/program-crm-ingest.ts
    UI Server Actionsapp/_actions/opportunities.ts
    Firestore legacylib/converters/opportunity-converter.ts when DB_BACKEND_MODE=firebase-full only

    Database: default production clones use DB_BACKEND_MODE=k8s-postgres-fcm (also supabase-fcm). Services return SerializedOpportunity (ISO date strings). Do not invent DATABASE_MODE=firebase_only.

    Browse list (verified)

    1. 1

      (protected)/opportunities/page.tsx and GET /api/opportunities pass viewerUserId: session.user.id into getOpportunitiesForRole. Hidden ids are loaded first, then the query uses DatabaseService operator not-in on opportunities.id. PostgreSQLAdapter maps opportunities / likes id to the SQL column (not data->>'id').

    2. 2

      attachOpportunityFeedFields adds creator, viewer, and likes. Cards hydrate useOptimistic from opportunity.viewer (the previous constant-base optimistic state never showed saved/liked). After Like / Save / Hide, onInteractionChange patches the cursor-feed item so session flags stay consistent.

    3. 3

      useCursorFeed persists 24h in localStorage. The list fingerprint is …|viewer=<userId> so one account cannot inherit another’s like/save/hide. Reloads overlay server initialItems onto cached rows by id (hooks/use-cursor-feed.ts).

    4. 4

      Browse useRealtimeOpportunities({ autoConnect: true }) stays for silent tunnel subscribe. Create snippets include creator { id, name, avatar } so live-inserted cards are not “Private User”. Detail (opportunity-details.tsx) still renders the Live Updates bar.

    convertTaskToOpportunity writes metadata.source: { kind: 'chat_task', messageId, conversationId } and tags: []. splitOpportunityTags still parses legacy sourceMessageId: / sourceConversationId: / chat_task tags at render. There is no backfill job.

    Visibility & status

    • Visibility: public | subscriber | member | confidential
    • Status: draft | pending | active | closed | expired | archived
    • Confidential create/edit requires confidential-capable roles (canCreateOpportunityConfidential / canEditOpportunity)

    Role gates (create)

    BucketTypes (examples)Minimum role
    Member offersoffer, job, partnership, volunteer, mentorship, resource, event, ring_customization, program, collective_order, tender, asset_rentalmember privileges
    Requestsrequestsubscriber+
    Subscriber specialscv, scheduled_services, bountysubscriber+
    Other union memberse.g. future-facing types not in enabled/permission setsadmin+ (and must be enabled)

    Route contracts, create body, and list JSON live on Opportunities API. Like / Save / Hide / Contact are Server Actions (app/_actions/opportunity-interactions.ts), not REST. MCP GET /api/mcp/v1/opportunities currently does not pass viewerUserId.

    After mutations, syncOpportunityDiscovery invalidates cache tags, revalidatePath for list/detail/my, and publishes Tunnel opportunity:*. That does not bust client useCursorFeed — overlay + viewer fingerprint are the browse freshness path.

    LLM / Matcher env (template)

    Keys documented in env.local.template: LLM_PROVIDER, OPENAI_API_KEY, ANTHROPIC_API_KEY, LLM_MODEL, MAX_MATCHES_PER_OPPORTUNITY, MATCHING_MAX_TOKENS. Without a working provider, Matcher falls back to non-LLM / tag-style explanations with lower confidence.

    Entities

    Same-workflow: organization linkage and verified posters for opportunities.

    Notifications

    Same-workflow: match invitations are delivered as platform notifications.

    Public Pools & DAO Jars

    See-also: collective / builder jar payouts tied to opportunity owners.

    AI Matcher

    Deep-dive: MatchFactors, LLM vs heuristic scoring, and explanation length.

    Admin console

    Deep-dive: Admin Matcher tab and platform AI settings for thresholds.

    Public Pools

    When a related jar fills, builders receive net native token (role fee from publicPools.platformFeePercentByRole).

    Browse feed (/opportunities)

    Authenticated members see compact cards: type chip, title, two-line description, poster byline, relative posted time, human tags, like count, and icon actions.

    ActionWhoWhat happens
    LikeSigned-in viewerToggles the likes collection; heart fills from opportunity.viewer.liked
    SaveSigned-in viewerToggles user_content_interactions (save); hydrates from viewer.saved
    HideSigned-in viewerMarks not_interested; in-session Hidden · Undo; next list query excludes the id
    ContactSubscriber+; hidden on own postsNavigates immediately to /messages?user=<createdBy>; Matcher contact_intent is best-effort and must not gate navigation
    From chatOwner only, when the listing came from a taskOpens /messages?c=<conversationId>

    There is no anonymous-posting flag. Authenticated viewers see the poster name/avatar. Username appears only on public profiles. Empty name renders Member vs Private User.

    Right-rail query params (q, types, location, …) filter the current page of cards in the browser. GET /api/opportunities still paginates with limit / startAfter only — it is not a full-text search endpoint. Use Opportunities API search for q=.

    My Opportunities at /opportunities/my is the owner dashboard (Edit / Delete / View). Saved and Applied tab counts are hardcoded 0 today — do not treat them as live metrics.

    Typical clone scenarios

    1. Talent / partnership ring — enable job, offer, request, partnership; keep Matcher auto-approve off until you trust scoring.
    2. Commerce vertical — enable collective_order, scheduled_services, asset_rental, bounty, tender with Wallet / PaymentConductor rails.
    3. Ringdom settler / builder ring — enable ring_customization and program so customization quests and institution programs land in CRM.

    Operator checklist

    1. Confirm feature flag / whitelabel includes opportunities.
    2. Set opportunities.enabledTypes in ring-config.json (do not assume every OpportunityType union member is UI-visible).
    3. Configure LLM (LLM_PROVIDER, OPENAI_API_KEY or ANTHROPIC_API_KEY, LLM_MODEL) before expecting auto-fill / LLM explanations.
    4. Tune Matcher in Admin → Matcher / Platform Settings → AI → Matcher (threshold, max matches, auto-approve).
    5. Teach members My Opportunities at /opportunities/my (ROUTES.MY_OPPORTUNITIES) — legacy /opportunities/my-opportunities is removed.
    6. Confirm browse cards show names (not UUIDs) and that Hide survives a refresh for that signed-in user.
    Viewer like / save / hide
    get-viewer-interactions.ts ← likes + user_content_interactions
    Tag / chat provenancelib/opportunity-tags.ts (splitOpportunityTags)
    Feed card / listopportunity-feed-card.tsx, opportunity-list.tsx
    Interactionsapp/_actions/opportunity-interactions.ts
    Matcher corelib/ai/matcher.ts + OpportunityMatchingService
    Auto-fillfeatures/opportunities/services/auto-fill-service.ts
    Auto-approvefeatures/opportunities/services/auto-approval-service.ts
    PG JSONB ↔ clientfeatures/opportunities/lib/opportunity-db-mapper.ts
    Post-mutation syncfeatures/opportunities/lib/opportunity-mutation-sync.ts → syncOpportunityDiscovery
    Program → CRMfeatures/opportunities/lib/program-crm-ingest.ts
    UI Server Actionsapp/_actions/opportunities.ts
    Firestore legacylib/converters/opportunity-converter.ts when DB_BACKEND_MODE=firebase-full only

    Database: default production clones use DB_BACKEND_MODE=k8s-postgres-fcm (also supabase-fcm). Services return SerializedOpportunity (ISO date strings). Do not invent DATABASE_MODE=firebase_only.

    Browse list (verified)

    1. 1

      (protected)/opportunities/page.tsx and GET /api/opportunities pass viewerUserId: session.user.id into getOpportunitiesForRole. Hidden ids are loaded first, then the query uses DatabaseService operator not-in on opportunities.id. PostgreSQLAdapter maps opportunities / likes id to the SQL column (not data->>'id').

    2. 2

      attachOpportunityFeedFields adds creator, viewer, and likes. Cards hydrate useOptimistic from opportunity.viewer (the previous constant-base optimistic state never showed saved/liked). After Like / Save / Hide, onInteractionChange patches the cursor-feed item so session flags stay consistent.

    3. 3

      useCursorFeed persists 24h in localStorage. The list fingerprint is …|viewer=<userId> so one account cannot inherit another’s like/save/hide. Reloads overlay server initialItems onto cached rows by id (hooks/use-cursor-feed.ts).

    4. 4

      Browse useRealtimeOpportunities({ autoConnect: true }) stays for silent tunnel subscribe. Create snippets include creator { id, name, avatar } so live-inserted cards are not “Private User”. Detail (opportunity-details.tsx) still renders the Live Updates bar.

    convertTaskToOpportunity writes metadata.source: { kind: 'chat_task', messageId, conversationId } and tags: []. splitOpportunityTags still parses legacy sourceMessageId: / sourceConversationId: / chat_task tags at render. There is no backfill job.

    Visibility & status

    • Visibility: public | subscriber | member | confidential
    • Status: draft | pending | active | closed | expired | archived
    • Confidential create/edit requires confidential-capable roles (canCreateOpportunityConfidential / canEditOpportunity)

    Role gates (create)

    BucketTypes (examples)Minimum role
    Member offersoffer, job, partnership, volunteer, mentorship, resource, event, ring_customization, program, collective_order, tender, asset_rentalmember privileges
    Requestsrequestsubscriber+
    Subscriber specialscv, scheduled_services, bountysubscriber+
    Other union memberse.g. future-facing types not in enabled/permission setsadmin+ (and must be enabled)

    Route contracts, create body, and list JSON live on Opportunities API. Like / Save / Hide / Contact are Server Actions (app/_actions/opportunity-interactions.ts), not REST. MCP GET /api/mcp/v1/opportunities currently does not pass viewerUserId.

    After mutations, syncOpportunityDiscovery invalidates cache tags, revalidatePath for list/detail/my, and publishes Tunnel opportunity:*. That does not bust client useCursorFeed — overlay + viewer fingerprint are the browse freshness path.

    LLM / Matcher env (template)

    Keys documented in env.local.template: LLM_PROVIDER, OPENAI_API_KEY, ANTHROPIC_API_KEY, LLM_MODEL, MAX_MATCHES_PER_OPPORTUNITY, MATCHING_MAX_TOKENS. Without a working provider, Matcher falls back to non-LLM / tag-style explanations with lower confidence.

    Entities

    Same-workflow: organization linkage and verified posters for opportunities.

    Notifications

    Same-workflow: match invitations are delivered as platform notifications.

    Public Pools & DAO Jars

    See-also: collective / builder jar payouts tied to opportunity owners.

    AI Matcher

    Deep-dive: MatchFactors, LLM vs heuristic scoring, and explanation length.

    Admin console

    Deep-dive: Admin Matcher tab and platform AI settings for thresholds.