---
title: "Semantic search across received and sent mail"
method: POST
path: "/semantic-search"
tags: ["Search"]
---

# Semantic search across received and sent mail

`POST /semantic-search`

Ranked search across both received and sent mail. The `mode`
field selects the ranking strategy:

- `keyword`: lexical full-text matching only (no embeddings).
- `semantic`: meaning-based matching using vector embeddings.
- `hybrid` (default): blends the semantic and keyword signals.

Results are ordered by a relevance `score`. Every row reports the
fields it matched (`matched_fields`), a match-centered excerpt per
field (`snippets`), and a `score_breakdown` whose components account
for the `score`. Page through results by passing the prior
response's `meta.cursor` back as `cursor`.

Requires the Pro plan and the `semantic_search_enabled`
entitlement; callers without them receive `403`.

Host routing: this operation is served only by the search host
(`https://api.primitive.dev/v1`). The typed SDKs route it there
automatically.

## Headers

- `Idempotency-Key` string

## Request body

- object
  - `query` string — Free-text query. Required for `semantic` and `hybrid` modes; optional for `keyword` mode.
  - `mode` 'hybrid' | 'semantic' | 'keyword' — Ranking strategy. `keyword` is lexical only, `semantic` is embedding-based, `hybrid` blends both.
  - `corpus` string[] — Which mail to search. Defaults to both received (`inbound`) and sent (`outbound`).
  - `search_in` string[] — Restrict matching to these fields. Defaults to all.
  - `exclude` string[] — Exclude these fields from matching.
  - `date_from` string, date-time — Only include mail at or after this timestamp.
  - `date_to` string, date-time — Only include mail at or before this timestamp.
  - `include` string[] — Opt-in extras. `coverage` adds an index-coverage snapshot to `meta`. Matched fields, snippets, and the score breakdown are always returned regardless of this field.
  - `limit` integer — Maximum number of results to return.
  - `cursor` string — Opaque pagination cursor from a prior response's `meta.cursor`.

## Response `200`

Ranked search results

- object
  - `success` true, required
  - `data` object[], required
    - `source_type` 'inbound_email' | 'sent_email', required — Whether this row is a received or sent message.
    - `id` string, required — Message id. Combine with `api_url` to fetch the full record.
    - `subject` string, nullable, required
    - `from` string, nullable, required
    - `to` string, nullable, required
    - `timestamp` string, required — Message timestamp (received_at for inbound, created_at for sent).
    - `status` string, required — Lifecycle status of the message.
    - `score` number, required — Overall relevance score; the `score_breakdown` components account for it.
    - `semantic_score` number, nullable, required — Raw semantic similarity signal, or null when not applicable.
    - `keyword_score` number, nullable, required — Raw keyword (lexical) signal, or null when not applicable.
    - `matched_fields` string[], required — Fields where the query matched.
    - `snippets` object[], required — Match-centered excerpts, one per matched field.
      - `field` string, required — The field this excerpt came from.
      - `text` string, required — Plain-text excerpt centered on the match (no markup).
    - `score_breakdown` object, required — Additive contributions to `score`. `semantic` and `keyword` are the raw signals times the mode's weight (null when not applicable); these plus `field_boost` and `recency` sum to `score` before each value is independently rounded to 5 decimal places.
      - `semantic` number, nullable, required
      - `keyword` number, nullable, required
      - `field_boost` number, required
      - `recency` number, required
    - `api_url` string, nullable, required — Relative API path to fetch the full message.
  - `meta` object, required
    - `limit` integer, required — Page size used for this request.
    - `cursor` string, nullable, required — Cursor for the next page, or null if there are no more results.
    - `mode` 'hybrid' | 'semantic' | 'keyword', required — Ranking mode used for this response.
    - `coverage` object, nullable, required — Index-coverage snapshot for the org, returned only when the `coverage` include option is requested.
      - `embedded_chunks` integer, required
      - `pending_chunks` integer, required
      - `skipped_plan_chunks` integer, required
      - `skipped_quota_chunks` integer, required
      - `unsupported_attachment_chunks` integer, required
      - `failed_chunks` integer, required

## Other responses

- `400` — Invalid request parameters
- `401` — Invalid or missing API key
- `403` — Authenticated caller lacks permission for the operation
- `429` — Rate limit exceeded
- `500` — Primitive encountered an internal error
- `503` — Primitive is temporarily unable to process the request

---

[API](https://skmtc.dev/primitive/apis/primitive-api.md) · [All operations](https://skmtc.dev/primitive/apis/primitive-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/primitive/primitive-api/revisions/c2299d28022c/schema)
