Settings
Organization settings, the analysis prompts, the extraction fields, and model routing. The last two have the widest blast radius — they change how every call is analysed.
Organization settings
GET /api/settings
{
"report_recipients": ["[email protected]"],
"call_context": "Outbound calls selling ergonomic office furniture to SMBs.",
"stt_model": "gpu",
"auto_reports_enabled": true,
"is_premium": false,
"dismissed_default_scores": ["small_talk"]
}
PUT /api/settings — manager or above
{
"report_recipients": ["[email protected]", "[email protected]"],
"call_context": "Outbound calls selling ergonomic office furniture to SMBs.",
"auto_reports_enabled": true
}
Report recipients
Who receives generated reports. Plain addresses; they do not need to be members of the organization.
Dismissed default scores
PATCH /api/settings/dismissed-default-scores
{ "dismissed_default_scores": ["small_talk", "call_opening"] }
Hides built-in scoring criteria that do not apply to your calls. The score is still computed — this only removes it from display, so dismissing one and changing your mind later costs nothing.
Analysis prompts
GET /api/settings/analysis-prompts
{
"system_prompt": "You are analysing B2B furniture sales calls…",
"safety_prompt": "Never infer a customer's health, religion, or finances…",
"default_system_prompt": "…",
"default_safety_prompt": "…",
"system_prompt_uses_default": false,
"safety_prompt_uses_default": true,
"combined_prompt_preview": "…"
}
The defaults are returned alongside your overrides, so a UI can show what you
are diverging from — and combined_prompt_preview shows exactly what the model
will receive, which is the only reliable way to check a prompt edit did what
you meant.
PATCH /api/settings/analysis-prompts — manager or above
{
"system_prompt": "You are analysing B2B furniture sales calls…",
"set_system_prompt": true,
"safety_prompt": null,
"set_safety_prompt": false
}
The set_* booleans distinguish "set this to the value I sent" from "leave it
alone" — which is what lets you clear a prompt back to the default by sending
null with set_*: true, rather than an omitted field meaning two things.
They apply to calls analysed afterwards. Use
POST /api/calls/{id}/reanalyze to bring
an existing call up to date — superadmin only, because doing it across a
deployment is expensive.
Extraction fields
The structured questions asked of every transcript. Reading them is open to any member; changing them is superadmin only, because they apply across the whole deployment.
List fields
GET /api/extraction-fields — superadmin
{
"fields": [
{
"id": 7,
"uuid": "ef1a2b3c-…",
"name": "budget_confirmed",
"field_type": "boolean",
"description": "Did the customer confirm they have budget approved?",
"options": [],
"min_value": null,
"max_value": null,
"scope": ["outbound"],
"category": "qualification",
"enabled": true
}
]
}
Create a field
POST /api/extraction-fields — superadmin
{
"name": "budget_confirmed",
"field_type": "boolean",
"description": "Did the customer confirm they have budget approved?",
"scope": ["outbound"],
"options": [],
"min_value": null,
"max_value": null,
"category": "qualification",
"enabled": true
}
| Field | Required | Notes |
|---|---|---|
name | ✓ | The key this lands under in a call's custom_field_values |
field_type | ✓ | boolean, string, number, enum |
scope | ✓ | Restricts the field to inbound, outbound, or both |
description | — | What the model is told to look for — write it as an instruction |
options | — | Allowed values for enum |
min_value / max_value | — | Bounds for number |
category | — | Grouping for display |
enabled | — | Disable without deleting, preserving historical values |
Update a field
PATCH /api/extraction-fields/{id} — superadmin
Every field above is optional on update, plus one that only exists here:
| Field | Notes |
|---|---|
prompt_override | Replaces the generated instruction for this field with your own wording |
Delete a field
DELETE /api/extraction-fields/{id} — superadmin
Field labels
GET /api/extraction-field-labels
Available to any member, and to API keys with sales:analyses:read. Returns
just the UUID, name, and type of each field — enough to render a call's
custom_field_values without exposing the prompts behind them.
description is a prompt, not a label"Did the customer confirm they have budget approved?" extracts reliably. "Budget" does not. If a field is coming back empty or wrong across many calls, the description is usually why.
Model routing
GET /api/settings/model-routing
{
"stt_model": "gpu",
"diarization_url": "https://…",
"translation": { "enabled": true, "provider": "openai", "vllm_endpoint": null },
"custom_analysis": { "enabled": true, "provider": "vllm", "vllm_endpoint": "http://localhost:8000/v1" }
}
PATCH /api/settings/model-routing — superadmin only
stt_model accepts gpu or sarvam. The remaining keys choose which model
serves each stage: diarization,
translation, and the custom analysis pass. provider: "vllm" points a stage at
self-hosted inference through vllm_endpoint, which is how an on-prem
deployment keeps transcripts inside its own network.
This is superadmin-gated in every binary that has it — a misrouted model breaks analysis for every organization on the deployment, not just the one that changed it.
Call layout
Control how a call's analysis is arranged in the console. Display only — nothing here changes what is extracted.
List sections
GET /api/call-sections — any member
{
"sections": [
{ "key": "__section:scores", "label": "Scores" },
{ "key": "__section:key_details", "label": "Key Details" }
]
}
Set the layout
PUT /api/call-layout — superadmin
{
"hidden": ["waveform"],
"collapsed": ["key_details"]
}
| Field | Notes |
|---|---|
hidden | Section ids not shown at all |
collapsed | Section ids shown collapsed by default |
Order the signal groups
PUT /api/signal-group-order — superadmin
{ "group_order": ["qualification", "outcome", "next_action"] }