Admin API Advanced
Automate panel operations through the admin routes under /api/phoenix-mcp/admin/. Use a Home Assistant administrator access token or session; Phoenix access keys cannot call this API.
Home Assistant session, admin only
Every admin route requires a valid Home Assistant access token or session belonging to an active administrator. A long-lived access token belonging to that administrator is accepted. A Phoenix MCP access key can never authenticate here, not even a pass-through access key. See Security.
Conventions
- Bodies
- JSON,
Content-Type: application/json. Bodies over 1 MB are rejected with 413. - Request ID
- Every response carries
X-Phoenix-Request-ID. Audited administrator mutations use this ID in the Phoenix audit store, with the administrator as actor. Version restores retain an optional opaqueoriginating_access_key_idfor provenance. Administrator reads and diagnostic previews are not a complete audit of Home Assistant; the logging switches still apply. - Errors
- Error responses are
{ "error": code, "message": text }, with an optionalsuggestionsarray. Codes:unauthorized(401),forbidden(403),not_found(404),invalid_request(400),request_too_large(413),precondition_failed(412, when creating a MESA profile withIf-None-Match: *and a profile already exists),conflict(409, e.g. a duplicate access key name or an approval already being processed),already_exists(409, duplicate provider account),restore_unverified(409, a version restore was dispatched but its readback does not match the chosen version),service_unavailable(503, e.g. the kill switch or MESA unavailable),internal_error(500).
Access keys
Manage access keys under /access-keys. Credential references use access_key_id; Voice Agent, Assist and AI Task settings use the access-key binding fields shown below. Existing access keys and permission settings are preserved when Phoenix upgrades saved records. Scripts must use the current route and field names; no aliases are provided for retired token names.
GET /api/phoenix-mcp/admin/access-keys List access keys
POST /api/phoenix-mcp/admin/access-keys Create an access key
GET /api/phoenix-mcp/admin/access-keys/{id} Get one access key
PATCH /api/phoenix-mcp/admin/access-keys/{id} Update an access key
DELETE /api/phoenix-mcp/admin/access-keys/{id} Revoke an access key
POST /api/phoenix-mcp/admin/access-keys/{id}/rotate Generate a new raw value
POST /api/phoenix-mcp/admin/access-keys/{id}/presets Save current settings as a preset
PATCH /api/phoenix-mcp/admin/access-keys/{id}/presets/{pid} Rename a preset
DELETE /api/phoenix-mcp/admin/access-keys/{id}/presets/{pid} Delete a preset (active preset refused)
POST /api/phoenix-mcp/admin/access-keys/{id}/presets/{pid}/apply Make the access key match a preset
GET /api/phoenix-mcp/admin/access-keys/{id}/stats Request counters
GET /api/phoenix-mcp/admin/access-keys/{id}/connection last_used_at + request_count
GET /api/phoenix-mcp/admin/access-keys/{id}/audit Audit log for one access key
GET /api/phoenix-mcp/admin/access-keys/archived List archived access keys
DELETE /api/phoenix-mcp/admin/access-keys/archived/{id} Delete an archived record
Create (POST /access-keys) takes a name and optional lifetime and limits. The response is the full access key record plus a one-time access_key field (the raw value, never returned again).
{
"name": "claude-code", // required, 3-32 chars: letters, digits, _ or -
"expires_at": "2026-07-01T00:00:00Z", // optional ISO-8601, omit for no expiry; a value without a timezone is read as HA's local time and stored as UTC
"pass_through": false, // optional
"confirm_pass_through": false, // required to be true when pass_through is true
"rate_limit_requests": 60, // optional, 0 disables rate limiting
"rate_limit_burst": 10 // optional
}
Presets (all four endpoints return 403 while the access_key_presets_enabled setting is off): a preset is a named snapshot of the access key's full configuration. Create takes {"name": "..."} and snapshots the CURRENT settings (at most 8 per access key, names unique per access key). Apply makes the access key match the stored preset; applying a preset other than the active one first auto-saves the live state into the outgoing active preset, while applying the active preset reverts to its saved state. Applying a preset that would enable pass-through on a scoped access key requires {"confirm_pass_through": true}. All return the updated access key record.
Update (PATCH /access-keys/{id}) accepts any subset of these fields. Capability fields take "deny" or "allow"; "confirm" is accepted only for the Confirm-eligible capabilities (the write, system, and irreversible tiers listed under Capabilities), and a PATCH setting it on a read-tier capability returns 400. Enabling pass-through requires confirm_pass_through: true in the same request, or the call returns 400. An access key can also be renamed by sending name (same format and uniqueness rules as creation, excluding the access key's own name); expires_at stays immutable. Renaming updates the display name of the access key's sensors and their device; their entity IDs are unchanged.
{
"persona": "voice_assistant",
"pass_through": false,
"confirm_pass_through": false,
"confirm_inline_wait_seconds": 60,
"rate_limit_requests": 60,
"rate_limit_burst": 10,
"cap_config_read": "allow",
"cap_camera_read": "deny",
"cap_template_render": "allow",
"cap_log_read": "deny",
"cap_log_control": "deny",
"cap_search": "allow",
"cap_registry_read": "allow",
"cap_traces": "deny",
"cap_diagnostics": "deny",
"cap_broadcast": "deny",
"cap_service_response": "allow",
"cap_automation_write": "deny",
"cap_script_write": "deny",
"cap_scene_write": "deny",
"cap_helper_write": "deny",
"cap_calendar_write": "deny",
"cap_blueprint_write": "deny",
"cap_physical_control": "confirm",
"cap_restart": "deny",
"cap_integration_write": "deny",
"cap_integration_reconfigure": "deny",
"cap_lovelace_write": "deny",
"cap_registry_write": "deny",
"cap_radio_write": "deny",
"cap_energy_write": "deny",
"cap_backup": "deny",
"cap_filesystem": "deny",
"cap_yaml_edit": "deny",
"cap_esphome_yaml": "deny",
"cap_esphome_flash": "deny",
"use_assist_exposure": false,
"announce_all_tools": false
}
Setting a persona applies that persona's full preset across every capability in one request; see Personas. use_assist_exposure and announce_all_tools are advanced options described in Operations. confirm_inline_wait_seconds is how long a confirm-gated call holds the response open waiting for your decision before returning pending_approval. It must be 0 (off) or an integer from 30 to 180, and off is the default. It applies to external MCP clients only: Agent Chat, the Assist bridge, the voice agent and AI Task always return immediately and do their own waiting, so the value makes no difference to them.
Permissions
GET /api/phoenix-mcp/admin/access-keys/{id}/permissions Read the tree
PUT /api/phoenix-mcp/admin/access-keys/{id}/permissions Replace the tree
GET /api/phoenix-mcp/admin/access-keys/{id}/permissions/integration-options List integration selectors
POST /api/phoenix-mcp/admin/access-keys/{id}/permissions/bulk-select Apply one state to an area, label, or integration
PATCH /api/phoenix-mcp/admin/access-keys/{id}/permissions/domains/{node} Set one domain node
PATCH /api/phoenix-mcp/admin/access-keys/{id}/permissions/devices/{node} Set one device node
PATCH /api/phoenix-mcp/admin/access-keys/{id}/permissions/entities/{node} Set one entity node
GET /api/phoenix-mcp/admin/access-keys/{id}/resolve/{entity} Explain effective permission
GET /api/phoenix-mcp/admin/access-keys/{id}/scope Readable / writable lists
A node PATCH sets a single node's state, with an optional per-access-key hint (max 200 characters; null clears it). The PUT replaces the whole tree; nodes use the same shape.
{
"state": "GREEN", // GREY | YELLOW | GREEN | RED
"hint": "Rachel's desk lamp, not the ceiling light" // optional, <= 200 chars, null clears
}
resolve returns the decision and the ancestor that made it: { entity_id, resolution_path: [{level, state}], effective, effective_hint }.
integration-options returns { integrations: [...] }. Each row includes entry_id, domain, title, device_count, deviceless_entity_count, registry_only_deviceless_count, required_domain_ids, and shared_device_count.
{
"selector_type": "integration", // area | label | integration
"selector_id": "config-entry-id",
"state": "GREEN" // GREY | YELLOW | GREEN | RED
}
bulk-select resolves the selected area, label, or integration on the server and atomically applies the state to its device and entity nodes. GREY removes explicit nodes. The response is { permissions, summary }; the summary includes the selector, state, affected counts, registry-only deviceless count, required domain IDs, and shared-device count.
Entity hints
Global hints apply to every access key that can see the entity. A per-access-key permission-node hint (above) always takes precedence over the global one. Both are capped at 200 characters.
GET /api/phoenix-mcp/admin/entity-hints The global entity_id > hint map
PUT /api/phoenix-mcp/admin/entity-hints/{entity_id} Set or clear one global hint
{ "hint": "Garage side door" } // null clears the hint
Entities
GET /api/phoenix-mcp/admin/entities The entity tree (domains, devices, entities)
GET /api/phoenix-mcp/admin/entities?force_reload=1 Rebuild the cached tree
Approvals
GET /api/phoenix-mcp/admin/approvals List approvals (filter by status, access_key_id)
GET /api/phoenix-mcp/admin/approvals/{id} Get one approval
POST /api/phoenix-mcp/admin/approvals/{id}/approve Approve and run the held action
POST /api/phoenix-mcp/admin/approvals/batch/approve Approve several, stopping at the first failure
POST /api/phoenix-mcp/admin/approvals/{id}/reject Reject with an optional reason
DELETE /api/phoenix-mcp/admin/approvals/{id} Cancel a pending approval
List accepts ?status=pending|approved|rejected|expired|cancelled|failed, ?access_key_id=, ?limit=, and ?offset=, and returns { approvals: [...], total, limit, offset }. Approve and reject take small optional bodies.
// POST .../approve
{ "note": "looks fine" } // optional
// POST .../reject
{ "reason": "not this one" } // optional
Configuration history
GET /api/phoenix-mcp/admin/versions Recent changes across all resources
GET /api/phoenix-mcp/admin/versions?resource_type=&resource_id= One resource's version history
GET /api/phoenix-mcp/admin/versions/{id} One version, with full before/after
POST /api/phoenix-mcp/admin/versions/{id}/restore Re-apply a version (admin authority)
Supported agent configuration writes append best-effort before/after history, including authoring, helper settings, layouts, registry metadata and scoped files. History is not a pre-dispatch backup guarantee. With no query parameters, the list returns recent versions across all resources. Supply both resource_type and resource_id for one resource's history. List rows are compact summaries; fetch one version for the full before and after values.
Restore re-applies one snapshot. Optional body field side selects before or after. It defaults to after, or falls back to before for a delete. An empty body uses that default; malformed or non-object JSON returns 400 without restoring. Phoenix edits an existing resource or recreates a deleted one, then records a rollback attributed to the admin. Automations, scripts, and scenes keep their original IDs; Storage helpers may receive new IDs; tag/person identity exceptions still apply. Qualified Template sensor and binary-sensor recovery preserves the entity ID and custom name but receives a new config-entry ID.
Configs are stored raw so they can round-trip. ESPHome snapshots are credential-masked when displayed. Phoenix refuses an automation, script, or scene snapshot containing YAML tags such as !secret or !include, because the stored display value cannot safely recreate the tag. A deleted entity-registry entry also cannot be recreated; its integration owns it. Restoring an existing entity reapplies its captured metadata, enabled and hidden state, labels, categories, aliases, and entity ID. Raw-content snapshots over 100 KB are metadata-only and cannot be restored. A restore that was dispatched but whose readback does not match the chosen version answers 409 with error: restore_unverified (message key adminError.restoreUnverified) instead of restored: true; the change may be partly applied, and configuration recovery follows up.
Configuration recovery
Needs attention lists unresolved agent and calendar actions plus unavailable MESA/calendar history. It exposes bounded metadata, not action payloads. Reviewing one inactive action requires { "checked_outcome": true }; it saves the acknowledgement without replay or rollback. Running actions return a conflict. MESA and storage items provide instructions and cannot be acknowledged.
GET /api/phoenix-mcp/admin/attention Items needing review
POST /api/phoenix-mcp/admin/attention/{kind}/{record_id} Acknowledge one checked agent or calendar action
GET /api/phoenix-mcp/admin/config-recovery Safety and restore-source records
POST /api/phoenix-mcp/admin/config-recovery/{id} Inspect, review, restore or remove a record
Both endpoints require an authenticated HA administrator. POST accepts action: inspect reads current state, reviewed acknowledges manual reconciliation, restore applies the selected side (before or source), and delete removes a resolved record. Active records cannot be reviewed or removed. Unresolved records must be reviewed before restoration or removal. Inspection does not prove which request caused the observed state. Restoration dispatches a new guarded operation and may return an error or partial result.
Restoration supports storage helpers, qualified Template helpers, authored files and YAML, blueprints, Energy preferences, and dashboard registry/layout snapshots. It uses the administrator's authority and repeats the owning family's safety and current-target checks. HA restart records support verification, not replay. Records retain safety copies independently of ordinary version history. Unsupported snapshots, changed HA versions, occupied identities and unavailable safety storage refuse dispatch. Legacy Template options-only history cannot recreate a deleted helper. Automatic readback clears matching intended state without replay. Unresolved cases use ordinary administrator Approvals and their existing notifications. Approve restores the displayed supported snapshot; Reject keeps the reviewed current configuration. When safe restoration is unavailable, approval acknowledges the current state after external inspection and rejection leaves it blocked. Resolved copies rotate automatically; active and unresolved copies are retained. These recovery endpoints remain available for compatibility and inspection. See recovery operations.
Dashboard card catalog
GET /api/phoenix-mcp/admin/card_catalog The harvested catalog
POST /api/phoenix-mcp/admin/card_catalog Report a harvest (panel only)
Which custom Lovelace cards this instance can render, backing the list_dashboard_cards tool. The POST is sent by the Phoenix MCP panel, not by hand: a card registers itself on window.customCards at runtime and many card sets build their type names by string concatenation, so the catalog can only be built by a browser that has loaded the resources. The panel harvests on load and re-checks shortly afterwards for cards that register late (an integration-provided card may wait on its own websocket first), re-reporting only if the set grew. Each report REPLACES the stored catalog, so an uninstalled card disappears rather than being recommended forever. When a report arrives, Phoenix reads the registered dashboard resources itself and stores their fingerprint as resource_fingerprint, or null when they cannot be read. The tool compares it with the current resources to report whether the catalog is still complete. harvested: false means no browser has reported yet, which is not the same as an instance with no custom cards, and the tool says so explicitly rather than returning an empty list.
MESA profiles
Profiles exist at the entity, device, area, integration, and domain levels, plus a canonical-tag vocabulary and a validation view. The profile document shape is defined by mesa-core; see MESA for how it fits into Phoenix MCP.
GET /api/phoenix-mcp/admin/mesa/profiles List entity profiles (domain, tag, area, origin, cursor)
GET /api/phoenix-mcp/admin/mesa/profiles/{entity_id} Stored + effective profile for one entity
PUT /api/phoenix-mcp/admin/mesa/profiles/{entity_id} Create or replace an entity profile
DELETE /api/phoenix-mcp/admin/mesa/profiles/{entity_id} Remove an entity profile
GET /api/phoenix-mcp/admin/mesa/domains List domain profiles
GET/PUT/DELETE /api/phoenix-mcp/admin/mesa/domains/{domain} One domain profile
GET /api/phoenix-mcp/admin/mesa/integrations List integration profiles
GET/PUT/DELETE /api/phoenix-mcp/admin/mesa/integrations/{integration} One integration profile
GET /api/phoenix-mcp/admin/mesa/integration-options Integrations with entities (picker source)
GET /api/phoenix-mcp/admin/mesa/areas List area profiles
GET/PUT/DELETE /api/phoenix-mcp/admin/mesa/areas/{area_id} One area profile
GET /api/phoenix-mcp/admin/mesa/devices List device profiles
GET/PUT/DELETE /api/phoenix-mcp/admin/mesa/devices/{device_id} One device profile
GET /api/phoenix-mcp/admin/mesa/device-options Devices with display names (picker source)
GET/PUT /api/phoenix-mcp/admin/mesa/defaults Deployment-default profile (the fallback level)
GET /api/phoenix-mcp/admin/mesa/vocabulary Canonical tags and roots
GET /api/phoenix-mcp/admin/mesa/issues Validation issues + orphans (?refresh=1)
POST /api/phoenix-mcp/admin/mesa/orphans/clear Delete every orphaned entity, device, area, and integration profile in one call
GET /api/phoenix-mcp/admin/mesa/export Export every profile as a portable archive
POST /api/phoenix-mcp/admin/mesa/import Import a portable archive
POST /api/phoenix-mcp/admin/mesa/suggestions/dismiss Dismiss one profile suggestion
POST /api/phoenix-mcp/admin/mesa/suggestions/restore Restore a dismissed suggestion, or all of them
The orphans/clear action recomputes the orphan lists against the live registries, then deletes exactly those profiles (the per-profile DELETE endpoints remain for one-off removals). It returns the deleted ids grouped by kind and a total count. Profiles are never deleted automatically; this is an explicit admin action.
{
"semantic_profile": { }, // mesa-core schema; see the mesa-core repo
"privacy_classification": { }
}
Export / import move profiles between deployments using mesa-core's own portable archive format. Export takes no body and returns every stored profile (entity, device, area, integration, and domain levels, plus the deployment default) verbatim, with no filtering, so the response is exactly what import expects back. Import validates every document in the archive and never writes an invalid one; a document that fails validation is reported in invalid rather than aborting the rest of the archive.
{
"archive": { }, // a document previously returned by GET .../export
"on_conflict": "skip" // skip (default, keep the existing profile) | overwrite
}
Import responds with { imported, overwritten, skipped_existing, invalid }, each a list or map keyed by profile.
Suggestions (surfaced as the suggestions array on GET .../issues) are computed, never auto-applied; dismiss and restore only change whether a suggestion is shown, they never touch a profile. Dismiss requires the key to match a suggestion in the currently computed set. Restore takes either key for one suggestion or {"all": true} to clear every dismissal; both return the current suggestions and dismissed_suggestions lists alongside the action's own result field (dismissed or restored).
// POST .../suggestions/dismiss
{ "key": "naked_risky:lock" }
// POST .../suggestions/restore
{ "key": "naked_risky:lock" } // restore one
{ "all": true } // or clear every dismissal
Agent Chat
Agent Chat is the in-panel LLM chat that runs an agentic loop against a chosen access key. Provider accounts ("instances", since more than one account of the same kind is allowed, for example two Claude keys) are configured through these admin routes and stored in a dedicated secrets file separate from access keys; the chat turn itself streams over Server-Sent Events rather than returning a single JSON body. Every route here, including the chat route, requires the same Home Assistant admin session as the rest of this page.
GET /api/phoenix-mcp/admin/agentcli/providers List provider accounts
POST /api/phoenix-mcp/admin/agentcli/providers Add a provider account (validated before it is stored)
PATCH /api/phoenix-mcp/admin/agentcli/providers/{instance_id} Change that account's default model, or set it to follow the provider's newest model
POST /api/phoenix-mcp/admin/agentcli/providers/{instance_id}/refresh Re-read that account's models and declared capabilities
POST /api/phoenix-mcp/admin/agentcli/providers/{instance_id}/probe Ask the API which options the selected model accepts (uses API credit)
DELETE /api/phoenix-mcp/admin/agentcli/providers/{instance_id} Remove a provider account
GET /api/phoenix-mcp/admin/agentcli/providers/{instance_id}/models List that account's available models
POST /api/phoenix-mcp/admin/agentcli/probe Validate credentials and list models, without storing anything
POST /api/phoenix-mcp/agentcli/chat Run one streaming agent turn (Server-Sent Events)
A provider account holds a kind (one of claude, deepseek, chatgpt, gemini, grok, groq, kimi, meta, minimax, mistral, mimo, tencent, openrouter, nvidia, opencode, ollama, ollama_cloud, zai, together, cerebras, fireworks, or qwen). Most use api_key; local ollama uses base_url, Z.ai also accepts endpoint_id (standard, coding, or china), Tencent Cloud TokenHub accepts endpoint_id (singapore, the default, guangzhou, or silicon_valley), Kimi (global or china), Mistral (global, eu, or us), OpenAI (global, us, or eu), Grok (global or us) and MiMo (payg or token_plan) accept it too, the first value being the default, and Qwen requires both api_key and base_url. Create validates the setup against the provider before saving it; list never returns the key.
{
"kind": "claude",
"api_key": "sk-ant-...", // required for every kind except ollama
"base_url": "http://localhost:11434", // required for ollama and Qwen; ignored for fixed endpoints
"endpoint_id": "standard", // optional route or region for kinds that offer one
"model": "..." // optional; omit it and the account follows the provider's newest model
}
The response is { "instance": { id, kind, name, model, model_mode, model_replaced_from, models_checked_at, base_url, endpoint_id } }. An account created without a model is in auto mode: model is the newest suitable model of the provider's own catalogue, which Phoenix re-reads periodically (every 24 hours by default; the period can be changed or the check turned off), after an unknown-model error, and on refresh. model_mode is fixed when a model was chosen. If a chosen model that the provider once listed is later dropped, model becomes the newest model of the same family, or the provider's best model when that family has none left, and model_replaced_from names the retired one; an id the provider never listed is left as written. PATCH accepts { "model": "..." } or { "auto": true }, not both. Decision accounts never use automatic mode. GET .../providers returns { "instances": [...], "provider_types": [...] }; the non-secret catalog contains each provider's kind, translated label_key, and ordered setup fields, including safe endpoint choices. name is a display label the server derives, with a discriminator only when needed. Probe accepts the same body as create but stores nothing, so a Settings form can validate a setup and populate a model dropdown before committing; it responds { "ok": true, "models": [...] } or { "ok": false, "error": "...", "models": [] } instead of an HTTP error, since a failed probe is an expected outcome, not a server fault.
Decision accounts use the same probe and model routes. Probe discovers models without requiring a model ID. Creating an account or changing its model requires a nonempty model ID advertised by that provider at save time. Invalid IDs or unavailable catalogs reject the write; a changed account during validation must be reloaded. Conversational accounts retain their existing support for unlisted model IDs. These checks read model metadata and send no inference requests.
Numeric routing is automatic, with no per-account switch. Local intents retain their recognized values; qualified JEV decisions require independently validated absolute values, native units, bounds and precision. Current options, quoted text literals and typed date/time values use their own validation. See the routing guide for supported operation families and testing limits. Unqualified or invalid decisions retain ordinary-provider fallback. Legacy numeric opt-ins remain ignored on load, absent from responses and rejected by PATCH. Old numeric plans without qualified review cannot execute.
Chat takes the access key to act as, the provider account to use, the new user message, and the prior turns. The browser holds the transcript and resends it each turn. Local follow-up routing keeps only bounded, expiring history-verification digests in memory, not a server-side transcript.
{
"access_key_id": "...", // required, the Phoenix MCP access key this turn acts as
"instance_id": "...", // required, a stored provider account id
"model": "...", // optional, overrides the account's model for this turn
"user": "turn off the kitchen lights", // required, the new user message
"messages": [ ], // optional prior turns: [{"role": "user"|"assistant", "content": "..."}]
"options": { "thinking": true, "effort": "high", "show_thinking": false, "temperature": 0.7, "max_tokens": 8192 }
}
The response is text/event-stream, not a single JSON body: named SSE frames (ready, assistant_delta, thinking_delta, tool_call, tool_progress (a live status line from a long-running tool, e.g. a firmware build percentage), tool_result, tool_image (a bounded base64 image for the current browser session), approval_required, approval_resolved, focus_declined (the model used the private Home-focused refusal protocol; the private marker is stripped from streamed and retained provider messages), continue_required (the turn paused at the steps-before-check-in limit; re-POST with "continue": true to resume it, the panel's Continue button), usage, notice, messages, error (after an HTTP failure whose body carries an error message, provider_detail holds the provider's own sentence, clipped and with recognisable secrets redacted, and provider_code its machine-readable error code when it has one), done) carry the turn's progress as it streams, with periodic unnamed keepalive comments so an idle stretch (the model thinking, an approval awaiting a decision) does not get dropped by an intermediary. The usage frame reports provider-exact token counts for the turn so far (input_tokens, output_tokens, and context_tokens, the newest model call's input size); it only appears when the provider reports usage. Every tool call the agent makes runs through the same capability gates, MESA checks, and audit log as a real MCP client on that access key; a confirm-gated call surfaces as an inline approval card in the panel rather than a plain pending_approval reply.
Home-focused mode cannot be overridden per request. A home_focus_bypass field sent by an older client is ignored.
The chat route is kill-switch-gated; provider config is not
The provider configuration and model routes above are kill-switch-immune, like the rest of the admin API. POST /api/phoenix-mcp/agentcli/chat is agent activity, not administration, so it is gated by the kill switch the same way the MCP endpoint is: with the kill switch on, requests are refused. Already registered views stay attached to HA and resolve current availability on every request.
Settings and system
GET /api/phoenix-mcp/admin/settings Read global settings
PATCH /api/phoenix-mcp/admin/settings Update global settings (any subset)
GET /api/phoenix-mcp/admin/conversations Conversation summaries (limit, offset, search, source, outcome, since, grouped)
GET /api/phoenix-mcp/admin/conversations/{id} Full sanitized conversation evidence
DELETE /api/phoenix-mcp/admin/conversations Clear history with {"confirm": true}
GET /api/phoenix-mcp/admin/audit Global audit log (limit, offset, access_key_id, outcome, ip)
GET /api/phoenix-mcp/admin/info Version info and tool counts
GET /api/phoenix-mcp/admin/catalog/{language} Panel string catalog for one language
POST /api/phoenix-mcp/admin/command-routing/preview Preview recognition without execution
GET /api/phoenix-mcp/admin/command-routing/preview Read aggregate Voice and Chat observations
GET /api/phoenix-mcp/admin/command-routing/statistics Read persisted Voice and Chat routing totals
DELETE /api/phoenix-mcp/admin/command-routing/statistics Reset one routing surface
POST /api/phoenix-mcp/admin/voice_agent/pipeline Create an Assist pipeline pointed at Phoenix MCP's voice agent
DELETE /api/phoenix-mcp/admin/voice_agent/pipeline Remove the Phoenix-created pipeline
GET /api/phoenix-mcp/admin/ai_task/preferred Read the "Data generation tasks" default vs Phoenix MCP's entity
POST /api/phoenix-mcp/admin/ai_task/preferred Make Phoenix MCP the default data-gen entity
DELETE /api/phoenix-mcp/admin/ai_task/preferred Clear Phoenix MCP as the default
DELETE /api/phoenix-mcp/admin/wipe Wipe access keys, audit log, and settings
GET /catalog/{language} returns { "language": …, "resources": { … } }, the panel's strings as flat dotted keys. Anything that language does not translate comes back in English.
GET /info returns version, min_ha_version, github_url, and tool_count: { "total": …, "native": …, "additional": … }, counted from the running build's own tool registry. That registry is the single source of truth for the count, so this endpoint is authoritative for the build you are running.
GET /command-routing/statistics returns separate voice and chat totals, outcome counts, provider turns, durations and reported token usage. DELETE /command-routing/statistics accepts {"surface":"voice","confirm":true} or {"surface":"chat","confirm":true}. Reset affects that statistics surface, not the separate decision request budget.
Settings are described in Operations. A PATCH may include any subset of the writable settings. An unknown field rejects the whole request with HTTP 400 and an invalid_request response that names the field. The GET response additionally carries computed, read-only fields (tool_routing_qualified_languages, the *_supported flags, the ESPHome availability fields, and voice_agent_pipeline_id, which is managed through the pipeline routes below, not by PATCH). The voice_agent/pipeline and ai_task/preferred routes back the one-click Assist and AI Task setup buttons in the panel: POST a pipeline requires { "preferred": true|false } and needs the voice agent fully configured; the AI Task routes report and set Home Assistant's single default data-generation entity.
POST /command-routing/preview requires {"access_key_id":"...","sentence":"turn on the desk lamp","language":"en"} with an active access key and at most 512 sentence characters. Optional language defaults to en and accepts the installed Assist language catalog with supported regional and Chinese script aliases; preview language is independent of the ingress language and routing switches. It returns route (local_candidate or provider), reason, elapsed_ms, an optional scoped proposal, executed: false, and aggregate voice_observations. The preview does not execute or add an observation.
Optional "decision":true sends an eligible local miss to the configured decision account and returns an inert decision_shadow result; without it no provider is contacted. Local matches and recognition failures skip this basic comparison. The additional batch and history comparisons below have their own admission checks. It remains an administrator endpoint, not an MCP tool. A malformed combined response may include validation_check, an internal validation label without provider response text or values.
GET also returns request_budget with limit, used, nullable remaining, Unix reset_at and available. Exhausted or unavailable enabled allowances produce request_budget_exhausted or budget_unavailable before a decision HTTP request. The ledger survives restart and is independent of statistics reset. GET returns Voice counters and mean elapsed time, separate agent_chat local counts, and decision_shadow aggregates since runtime setup. Decision aggregates separate Voice observation, hybrid, Chat observation (agent_chat) and chat_hybrid reasons. The decision_shadow.requests counters distinguish request kind, provider and ingress, including administrator previews; usage reported by the provider is separate from missing usage. A plan_ready count does not establish successful execution, and missing usage is not zero usage. Provider accounts are shared with the account API: GET /agentcli/providers?purpose=decision returns only decision accounts and setup definitions; the default list remains conversational.
Optional "targets":true additionally requires "decision":true. In the sequential comparison, a supported category leads to a second closed-choice request with scoped, eligible entity identity metadata. Both calls share the configured decision budget. decision_shadow.target_selection contains a reason, nullable entity_id, nullable validated decision and elapsed time. No target is selected when the provider declines, targets change, probabilities tie, or catalog limits are exceeded. Generic catalogs support up to 392 entities and 64 KiB across at most eight complete groups; each question includes a decline choice and obeys the model's advertised capacity, defaulting to 50 choices when unavailable. Other operation-specific and provider context limits also apply. This manual opt-in never enables target sharing for background observations, creates approvals or executes.
Add "batch":true with both decision and targets to compare a combined operation/target request in batch_review. Add "numeric":true to include percentage choices. Numeric comparisons permit up to 254 entities and 32 KiB combined metadata, with 16 KiB per operation. A catalog_limit result identifies the category, bound, observed size and limit without entity text. Catalogs are never truncated. Batch mode replaces the sequential comparison for that preview and makes one combined request. Comparing both modes requires separate previews and incurs both costs.
Admitted numeric proposals in batch previews include value_check with a status of match, mismatch or unverified, and source_value when independently readable. A model proposal can still be shown when its value fails this check, for comparison only. Neither a proposal nor a matching diagnostic authorizes execution.
Alternatively, supply messages with decision to review complete bounded conversation history against a fixed candidate. Local misses can first obtain a joint decision candidate; the inert result includes candidate_review inside context_review. This excludes targets, batch and numeric. Supported formats include plain-text turns, complete OpenAI-compatible and Anthropic tool exchanges, text blocks and retained reasoning. History is checked without truncation, including nested secrets and inaccessible entity IDs. Unsupported, sensitive or oversized history falls back. All preview variants remain inert and return no execution plan.
With decision and messages, optional boolean references: true instead previews an English singular-light reference directly. It requires complete alternating plain-text user/assistant pairs, an eligible local miss and a preceding user command locally resolving to one light. The separate operation, reference and restriction checks appear in context_review; any proposal remains inert. It excludes targets, batch and numeric, and never returns an execution plan.
The settings switches write auto or off to voice_agent_routing_mode and agentcli_routing_mode. Auto tries local recognition, then the selected decision account for eligible misses, otherwise the usual provider. Selecting an account does not change either switch. Language follows the ingress and installed Assist language catalog, including regional and Chinese script aliases. Unknown tags never become English. Chat supports first messages, verified local follow-ups and qualified full-history review; non-English prompts have not been fully tested with JEV or other decision providers.
Legacy diagnostic modes remain available through this API: local_shadow (Voice only), local and hybrid. Hybrid requires a decision account. These modes and background observation use command_routing_languages, a duplicate-free subset of the supported routing language tags, default ["en"]; an empty list pauses those diagnostics. Auto ignores that legacy allowlist. Malformed PATCH values return HTTP 400. Setting either switch to auto or off clears both background observation flags.
Tool selection review is an API-only evaluation facility. The independent agentcli_tool_routing_mode, voice_agent_tool_routing_mode and ai_task_tool_routing_mode fields accept off, observe or advise, defaulting to off. Explicit API opt-ins survive restarts. Observe records recommendations without changing dispatch; advise requires separate model/language qualification and is currently unavailable. Home Companion skips review in every mode. See the review limits and data sharing.
All routing settings changes invalidate in-flight results and saved command approvals. Continuations and unsupported history skip routing. A verified local-command chain preserves history; an eligible later local miss can use qualified joint candidate selection followed by full-history review within one decision timeout. A candidate alone cannot authorize execution, and a declined or unavailable review retains the conversational provider. Approvals, errors and model replies do not establish local-only history evidence. Each qualified history review is fresh. Only the bounded singular-light reference path may reuse a verified subject from complete plain-text history; values are never inferred from history. See the routing guide for setup, model qualification, data sharing and fallback behavior.
{
"kill_switch": false,
"disable_all_logging": false,
"log_allowed": true,
"log_denied": true,
"log_rate_limited": true,
"log_entity_names": true,
"log_client_ip": true,
"notify_on_rate_limit": false,
"notify_on_approval": true,
"audit_flush_interval": 15, // minutes: 0 (never), 5, 10, 15, 30, 60
"audit_log_maxlen": 10000,
"conversation_log_enabled": false,
"conversation_log_max_entries": 100, // 1 to 2000
"conversation_log_max_days": 7, // 1 to 365
"conversation_log_max_mb": 25, // 1 to 250; decimal MB
"mesa_mode": "advisory", // off | advisory | enforced
"mesa_inject_enabled": false, // experimental in-context MESA buttons; admin-only, off by default
"access_key_presets_enabled": false, // the "Enable presets" switch; enabling seeds a default preset on every access key
"agentcli_global": true, // Agent Chat floats over all of Home Assistant, not just the Phoenix MCP panel (default on)
"agentcli_scrollback_lines": 100, // Agent Chat history limit: 0-5000 lines, shown in the panel as "Chat history limit"
"agentcli_max_iterations": 20, // Agent Chat rounds per turn (3-100) before it pauses to ask whether to continue
"agentcli_conversation_style": "direct", // direct | warm | calm_guide | lively | technical
"agentcli_detail_level": "concise", // concise | balanced | detailed
"agentcli_home_focused": false, // focus preference for Agent Chat, not a security control
"assist_bound_access_key_id": null, // Assist Tool Provider: access key whose tools "Phoenix MCP (scoped)" exposes (null = unbound)
"voice_agent_routing_mode": "off", // off | auto; legacy diagnostics: local_shadow | local | hybrid
"agentcli_routing_mode": "off", // off | auto; legacy diagnostics: local | hybrid
"agentcli_routing_shadow_enabled": false, // background observation while Chat execution is off
"command_routing_languages": ["en"], // legacy diagnostics only; auto follows ingress language
"command_routing_timeout_ms": 500, // integer 50 to 5000; local recognition wait
"decision_provider_id": null, // the decision-only account in use; null means no decision provider is used
"decision_provider_default_id": null, // the selected default decision account; kept when the provider is off
"provider_catalog_refresh_enabled": true, // periodically re-read each provider's model list
"provider_catalog_refresh_hours": 24, // 6 to 720 hours between those checks
"decision_shadow_enabled": false, // legacy Voice observation; never executes
"decision_timeout_ms": 5000, // integer 250 to 30000; independent network budget
"decision_request_budget": 0, // integer 0 to 100000 per UTC hour; 0 unlimited
"voice_agent_enabled": false, // Voice Agent: Phoenix MCP as an HA conversation agent, on its own access key/provider/model
"voice_agent_access_key_id": null,
"voice_agent_provider_id": null, // an Agent Chat provider account id
"voice_agent_model": null,
"voice_agent_conversation_style": "direct",
"voice_agent_detail_level": "concise",
"voice_agent_home_focused": false,
"voice_agent_pipeline_id": null, // set by Phoenix MCP when it creates the HA assistant for the voice agent; not set by hand
"ai_task_enabled": false, // AI Task: Phoenix MCP as an HA AI Task entity, on its own access key/provider/model
"ai_task_access_key_id": null,
"ai_task_provider_id": null, // an Agent Chat provider account id
"ai_task_model": null,
"ai_task_conversation_style": "direct",
"ai_task_detail_level": "balanced"
}
Style and detail values are closed server-owned enums; invalid PATCH values are rejected. Older storage records load with the defaults shown above. Each surface reads its settings at the start of a model turn, so a PATCH affects the next turn without clearing Agent Chat history. AI Task omits its style and detail prompt additions whenever the task supplies a structured output schema. External MCP clients and the Assist Tool Provider do not use these settings.
The Conversation Log endpoints require HA administrator authentication and remain separate from agent audit access. Listing returns summaries, total count, recording/storage availability, retained size (bytes), and the configured budget (max_bytes); opening an entry returns sanitized events, payload omission reasons, an omitted-event count, and available prompt/response content. With grouped=true, explicit Agent Chat conversations return turns summaries in chronological order and top-level rows are newest first. Filters select conversations containing a matching turn and include other retained turns as context. Detail requests use each turn ID, including when the first turn has expired. The default list remains flat. The denied and failed filters include mixed turns containing those outcomes. Text search matches retained content values, not envelope field names. A missing or expired entry returns 404. Clear requires exactly {"confirm": true}. Recording is disabled on upgrade and starts with an empty history; old audit rows are not reconstructed into conversations.
The wipe route is intentionally guarded: it requires an exact confirmation string. It is scoped by three independent flags, so you choose what to delete. wipe_core (default true) clears every access key and archived access key, all settings back to their defaults, global entity hints, the audit log, the conversation log, and the configuration/version change history, plus in-memory request-tracking state (the rate limiter's windows, per-access-key request counters, and rate-limit/stale-tools notification markers) and any queued pending approvals (dismissing their notifications). wipe_providers (default true) deletes all Agent Chat and decision-provider accounts, their saved model selections and stored API keys from .storage/phoenix_mcp_agentcli_secrets. wipe_mesa (default false) deletes all MESA profiles from .storage/phoenix_mcp_mesa; it is off by default so a reset keeps hand-authored safety policy unless you ask otherwise. Omitting a flag uses its default (a bare { "confirm": "WIPE" } clears core data and provider keys but leaves MESA intact). The panel's Data Management card exposes all three as switches.
{
"confirm": "WIPE",
"wipe_core": true, // access keys, settings, audit, versions, pending approvals (default true)
"wipe_providers": true, // Chat and decision-provider accounts + API keys (default true)
"wipe_mesa": false // MESA safety profiles (default false)
}
PATCH is read-modify-write under a lock
Access key and record PATCH handlers load, modify, and save atomically, so two admins editing the same access key at once cannot clobber each other.
The access-key-facing MCP and skill routes are listed in Operations.