---
title: "Search Hybrid"
method: GET
path: "/v1/search/hybrid"
---

# Search Hybrid

`GET /v1/search/hybrid`

## Request

- Base URL: `https://api.storyflo.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api.storyflo.com/v1/search/hybrid`
- Auth: none declared

Hybrid search — keyword overlap + send_count weighting + recency.

v0 is application-side scoring (no pgvector); we score every
article that hits at least one query token, then rank by:
  score = 1.5 * keyword_overlap + 0.4 * log(send_count+1) + recency_bonus

Graduates to pgvector once batch embeddings ship — keeping the
same return shape so the frontend won't churn.

Performance (SEV 2026-06-15): ``storyflo.com/search`` chip clicks
were 5-39s cold because this endpoint scans up to 500 rows
(full-text contains) + Python-side scoring + a per-row
``RenderJob.status == done`` correlated subquery. Cold path on Fly
measured 39.5s for ``q=SpaceX`` before this cache. Wrapped in the
same ``search_cache.SearchCache`` singleton ``/v1/stories/search``
uses (5-min TTL, single-flight, stale-while-revalidate, 200-entry
LRU). Key disambiguator ``"hybrid"`` keeps hybrid + stories/search
entries from colliding inside the shared singleton.

Operator can bypass per-request with
``X-Storyflo-Search-Cache: bypass`` (smoke probes measuring cold
latency) or globally via env ``SEARCH_CACHE_TTL_SEC=0``.

Pool-exhaustion fix (P0 2026-06-20 12:25 PDT, Sentry
``TimeoutError: QueuePool limit of size 5 overflow 3 reached``):
deliberately no ``db: AsyncSession = Depends(get_db)`` parameter.
With basic-plan PG sized at ``DB_POOL_SIZE=5 + MAX_OVERFLOW=3``
(total 8 conns, see
``[[storyflo-prod-deploy-discipline-2026-06-19]]``), a request-
scoped Depends session would be held for the full duration of
``cache.get_or_compute`` — including for non-owners who are merely
awaiting another coroutine's Future inside ``search_cache.py``
(lines 246-248). With 3+ concurrent search requests that all hit
the single-flight wait path, the pool exhausts in seconds even
though only one DB query is actually running. The owner opens a
short-lived session inside ``_compute`` and releases it before the
Future resolves; non-owners hold zero connections while waiting.

## Query parameters

- `q` string, required
- `vertical` string, nullable
- `limit` integer

## Response `200`

Successful Response

- object

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/storyflo/apis/storyflo-inference-router.md) · [All operations](https://skmtc.dev/storyflo/apis/storyflo-inference-router/llms.txt) · [OpenAPI document](https://skmtc.dev/storyflo/apis/storyflo-inference-router/revisions/c3a1a735ac7a?raw)
