User search.
Postgres FTS over a generated tsvector column on the actor table. Display name weighted A, external_id (username) B, bio C. Sub-100ms ranked search up to a few million actors per community. No fuzzy matching today — that's a follow-up.
1
Find users
One endpoint, one query string. Returns up to limit hits (default 20, max 50) ordered by ts_rank. Pass requester_id to filter out actors the requester has muted or blocked, and actors who have blocked the requester.
curl -H "Authorization: Bearer pcft_live_..." \
"https://social.productcraft.co/v1/communities/<communityId>/search/actors?q=ada&limit=10"{
"data": [
{
"actor": {
"id": "...",
"external_id": "ada",
"display_name": "Ada Lovelace",
"avatar_url": "https://...",
"metadata": {},
"status": "active",
"...": "..."
},
"rank": 0.6687
}
]
}2
Filter to the requester's view
The PAK lane has no end-user principal — pass requester_id as a query param to apply the same block / mute filtering you'd see in feeds:
- Actors the requester has muted or blocked are dropped.
- Actors who have blocked the requester are dropped.
- Suspended / deleted actors are always dropped.
curl -H "Authorization: Bearer pcft_live_..." \
"https://social.productcraft.co/v1/communities/<communityId>/search/actors?q=alice&requester_id=<actorId>"Internals
What gets indexed
The actor.search_doc column is a generated tsvector built from three weighted parts using the simple locale (no stemming — usernames don't want “ada” matching “adamant”):
display_name— weight A (highest).external_id— weight B.bio— weight C.
Backed by a GIN index on the column. PG re-computes the vector on every actor write — no app-side maintenance.
Out of scope
Follow-ups
- Trigram fuzzy matching (
pg_trgm) — handles typos, partial-word matches. Deliberately deferred; the trade-off is index size. File a task if you need it. - Cross-community search — search is scoped per community. A future endpoint could fan out across the workspace.
- Cursor pagination — search returns up to
limithits in a single call. Hard max 50; if you need more, the right answer is to narrow the query, not paginate.
Permissions
PAK scopes
- GET requires
social.listonpcft:agora:community/<communityId>.