AACsearch
Relevance Studio

Personalization

User profiles, segments, session reranking, and recommendations in Relevance Studio. How personalize=true + sessionId wire profile-boost into ranking, and best practices for PII.

The Personalization area of Relevance Studio turns anonymous and logged-in user signals into ranking adjustments. It is built on four layers, each one optional and independently togglable per index:

  1. User profiles — per-user feature vectors derived from purchase and click history.
  2. Segments — coarse cohorts (e.g. "frequent buyers", "discount hunters") that group profiles for editor-tunable boosts.
  3. Session reranking — short-lived, in-session reordering based on the last few queries and clicks.
  4. Recommendationsrelated products, also-bought, frequently-viewed-together blocks fed by the same profile store.

When to enable personalization

Turn personalization on when all three of the following hold:

  • Your search has a real concept of a returning user (logged-in or stable anonymous ID).
  • You have at least ~10k click events per index per week — below that, the signal is too noisy.
  • Editor curations alone are not enough to differentiate the same query for different users.

If you are unsure, start with session reranking only — it requires no historical data and degrades to a no-op for first-time visitors.

How personalize=true + sessionId wire into the search call

Every public search request can opt in by sending two extra fields:

  • personalize: true on the request body — turns the layer on for this query.
  • sessionId: "<stable-id>" — a UUID or hashed-cookie value that ties this request to the user's profile (or, for anonymous users, their session).

The server resolves these fields in the read path, before the Typesense multi_search call, and AND-combines a profile-boost expression into the existing ranking. The flow is the same five gates as a normal search — auth, rate limit, tenant filter, scoped filter, multi_search — with one extra step between rate-limit and tenant-filter: applyPersonalization.

Stable IDs and PII

The sessionId MUST be opaque to AACsearch. Do not send raw emails, phone numbers, or user IDs from your CRM. The recommended pattern is:

sessionId = sha256(your_internal_user_id + BETTER_AUTH_SECRET_FRAGMENT).slice(0, 32)

The server-side history that backs each profile caps at 100 entries per session. Older clicks and views are evicted FIFO. This is intentional: it keeps the profile small, fast to load on the hot path, and easy to expire for GDPR right-to-be-forgotten requests (a single DELETE /personalization/profile/<sessionId> clears it).

Example: curl

curl -X POST https://api.aacsearch.com/api/search/public/multi \
  -H "Authorization: Bearer ss_search_pub_XXXXXX" \
  -H "Content-Type: application/json" \
  -d '{
    "searches": [
      {
        "collection": "products",
        "q": "running shoes",
        "query_by": "title,brand,description",
        "personalize": true,
        "sessionId": "9f3c1b2e8a7d4f6c0b1a2d3e4f5a6b7c"
      }
    ]
  }'

The response shape is unchanged. If personalize=true cannot be applied — for example the profile is empty, the index has personalization disabled, or the Scale plan entitlement is missing — the request gracefully falls back to the standard ranking and a personalization_applied: false flag is included in the response metadata.

Example: TypeScript SDK / oRPC client

import { client } from "@repo/api/client";

const res = await client.search.public.multi.call({
  searches: [
    {
      collection: "products",
      q: "running shoes",
      query_by: "title,brand,description",
      personalize: true,
      sessionId: visitorSessionId, // sha256(uid + secret).slice(0, 32)
    },
  ],
});

For browser usage, prefer the scoped search token flow described in Architecture → Read path — the personalize and sessionId fields are forwarded through the scoped token unchanged.

Wiring profile-boost into ranking

Inside Studio → Relevance → Personalization, an admin chooses how profile features influence ranking. Three modes are supported:

ModeEffect on rankingWhen to use
offNo-op (default for new indexes).Establishing baselines.
boostAdds a _personalization_score boost to the existing ranker.Conservative roll-out, keeps editor curations dominant.
rerankRe-sorts the top-K results from the base ranker by the personalization score.High-traffic indexes with strong profile coverage.

The boost weight is a single number 0–10. Start at 1.0, watch CTR on the Click feedback panel for a week, and bump in 0.5 increments. Anything above 5.0 typically dominates curations — only go there if you have validated it in an A/B test.

Segments

A segment is a saved filter over the user-profile space. Segments are defined in Studio and evaluated server-side on every personalized request. Examples shipped out of the box:

  • frequent_buyers — profiles with ≥ 3 purchase events in the last 30 days.
  • cart_abandoners — profiles with ≥ 1 add_to_cart and 0 purchase in 7 days.
  • discount_hunters — profiles whose clicked products had discount_pct > 0 in ≥ 50% of recent clicks.

Each segment has its own boost weight. A user in two segments receives the max of the two weights (not the sum) — this keeps the math predictable when segments overlap.

Recommendations

The same profile store powers the recommendation endpoints. They are exposed as their own oRPC procedures and do not require personalize=true on the search call:

  • recommendations.related — content-based: similar to the input productId.
  • recommendations.alsoBought — collaborative: from the click-and-buy graph.
  • recommendations.frequentlyViewedTogether — co-view within a session.

See the dedicated Recommendations section for the full request and response shape.

Best practices on PII

  1. Never send raw email / phone / customer ID in sessionId. Hash it first with a server-side secret.
  2. Treat sessionId as a token, not an identifier. It should rotate when the user logs out.
  3. Cap history at 100 entries. This is the server-side default and the only supported configuration — it is documented here so customers can reason about retention.
  4. Wire a GDPR delete hook. When a user invokes right-to-be-forgotten, call the personalization.profile.delete procedure with their stable sessionId. The wipe is synchronous and idempotent.
  5. Do not personalize logged-out admin sessions. Inspect-mode queries from Studio should pass personalize=false so the ranking explainer sees the unbiased ranking.

On this page