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 actor_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
}
],
"pagination": { "next_cursor": "eyJ...", "has_more": true }
}2
Paginate
Results are cursor-paginated by (rank, id). Each response carries pagination: { next_cursor, has_more }; feed next_cursor back as ?cursor= for the next page. A cursor is valid only for the same q that produced it.
3
Filter to the requester's view
The PAK lane has no end-user principal — pass actor_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&actor_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. Indexed, but not yet writable or returned over the API; the actor payload isexternal_id/display_name/avatar_url/metadata.
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.
Permissions
PAK scopes
- GET requires
social.listonpcft:agora:community/<communityId>.