System
System status and maintenance
GET /health
Section titled “GET /health”Readiness check (database and storage)
Readiness probe used by Docker and Portainer health checks. Returns 200 when the DB and storage directory are reachable, 503 otherwise. Does not instantiate the processing queue; a busy queue is not an ill-health signal.
No session needed.
Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 200 | System is healthy | object |
| 503 | System is unhealthy | object |
200 response body
Section titled “200 response body”status(string, one ofhealthy)checks(object)checks.database(boolean)checks.storage(boolean)checks.queue_available(boolean)checks.llm_model_configured(boolean): Whether a detection model is configured. Informational only; false does not flip overall status to unhealthy, since an instance with no model still serves already-processed feeds.version(string)
503 response body
Section titled “503 response body”status(string, one ofunhealthy)checks(object)version(string)
GET /health/live
Section titled “GET /health/live”Liveness probe (minimal)
Minimal liveness probe with no side effects. Returns
{"status": "ok"} and 200 whenever the process is alive. Safe for
per-second polling by Kubernetes-style liveness probes.
No session needed.
Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 200 | Process is alive | object |
200 response body
Section titled “200 response body”status(string, one ofok)
GET /changelog
Section titled “GET /changelog”Get changelog sections
Returns released CHANGELOG.md sections, newest first, capped by limit. since optionally keeps only sections newer than that version. An empty list means the changelog is not bundled in this build.
Parameters
Section titled “Parameters”since(query, string): Optional X.Y.Z version; only newer sections are returned.limit(query, integer): Values above 50 are clamped to 50.
Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 200 | Changelog sections | object |
| 400 | Malformed since (bad_since) or limit below 1 (bad_limit) |
object |
200 response body
Section titled “200 response body”current(string, required): Running app versionsections(array of object, required)sections[].version(string, required)sections[].date(string, required)sections[].body(string, required): Markdown section body without its header
400 response body
Section titled “400 response body”error(string)
GET /system/status
Section titled “GET /system/status”Get system status
Returns system status, statistics, and configuration, plus three non-fatal degraded signals: feed-refresh outage state, Podping all-nodes-down state with per-node health, and the health of the configured transcription backend. None of them gates readiness; /health is the readiness probe.
Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 200 | System status | SystemStatus |
POST /system/podping/check
Section titled “POST /system/podping/check”Check every Podping RPC node
Requests one leader-owned check of every configured node. The check validates the head and a block response without changing the active listener node. An unfinished request is reused. Follow progress through podping.check in GET /system/status. This action also works while the Podping listener is disabled.
Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 202 | Check accepted or already in progress | PodpingNodeCheck plus object |
| 401 | Authentication required | |
| 403 | CSRF validation failed | |
| 503 | The background monitor is unavailable |
202 response body
Section titled “202 response body”Includes every field of PodpingNodeCheck.
accepted(boolean): True when this request created the check.
POST /system/database/checkpoint
Section titled “POST /system/database/checkpoint”Run a passive SQLite WAL checkpoint
Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 200 | Checkpoint completed | |
| 409 | Active readers prevented the checkpoint from completing |
GET /system/updates
Section titled “GET /system/updates”Get latest release info from GitHub
Returns the current version plus the latest stable and edge release info from GitHub Releases. Results are cached in-process for 6 hours; pass ?refresh=true to bypass the cache. Never fires the Update Available notification. That only happens on the daily background check.
Parameters
Section titled “Parameters”refresh(query, boolean): Bypass the 6-hour cache and re-fetch from GitHub
Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 200 | Update status | object |
| 429 | Rate limit exceeded | Error |
| 502 | GitHub unreachable or fetch failed | Error |
200 response body
Section titled “200 response body”current(object)current.version(string)current.releaseDate(string (date))stable(one of ReleaseInfo, null)edge(one of ReleaseInfo, null)channel(string, one ofstable,edge)updateAvailable(boolean)
GET /system/token-usage
Section titled “GET /system/token-usage”Get LLM token usage
Returns cumulative LLM token usage with global totals and per-model breakdown including pricing info
Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 200 | Token usage summary | object |
200 response body
Section titled “200 response body”totalInputTokens(integer): Total input tokens across all modelstotalOutputTokens(integer): Total output tokens across all modelstotalCost(number): Total estimated cost in USDmodels(array of object): Per-model usage breakdownmodels[].modelId(string): Model identifiermodels[].displayName(string): Human-readable model namemodels[].totalInputTokens(integer)models[].totalOutputTokens(integer)models[].totalCost(number): Estimated cost in USD for this modelmodels[].callCount(integer): Number of API calls made with this modelmodels[].inputCostPerMtok(number or null): Input cost per million tokens (USD)models[].outputCostPerMtok(number or null): Output cost per million tokens (USD)
GET /system/model-pricing
Section titled “GET /system/model-pricing”Get model pricing rates
Returns all known LLM model pricing rates from the model_pricing table
Parameters
Section titled “Parameters”source(query, string, one ofopenrouter_api,pricepertoken,default,legacy): Filter pricing entries by source
Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 200 | Model pricing list | object |
200 response body
Section titled “200 response body”models(array of object): All known model pricing entriesmodels[].matchKey(string or null): Normalized model key for pricing lookupsmodels[].rawModelId(string or null): Original model identifier from pricing sourcemodels[].displayName(string): Human-readable model namemodels[].inputCostPerMtok(number): Input cost per million tokens (USD)models[].outputCostPerMtok(number): Output cost per million tokens (USD)models[].cacheReadCostPerMtok(number or null): Cached-input cost per million tokens (USD), when the source publishes onemodels[].source(string): Pricing data source (openrouter_api, pricepertoken, default, legacy)models[].updatedAt(string): Last updated timestamp
POST /system/model-pricing/refresh
Section titled “POST /system/model-pricing/refresh”Force refresh model pricing
Force refresh pricing data from the active provider’s pricing source (OpenRouter API or pricepertoken.com)
Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 200 | Pricing refreshed | object |
| 502 | Pricing fetch failed | object |
200 response body
Section titled “200 response body”status(string)modelsUpdated(integer): Number of models with pricing data
502 response body
Section titled “502 response body”error(string)
POST /system/cleanup
Section titled “POST /system/cleanup”Trigger manual cleanup
Manually trigger cleanup of episodes older than retention period
Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 200 | Cleanup completed | object |
200 response body
Section titled “200 response body”message(string)episodesRemoved(integer)spaceFreedMb(number)
POST /system/vacuum
Section titled “POST /system/vacuum”Trigger SQLite VACUUM
Reclaim disk space by compacting the database. Rate limited to 1/hour.
Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 200 | VACUUM completed | object |
200 response body
Section titled “200 response body”status(string)message(string)durationMs(integer)
GET /docs
Section titled “GET /docs”Swagger UI
Renders this OpenAPI spec in an interactive Swagger UI. Assets
are bundled in the image under /ui/swagger/*, no CDN
dependency, air-gap friendly. Auth-gated; an unauthenticated
GET returns 401 (the blueprint-level check_auth runs first).
Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 200 | HTML page with Swagger UI | text/html |
| 401 | Authentication required | Error |
GET /docs/
Section titled “GET /docs/”Swagger UI (trailing-slash alias)
Same content as /docs; exists so browsers don’t get a Werkzeug redirect on the trailing slash variant.
Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 200 | HTML page with Swagger UI | text/html |
| 401 | Authentication required | Error |
GET /openapi.yaml
Section titled “GET /openapi.yaml”OpenAPI spec
Returns this spec file. Auth-gated. Note that the spec
describes endpoints relative to /api/v1; this handler
serves the spec at /api/v1/openapi.yaml.
Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 200 | OpenAPI YAML document | application/yaml, text/yaml |
| 401 | Authentication required | Error |
GET /system/backup
Section titled “GET /system/backup”Download database backup
Downloads encrypted SQLite by default. A missing master passphrase
returns 409, never plaintext. encrypted=false requests
plaintext; treat the file as sensitive. Limited to 6/hour.
Parameters
Section titled “Parameters”encrypted(query, boolean): true encrypts without fallback. false explicitly requests a plaintext database file.
Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 200 | Database backup file download | application/octet-stream |
| 409 | Encrypted export unavailable because no master passphrase is configured | Error |
| 429 | Rate limit exceeded | Error |
| 500 | Backup creation or encryption failed | Error |
GET /system/config-backup
Section titled “GET /system/config-backup”Download runtime configuration JSON
Exports portable global and feed settings, including configured credentials. Excludes the database, episodes, patterns, history, metrics, media, and administrator sign-in credentials. Keep the downloaded file private. Limited to 5/hour. The separate config-export endpoint produces a redacted diagnostic file.
Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 200 | Private configuration attachment with Cache-Control no-store | RuntimeConfigDocument |
| 401 | Authentication required | |
| 413 | Complete configuration exceeds the supported import request size | |
| 500 | Configuration or credentials could not be exported |
POST /system/config-import/preview
Section titled “POST /system/config-import/preview”Preview runtime configuration changes
Validates a configuration document and selected scope without writing settings. Returns setting names and feed slugs, never credential values. Limited to 20/hour.
Request body (application/json, required)
Section titled “Request body (application/json, required)”Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 200 | Preview and destination-state token | RuntimeConfigImportPreview |
| 400 | Invalid configuration, scope, selection, or conflicting feed identity | |
| 413 | Request exceeds 10 MiB |
POST /system/config-import
Section titled “POST /system/config-import”Apply a previewed runtime configuration import
Merges selected settings atomically and re-encrypts credentials for this installation. Omitted settings and destination-only feeds remain unchanged. Does not restore a database, episodes, history, metrics, patterns, or media. Limited to 5/hour.
Request body (application/json, required)
Section titled “Request body (application/json, required)”Includes every field of RuntimeConfigImportRequest.
previewToken(string, required): Token from previewing the same document and selection
Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 200 | Applied configuration and any feed-refresh warnings | object |
| 400 | Invalid configuration, selection, or destination compatibility | |
| 409 | Preview is stale; preview the selected configuration again | |
| 413 | Request exceeds 10 MiB |
200 response body
Section titled “200 response body”message(string)addedFeeds(array of string)updatedFeeds(array of string)warnings(array of string)
GET /system/config-export
Section titled “GET /system/config-export”Download instance configuration
Downloads settings, feed, and webhook configuration as pretty-printed JSON, ready to attach to a bug report. A redaction pass drops secret-shaped keys (API keys, passwords, tokens, the feed auth key, webhook secrets), strips credentials and tracking query params from URLs, and reduces each webhook URL to scheme + host. Limited to 6/hour.
Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 200 | Redacted instance configuration file download | object |
| 401 | Authentication required | Error |
| 429 | Rate limit exceeded | Error |
200 response body
Section titled “200 response body”settings(object)feeds(array of object)webhooks(array of object)system(object)
GET /system/diagnostic-export
Section titled “GET /system/diagnostic-export”Download bounded operational diagnostics
Downloads payload-free structured diagnostics for an optional UTC ISO 8601 start/end range. The range is limited to 24 hours. For warnings and errors the export includes the static message template as written in the source and the exception class name. It omits logged values, exception text, prompts, transcripts, descriptions, responses, identifiers, secrets, and URLs. Episode ratings and their notes are included for the range. Rate limited to 6/hour.
Parameters
Section titled “Parameters”start(query, string (date-time))end(query, string (date-time))
Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 200 | Structured diagnostic metadata file download | object |
| 400 | Invalid or oversized time range | Error |
| 401 | Authentication required | Error |
| 429 | Rate limit exceeded | Error |
200 response body
Section titled “200 response body”events(array of object, required)truncated(boolean, required)episodeRatings(array of object, required): Episodes rated inside the range, newest first, at most 500.episodeRatings[].slug(string)episodeRatings[].episodeId(string)episodeRatings[].title(string or null)episodeRatings[].rating(integer)episodeRatings[].notes(string or null)episodeRatings[].ratedAt(string (date-time))episodeRatings[].markers(object)episodeRatings[].markers.cut(integer)episodeRatings[].markers.kept(integer)episodeRatings[].markers.held(integer)start(string (date-time), required)end(string (date-time), required)coverage(object, required)coverage.description(string, required)coverage.firstEvent(string or null (date-time), required)coverage.lastEvent(string or null (date-time), required)coverage.version(string, required)
POST /system/db-backup/run
Section titled “POST /system/db-backup/run”Run a scheduled-style DB backup now
Forces a backup to the configured destination with rotation, ignoring the enabled flag. Distinct from GET /system/backup (which streams a one-off download). Rate limited to 6/hour.
Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 200 | Backup completed | DatabaseBackupRunSummary |
| 409 | A backup is already in progress | Error |
| 429 | Rate limit exceeded | Error |
| 500 | Backup failed | Error |
GET /system/queue
Section titled “GET /system/queue”Get auto-process queue status
Returns the current status of the auto-process queue for new episodes
Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 200 | Queue status | object |
200 response body
Section titled “200 response body”pending(integer): Episodes waiting to be processedprocessing(integer): Episodes currently being processedcompleted(integer): Episodes that completed processingfailed(integer): Episodes that failed processingdeferred(integer): Episodes waiting in the offline queue for the LLM or Whisper endpoint to come backtotal(integer): Total episodes in queueitems(array of object): Pending/processing rows in dequeue order (priority DESC, createdAt ASC), capped at 100items[].id(integer)items[].episodeId(string)items[].podcastSlug(string)items[].status(string)items[].priority(integer): Higher runs first; feed base (10/0/-10) plus manual (+20) and fresh-episode (+5) boostsitems[].createdAt(string)
DELETE /system/queue
Section titled “DELETE /system/queue”Clear auto-process queue
Clear all pending items from the auto-process queue
Responses
Section titled “Responses”| Status | Description | Body |
|---|---|---|
| 200 | Queue cleared | object |
200 response body
Section titled “200 response body”message(string)deleted(integer): Number of pending items removed