Contact

Voices

A voice is a reusable chunking + retrieval configuration that can be applied to any corpus in the same environment. Voices are model-agnostic — the embedding model lives on the corpus (see Concepts for why that matters).

This page documents every endpoint under /v1/voices/*.

The voice config shape

{
  "name": "docs-default",
  "config": {
    "chunking_strategy": "baseline",
    "parameters": { "min_tokens": "256", "max_tokens": "512", "overlap": "64" },
    "template_id": null,
    "score_threshold": 0.0,
    "default_limit": 10,
    "description": "Default voice for the developer documentation corpus",
    "tags": []
  }
}

Fields:

FieldNotes
chunking_strategybaseline (fixed-size chunks with overlap), story_beats (LLM-segmented narrative boundaries), or tone_segments (tone-shift boundaries). See Chunking for strategy-specific parameters
parametersString-keyed map of strategy parameters (tokens, overlap, etc.)
template_idOptional reference to a segmentation template for LLM-driven segmentation voices
score_thresholdMinimum similarity score for retrieval. 0.0 surfaces every match; raise once the corpus has enough content to filter aggressively. Overshooting produces empty results — start low and tune up
default_limitDefault top-K for voice-tuned search
descriptionFree-form
tagsFree-form categorization

Create a voice

POST /v1/voices
{ "name": "docs-default", "config": {  as above } }

Response: 201 Created with the full VoiceDetail (id, name, config, version starting at 1, timestamps).

enscrive-docs bootstrap reaches this endpoint when the voice listed in your enscrive-docs.toml does not already exist in the tenant.

List / get / delete

GET    /v1/voices                # all voices in the environment
GET    /v1/voices/{id}           # one voice
DELETE /v1/voices/{id}           # delete the voice

Delete returns { "deleted": true, "voice_id": "…" }. Deleting a voice does not affect any embeddings already generated with it; it only removes the future ability to search via that voice.

Update a voice (full-replace)

PUT /v1/voices/{id}
{ "config": {  entire config, not a patch } }

PUT is full-replace — the request body must contain the whole config object. The server rejects ill-formed configs (e.g. invalid chunking parameters, generative voices missing a template_id) at the boundary.

Every successful update bumps the voice’s version and writes a version snapshot (see below).

The ergonomic way to iterate on a voice is:

enscrive-docs voice tune docs-default

which fetches the current config, opens it in $EDITOR as TOML, validates your edit, and sends the PUT. See the CLI reference for all options.

Version history

Every successful create or PUT writes a version snapshot. You can audit the history and fetch any previous version.

GET /v1/voices/{id}/versions
GET /v1/voices/{id}/versions/{version}

Versions are immutable. Rollback is a normal PUT of the old config — not a privileged operation.

Compare two voices side-by-side

POST /v1/voices/compare
{
  "voice_a_id": "…",
  "voice_b_id": "…",
  "corpus_id": "…",
  "query": "how do I create a corpus?",
  "limit": 10
}

Runs the same query through both voices against the same corpus and returns both result sets in one response. Useful when tuning retrieval parameters — you can see whether raising score_threshold from 0.3 to 0.5 is losing relevant matches at the top of the result set.

POST /v1/voices/search

Apply a voice’s retrieval configuration (score threshold, default limit, granularity, hybrid-alpha, resolution) to a query. This is distinct from the raw /v1/search endpoint, which ignores voice-level retrieval tuning. See Search for the full request and response shape.

Promotion and gates

Voices can be promoted between environments (for example from staging into production) — configuration only; no embeddings are copied. Promotion can be gated on eval thresholds so that an underperforming voice cannot reach production.

Set gates on a voice

POST /v1/voices/{id}/gates
{ "metric": "ndcg_at_10", "min_score": 0.75 }

Each gate names an eval metric (ndcg_at_10, recall_at_20, precision_at_10, …) and the minimum score that voice must achieve on its most recent eval campaign to pass.

GET    /v1/voices/{id}/gates                 # list all gates
DELETE /v1/voices/{id}/gates/{metric}        # remove one gate

Promote a voice

POST /v1/voices/{id}/promote
{ "target_environment_id": "…" }

The server evaluates all configured gates against the voice’s most recent eval results. If every gate passes, the voice configuration is copied into the target environment (with a fresh id there). If any gate fails, promotion is rejected with a 403 citing which gate(s) blocked it.

See Evals for how to run campaigns that produce the scores gates read from.

Common configurations

The founder-approved memo [Voice vs Corpus model ownership] is worth restating:

  • A voice never names an embedding model. Models belong to corpora.
  • Changing a voice is safe — it doesn’t invalidate any embeddings in the corpus. Re-search the existing embeddings with the new voice to see the retrieval-side delta.
  • Changing chunking does invalidate downstream embeddings: if you change chunking_strategy or parameters, you need a re-ingest (or a reset) to regenerate chunks with the new rules.

What this endpoint group does not cover

  • Templates for generative segmentation — Segmentation templates.
  • Raw (non-voice) searchSearch.
  • Eval campaigns that feed gate scores — Evals.