---
title: Search
description: Hybrid semantic + lexical search over the legal corpus, with optional reranking.
order: 0
---

`POST /v1/search`

Embeds the query with BGE-M3 (dense + sparse), runs a hybrid Qdrant query with server-side RRF fusion, optionally reranks with bge-reranker-v2-m3, then hydrates metadata from MongoDB.

```mermaid
flowchart LR
    A[Query] --> B["Embed with BGE-M3<br/>(dense + sparse)"]
    B --> C["Hybrid Qdrant query<br/>server-side RRF fusion"]
    C --> D{rerank: true?}
    D -->|yes| E["Cross-encoder rerank<br/>top 4 × top_k candidates"]
    D -->|no| F[Hydrate metadata<br/>from MongoDB]
    E --> F
    F --> G[Response]
```

## Request

```json
{
  "query": "مسؤولية الناقل الجوي عن تأخير الرحلات",
  "top_k": 10,
  "filters": {
    "doc_type": ["legislation", "case_law"],
    "jurisdiction": "EG",
    "year_from": 1990,
    "year_to": 2026,
    "status": ["in_force"]
  },
  "rerank": true,
  "include_text": true
}
```

| Field | Type | Default | Notes |
|---|---|---|---|
| `query` | string | required | |
| `top_k` | integer | `10` | 1–50 |
| `filters.doc_type` | string[] | — | `legislation`, `case_law`, `fatwa`, `regulation`, `decree` |
| `filters.jurisdiction` | string | — | `EG` or `SA` |
| `filters.year_from` / `year_to` | integer | — | |
| `filters.status` | string[] | — | `in_force`, `amended`, `repealed`, `unknown` |
| `rerank` | boolean | `true` | Runs the cross-encoder over `4 × top_k` candidates |
| `include_text` | boolean | `true` | `false` returns metadata only, no `snippet` |

## Response

```json
{
  "results": [
    {
      "canonical_id": "eg-leg-1948-131",
      "score": 0.87,
      "doc_type": "legislation",
      "title": "القانون المدني",
      "article_no": "163",
      "snippet": "...",
      "year": 1948,
      "status": "in_force",
      "citation_string": "المادة 163 من القانون رقم 131 لسنة 1948"
    }
  ],
  "took_ms": 142,
  "fusion": "rrf",
  "reranked": true
}
```

`snippet` is capped at 800 characters. `fusion` is `"rrf"` when both dense and sparse vectors were used, `"dense"` for dense-only. `meta` (omitted above) carries type-specific fields — `court`/`session_date` for judgements, `fatwa_number` for fatawa — present only when applicable.

## Errors

- `401 unauthorized`
- `422 validation_error`
- `503 upstream_unavailable`

See [Rate limits & errors](/docs/getting-started/rate-limits-errors) for the full error shape and per-plan `429` limits.
