The search system provides a dual-index approach: fast SQL filtering for exact matches, with semantic vector search as a fallback for fuzzy queries. This ensures buyers can find relevant talent even with incomplete or imprecise search criteria.
Location: apps/zooly-app/app/api/indexing/search/route.ts
GET /api/indexing/search?name_query=<query>&limit=<number>&offset=<number>
POST /api/indexing/search
{
"filters": {
"category": "MODELS",
"gender": "FEMALE",
"hairColor": "BLONDE",
"minFollowers": 100000
},
"limit": 20,
"offset": 0,
"orderBy": "numberOfFollowers",
"orderDirection": "desc"
}
Type: SearchFilters (from packages/types/src/types/LikenessSearch.ts)
Supports filtering by:
category, gender, country, targetAudience, nameminFollowers, maxFollowers, primaryPlatform, isVerified, contentNiche, minEngagementRate, maxEngagementRateminAge, maxAge, minHeight, maxHeight, minWeight, maxWeight, bodyTypehairColor, hairLength, hairType, eyeColor, skinColor, ethnicity, faceShape, facialHair, wearGlasses, hasTattoos, hasPiercingsvoicePitch, voiceTone, accent, primaryLanguagehasVoiceSample, hasImageAssetFunction: searchLikeness(filters, options)
Location: packages/likeness-search/src/searchLikeness.ts
Function: searchByFilters(filters, limit, offset)
Location: packages/db/src/access/likenessSearch.ts
When hasVoiceSample and hasImageAsset are not specified:
>=, <=)name using ILIKE pattern matchingnumberOfFollowers desc by defaultWhen hasVoiceSample or hasImageAsset are specified:
likeness_assets table for image assetslikeness_assets table for voice assets (with voiceSampleUrl IS NOT NULL check)ip_terms table to verify VoiceOver terms are approvedaccount table for name filter and slug validationname filter is present, other voice characteristic filters are ignored (name search takes priority)Results can be ordered by:
numberOfFollowers - Total follower count (descending by default)birthYear - Age-based orderingengagementRate - Engagement rate orderingDirection: asc or desc (default: desc)
Function: vectorFallbackSearch(filters, options)
Location: packages/likeness-search/src/vectorFallbackSearch.ts
Vector search is used as fallback when:
offset === 0 (only on first page, not for pagination)Exception: Name filter searches never fall back to vector search (return empty instead).
filtersToDescription(filters) creates natural language description
"category: models, gender: female, hairColor: blonde, minFollowers: 100000"generateEmbedding(queryText) uses OpenAI text-embedding-3-small
hasImageAsset and hasVoiceSample → Intersect results from both searcheshasVoiceSample → vectorSearchWithVoiceAssets()hasImageAsset → vectorSearchWithImageAssets()vectorSearch()<=> operator)
0.8 (configurable)likenessSearch rows for vector resultsLocation: packages/db/src/access/likenessSearchVector.ts
vectorSearch(queryEmbedding, topK, maxDistance) - Basic vector searchvectorSearchWithImageAssets(queryEmbedding, opts) - Filters accounts with image assetsvectorSearchWithVoiceAssets(queryEmbedding, opts) - Filters accounts with voice samples (requires approved VoiceOver term)PostgreSQL pgvector uses cosine distance:
embedding <=> queryEmbedding (lower = more similar)1 - distance (higher = more similar)0.8 means similarity score ≥ 0.2Function: formatSearchResults(likenessSearchResults)
Location: packages/likeness-search/src/formatSearchResults.ts
account records for all result accountIdslikenessAssets for all result accountIdsdisplayName, imageUrl, slugaccount.imageUrl or fallback /img/no-img.svgIMAGE type assets with contentUrlVOICE asset with voiceSampleUrlType: FormattedSearchResult (from packages/types/src/types/LikenessSearch.ts)
{
accountId: string;
likenessSearch: LikenessSearch;
account?: {
displayName: string;
imageUrl: string | null;
slug: string;
};
id: string;
displayName: string;
profileImage: string;
images: string[];
followersCount: string;
slug: string;
score?: number; // Only for vector search
voiceSampleUrl?: string;
}
When a GET request includes ?name_query=<query>:
filters.name = queryILIKE pattern matching on account.displayName (case-insensitive partial match)For advanced search with natural language queries:
/api/generate-structured-data with a natural language descriptiongemini-2.5-flash-lite) to extract structured filters from the text/api/indexing/search endpoint as pre-processed SearchFiltersExample Flow:
User Input: "Looking for a tall blonde female model in the US with a warm British accent"
↓
Client calls: POST /api/generate-structured-data
↓
Gemini extracts: { category: "MODELS", gender: "FEMALE", hairColor: "BLONDE", country: "USA", voiceTone: "WARM", accent: "BRITISH_RP" }
↓
Client sends: POST /api/indexing/search { filters: { ...extracted filters } }
↓
Search executes with structured filters
Filters are validated against enum values:
When name filter is present:
ILIKE pattern matching on account.displayName(status, createdAt) and (accountId, status)accountId for fast lookupsoffset === 0 to avoid expensive operations on paginationPromise.all()Map for O(1) lookups when matching results to accounts/assetsRequest:
{
"filters": {
"category": "MODELS",
"gender": "FEMALE",
"hairColor": "BLONDE",
"minFollowers": 100000
},
"limit": 20
}
Process:
numberOfFollowers descRequest:
{
"filters": {
"category": "ACTORS",
"hairColor": "BROWN",
"eyeColor": "GREEN"
},
"limit": 20
}
Process:
Request:
{
"filters": {
"hasVoiceSample": true,
"accent": "BRITISH_RP",
"voiceTone": "WARM"
},
"limit": 10
}
Process:
likeness_assets and ip_termsOn This Page
OverviewSearch ArchitectureSearch APIEndpointSearch FiltersSQL SearchSearch ImplementationWithout Asset FiltersWith Asset FiltersOrderingVector Fallback SearchWhen UsedProcessVector Search FunctionsDistance CalculationResult FormattingEnrichment ProcessResult TypeSearch Flow DiagramFilter ProcessingName Query ProcessingAI-Based Filter Extraction (Client-Side)Filter ValidationName Filter Special HandlingPerformance OptimizationsSQL Search OptimizationsVector Search OptimizationsResult Formatting OptimizationsSearch ExamplesExample 1: Exact Filter SearchExample 2: Fuzzy Search with Vector FallbackExample 3: Voice SearchSearch Result QualitySQL Search QualityVector Search QualityCombined Strategy