Skip to main content
Interest matching connects people by their activity-derived interests. As users create and engage with content, Sublay folds that activity into a small set of interest facets per user — clusters of related topics, each with a recency-weighted “hotness”. The POST /match/users endpoint then ranks other users against an asker, either passively (“who is like me?”) or by an explicit topic (“who is into biotech?”). Unlike a static interests list, facets are built from behavior and decay over time, so a match reflects what someone is engaged with now, not what they once filled into a profile field.
Interest matching requires a paid plan. Free-tier projects cannot use embedding-based features.

Prerequisites

Interest matching builds on the semantic-search embedding pipeline, so it has an ordered set of prerequisites:
  1. Paid plan — embeddings are a paid feature.
  2. ai-search bundle — provides ContentEmbeddings, the source of facet building. Interest matching reads and annotates this table (it adds a facetProcessedAt marker column), so ai-search must be installed first. Installing interest-matching without it fails fast with database/bundle-prerequisite-missing.
  3. interest-matching bundle — provisions the UserInterestFacets table (with its vector + btree indexes). Install it from the dashboard Database section, or via the bundle install API.
  4. interestMatching.enabled — the project setting that turns folding and matching on.

How Facets Are Built

Facet building runs inside the existing embedding cron (every few minutes), so there is nothing extra to schedule:
  • Fold-in. Each newly-embedded, source-clean record is folded as one unit of mass into the author’s nearest facet (or spawns a new facet if it is far from all existing ones). Multi-chunk content still counts once — long posts do not get extra weight.
  • Decay. A facet’s mass decays with a long half-life (interests move slowly), so recent activity dominates. The current, decayed mass is a facet’s hotness.
  • Low-signal skip. Trivial content (“lol”, “same”) is skipped so it never pollutes a facet.
  • Pruning. Facets that never reach a significance threshold are garbage-collected after a grace window; a facet that was significant is kept even when it later goes cold.
Building is moderation-, soft-delete-, and DM-safe: an embedding row surviving admin removal is not treated as live content, and private-DM content never feeds a facet.

Project Settings

Configure the feature through the interestMatching block on the project settings (dashboard Settings → project settings update). Every field is optional and paid-plan-guarded; the tuning knobs fall back to sensible platform defaults when unset.
interestMatching.enabled
boolean
default:"false"
Master switch. Turns on both cron fold-in and the /match/users endpoint.
interestMatching.exposeSampleContent
boolean
default:"false"
Gate 1 of the two-gate sample exposure. When on, callers may request raw sample content per matched facet by also passing includeSampleContent: true (Gate 2) on the request. Both gates must be on for samples to be returned. When off, requesting samples returns 403 match/sample-content-disabled. Changing this setting invalidates the project cache so the gate takes effect immediately.
interestMatching.halfLifeHours
number
Half-life (hours) for facet mass/hotness decay. This is a distinct knob from the entity scoring.halfLifeHours — interests move far more slowly than trending content, so the default here is much longer (~60 days).
interestMatching.distanceThreshold
number
Cosine distance under which a new record folds into an existing facet instead of spawning a new one. Larger values produce fewer, broader facets.
interestMatching.significanceThreshold
number
A facet whose lifetime-peak hotness never reaches this value is treated as never-significant and eligible for pruning.
interestMatching.pruneGraceHours
number
Grace window (hours) after a facet is created during which it is never pruned, so a fresh facet has time to accumulate before garbage collection.
interestMatching.lowSignalThreshold
number
Threshold for the low-signal filter — content below it is skipped from folding (but still marked, so it never re-drains).

Two-gate sample exposure

Sample content — the raw text that illustrates why two people overlap — is exposed only when both gates are satisfied: Gate 1 is a project-owner-level consent; Gate 2 is a per-request opt-in. The always-returned match breakdown (scores, facet ids, hotness) never carries readable content on its own.

Accepted Limitations

Interest matching is a lossy, eventually-consistent primitive. Know these v1 tradeoffs:
  • Gradual backfill latency. Facets are built by the periodic cron, not synchronously on write. A brand-new project (or a burst of new content) takes several cron ticks to reflect fully in match results. Newly-created content is not blended into a live query at match time.
  • Cold-start asymmetry. A cold user can ask (via the bio-vector fallback) but, in v1, is not surfaced as a candidate until they have real facets. A rare niche expert with little activity may therefore be hard to find until they accrue facets.
  • Deletion / edits do not retract mass. Facet mass is additive and decays with time; deleting or editing content does not subtract the contribution it already made. Removed/soft-deleted/DM content is excluded from building new facets and from samples, but past mass stays until it decays. Facets do not carry stable identity across cron runs.

SDKs