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:
| Field | Notes |
|---|---|
chunking_strategy | baseline (fixed-size chunks with overlap), story_beats (LLM-segmented narrative boundaries), or tone_segments (tone-shift boundaries). See Chunking for strategy-specific parameters |
parameters | String-keyed map of strategy parameters (tokens, overlap, etc.) |
template_id | Optional reference to a segmentation template for LLM-driven segmentation voices |
score_threshold | Minimum 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_limit | Default top-K for voice-tuned search |
description | Free-form |
tags | Free-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.
Voice-tuned search
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_strategyorparameters, 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) search — Search.
- Eval campaigns that feed gate scores — Evals.