API Reference

September 3, 2026 · View on GitHub

Base URL for the public instance: https://metrics.openvoiceos.pt. For a self-hosted instance, substitute your own host.

Intake Endpoints

All intake endpoints require User-Agent: ovos-metrics — any other value returns 404 (intentionally, to avoid making the write API discoverable to casual scanners). If the server has API_KEY configured, requests must also send a matching X-API-Key header. Wake-word and STT uploads are validated as real WAV files before being stored. All three endpoints are rate limited per client IP (RATE_LIMIT, default 60/minute).

MethodPathForm FieldsNotes
POST/intentsutterance, intent, lang, match_data?, pipeline?, core_version?lang is normalized to lowercase; pipeline is a pipe-joined list of pipeline stages attempted before this intent matched (e.g. adapt_high|padatious_high); core_version is the reporting device's ovos-core version
POST/wake_wordname, audio (file), model?, lang?, plugin?, plugin_config?audio limited by MAX_AUDIO_SIZE_MB (default 10) and must be a valid WAV file
POST/stttranscript, lang, audio (file), model?, plugin?, plugin_config?audio limited by MAX_AUDIO_SIZE_MB and must be a valid WAV file

Errors

CodeMeaning
400Uploaded audio is not a valid WAV file
401API_KEY is configured on the server and the request's X-API-Key header is missing or wrong
404Wrong or missing User-Agent header
413Audio exceeds MAX_AUDIO_SIZE_MB
429Caller exceeded RATE_LIMIT

Examples

curl -X POST https://metrics.openvoiceos.pt/intents \
  -H "User-Agent: ovos-metrics" \
  -F utterance="what time is it" \
  -F intent="query_time" \
  -F lang="en-us" \
  -F pipeline="adapt_high" \
  -F core_version="0.0.8"
curl -X POST https://metrics.openvoiceos.pt/wake_word \
  -H "User-Agent: ovos-metrics" \
  -F name="hey mycroft" \
  -F lang="en-us" \
  -F model="hey_mycroft.tflite" \
  -F plugin="ovos-ww-plugin-precise-lite" \
  -F audio=@wakeword.wav
curl -X POST https://metrics.openvoiceos.pt/stt \
  -H "User-Agent: ovos-metrics" \
  -F transcript="turn on the lights" \
  -F lang="en-us" \
  -F model="whisper-base" \
  -F plugin="ovos-stt-plugin-server" \
  -F audio=@utterance.wav

With an X-API-Key header (only needed if the server configures API_KEY):

curl -X POST https://metrics.example.com/intents \
  -H "User-Agent: ovos-metrics" \
  -H "X-API-Key: your-shared-secret" \
  -F utterance="what time is it" \
  -F intent="query_time" \
  -F lang="en-us"

Query Endpoints

Read endpoints have no auth requirement — they're meant to be public. All support pagination via page (1-based, default 1) and limit (default 50, max 500); results are ordered newest first.

GET /intents

Returns PaginatedResponse[IntentRecord].

ParamTypeDefaultDescription
langstrFilter by language (case-insensitive)
intentstrExact intent name filter
date_fromdatetimeISO 8601 lower bound (inclusive)
date_todatetimeISO 8601 upper bound (inclusive)
pageint11-based page number
limitint50Max 500
curl "https://metrics.openvoiceos.pt/intents?lang=en-us&page=1&limit=20"

GET /wake_words

Returns PaginatedResponse[WakeWordRecord] (audio bytes excluded — use the audio endpoint below to fetch a specific recording).

ParamTypeDefaultDescription
namestrWake-word name filter
modelstrModel identifier filter
pluginstrPlugin name filter
langstrLanguage filter (case-insensitive)
pageint1
limitint50Max 500
curl "https://metrics.openvoiceos.pt/wake_words?name=hey+mycroft&limit=10"

GET /utterances

Returns PaginatedResponse[UtteranceRecord] (audio bytes excluded).

ParamTypeDefaultDescription
langstrLanguage filter
modelstrModel name filter
pluginstrPlugin name filter
pageint1
limitint50Max 500
curl "https://metrics.openvoiceos.pt/utterances?lang=pt-pt"

Audio Endpoints

MethodPathReturns
GET/wake_words/{id}/audioaudio/wav stream; 404 if no record with that id exists
GET/utterances/{id}/audioaudio/wav stream; 404 if no record with that id exists
curl -o sample.wav "https://metrics.openvoiceos.pt/wake_words/42/audio"

Export Endpoints

All accept format=csv (default) or format=json and accept the same filters as the corresponding query endpoint. Exports are capped at 100,000 rows per request; page through by narrowing the filters (e.g. by date_from/date_to) if you need more.

PathExtra filters
GET /intents/exportlang, intent, date_from, date_to
GET /wake_words/exportname, model, plugin, lang
GET /utterances/exportlang, model, plugin
curl -o intents.csv "https://metrics.openvoiceos.pt/intents/export?format=csv&lang=en-us"
curl -o wake_words.json "https://metrics.openvoiceos.pt/wake_words/export?format=json"

Invalid format values return 400.


Dashboard Endpoints

MethodPathReturns
GET/dashboard/statsDashboardStats JSON, cached server-side for DASHBOARD_CACHE_TTL seconds (default 60)
GET/HTML dashboard page
GET/status{"status": "success"} — health check

DashboardStats schema

{
  "total_intents": 15234,
  "total_wake_words": 4021,
  "total_utterances": 3890,
  "intent_distribution": {"query_time": 42, "light_on": 30},
  "language_distribution": {"en-us": 12000, "pt-pt": 900},
  "wake_word_distribution": {"hey mycroft": 3500}
}

PaginatedResponse schema

{
  "items": [ /* IntentRecord | WakeWordRecord | UtteranceRecord */ ],
  "total": 15234,
  "page": 1,
  "limit": 50,
  "pages": 305
}

IntentRecord fields: id, intent, language, utterance, pipeline (nullable), core_version (nullable), created_at.

WakeWordRecord fields: id, wake_word (nullable), model (nullable), plugin (nullable), language (nullable), created_at. No audio.

UtteranceRecord fields: id, model (nullable), plugin (nullable), language (nullable), created_at. No transcript or audio in the list view — fetch a single record's audio via /utterances/{id}/audio, or use the export endpoint for the transcript text.

See also: datasets.md for what these columns mean in practice and how to build a training set from them.