{"id":51469528,"url":"https://github.com/tikibozo/prowlarr-indexer-report","last_synced_at":"2026-07-06T15:30:47.883Z","repository":{"id":363473854,"uuid":"1263500914","full_name":"tikibozo/prowlarr-indexer-report","owner":"tikibozo","description":"Live web report ranking Prowlarr indexers by grabs, trend, query cost, and per-app source — so you can see which indexers earn their keep.","archived":false,"fork":false,"pushed_at":"2026-07-05T10:29:53.000Z","size":1041,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-05T12:12:03.555Z","etag":null,"topics":["fastapi","indexer","prowlarr","self-hosted","servarr"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tikibozo.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-09T02:33:54.000Z","updated_at":"2026-07-05T10:29:56.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/tikibozo/prowlarr-indexer-report","commit_stats":null,"previous_names":["tikibozo/prowlarr-indexer-report"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/tikibozo/prowlarr-indexer-report","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tikibozo%2Fprowlarr-indexer-report","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tikibozo%2Fprowlarr-indexer-report/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tikibozo%2Fprowlarr-indexer-report/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tikibozo%2Fprowlarr-indexer-report/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tikibozo","download_url":"https://codeload.github.com/tikibozo/prowlarr-indexer-report/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tikibozo%2Fprowlarr-indexer-report/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35197534,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-06T02:00:07.184Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fastapi","indexer","prowlarr","self-hosted","servarr"],"created_at":"2026-07-06T15:30:47.281Z","updated_at":"2026-07-06T15:30:47.843Z","avatar_url":"https://github.com/tikibozo.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# prowlarr-indexer-report\n\nA small live web service that turns Prowlarr's thin built-in stats into a\nreport you can act on: it ranks every indexer by **grabs**, shows **recent\ntrend**, exposes **query cost / failure / latency**, breaks grabs down by the\n**consuming app** (Sonarr/Radarr/Lidarr/…), and **flags indexers that are safe\nto disable** — so you can tell which of your indexers actually earn their keep.\n\nThe page is live: a background task re-queries Prowlarr on an interval and the\nUI polls for the latest snapshot, so charts stay current without a reload.\n\n![Prowlarr Indexer Usefulness Report — a verdict-first dark dashboard: summary cards with remove/watch/manual counts, a \"verdict\" panel listing the indexers safe to disable with the reason for each, a filterable sortable table of every indexer, and a collapsible Analysis section (grabs by indexer, recent trend, efficiency scatter, per-app source breakdown, grabs-over-time). Shown with demo data; indexer names are fictional.](docs/report.png)\n\n## How it works\n\n```\nProwlarr v1 API ──(every REFRESH_INTERVAL_MINUTES)──► background refresh\n   ├─ GET /api/v1/indexer        (name, protocol, enable, priority)\n   ├─ GET /api/v1/indexerstats   (grabs/queries/fails, all-time + 90d + 30d)\n   └─ GET /api/v1/history        (per-grab events → per-app split + timeline)\n                                          │\n                                   cached report\n                                          │\n   browser ◄── GET /api/data (polled) ◄── FastAPI ── GET / (UI), /healthz\n```\n\nAll Prowlarr calls are **read-only**. Grab counts (all-time / window / 30d) come\nstraight from `indexerstats`, which accepts `startDate`/`endDate`. The per-app\nbreakdown and the monthly timeline are reconstructed from grab history (the only\nendpoint that records which app consumed each grab).\n\n### History coverage\n\nThe report shows the **full grab history Prowlarr retains** — every page of\n`/api/v1/history` is read (no client-side cap in practice), and `indexerstats`\ntotals match it because Prowlarr derives both from the same History table.\nProwlarr prunes that table via its **History Cleanup** setting\n(`historycleanupdays`, **default 30 days** — unlike Sonarr/Radarr, which default\nto 365). A cleanup task runs every 24h and deletes history older than that many\ndays (set it to `0` to keep history forever). So \"all-time\" really means \"all\nretained history\": on a default Prowlarr that's only the last ~30 days, and once\nan instance is older than the configured window the oldest day rolls off daily.\nTo change it, open the **History** page and click **Options** in the top-right\ntoolbar (it's there, not under Settings → General). The header shows the actual\nspan (`full history since \u003cdate\u003e`), and a banner warns if paging ever hits its\nsafety cap so nothing is silently dropped.\n\n### Flags\n\nEach enabled indexer gets at most one flag, in priority order:\n\n- **manual** _(neutral)_ — the indexer's [App Sync Profile](https://wiki.servarr.com/prowlarr/settings#app-profiles)\n  has **Automatic Search off** (manual/interactive-only). Zero automated grabs is\n  *expected* here, so these are never called dead weight — they're broken out so\n  the remove/watch heuristics don't fire on them.\n- **remove** — an **auto-search** indexer that never grabbed anything (pure query\n  cost) or has no grabs within the recent window (gone cold; last-grab date shown).\n- **watch** — an **auto-search** indexer with high query volume (≥5000) but a grab\n  rate under 0.5% (lots of cost for little return).\n\nOnly auto-search indexers are eligible for **remove**/**watch**, since those are\nthe ones expected to produce automated grabs. Disabled indexers that never grabbed\nare noted (**disabled**) but not counted as candidates. The per-indexer App Profile\nand an `autoSearch` flag are included in the data for transparency.\n\n## Quick start (Docker)\n\n```yaml\nservices:\n  prowlarr-indexer-report:\n    image: ghcr.io/tikibozo/prowlarr-indexer-report:latest\n    container_name: prowlarr-indexer-report\n    environment:\n      - PROWLARR_URL=http://prowlarr:9696\n      - PROWLARR_API_KEY=\u003cyour-prowlarr-api-key\u003e\n      - WINDOW_DAYS=90\n      - REFRESH_INTERVAL_MINUTES=15\n    ports:\n      - 8787:8787\n    restart: unless-stopped\n```\n\nOpen `http://\u003chost\u003e:8787`. The API key is in Prowlarr under\n**Settings → General → API Key**.\n\n## Configuration\n\n| Env var | Default | Description |\n|---|---|---|\n| `PROWLARR_API_KEY` | *(required)* | Prowlarr API key. |\n| `PROWLARR_URL` | `http://localhost:9696` | Base URL of the Prowlarr instance (server-to-Prowlarr). |\n| `PROWLARR_PUBLIC_URL` | *(unset)* | Browser-facing Prowlarr URL. When set, the report deep-links to Prowlarr (header + flagged indexers). Leave unset if `PROWLARR_URL` is a Docker-internal host the browser can't reach. |\n| `WINDOW_DAYS` | `90` | Recent window for trend columns + removal heuristic. |\n| `REFRESH_INTERVAL_MINUTES` | `15` | How often the service re-queries Prowlarr. |\n| `HOST` / `PORT` | `0.0.0.0` / `8787` | Bind address/port for the web UI. |\n\n## Endpoints\n\n| Path | Purpose |\n|---|---|\n| `GET /` | Live report UI. |\n| `GET /api/data` | Cached report as JSON. `503` until the first fetch completes. |\n| `POST /api/refresh` | Force an out-of-band refresh (the UI's \"Refresh now\" button). |\n| `GET /healthz` | Liveness; reports last success/error without failing on a transient Prowlarr outage. |\n\n## Security\n\nThe web UI is **unauthenticated by design** — the report reveals which (often\nprivate) trackers you use, so deploy it on a trusted network or behind your own\nreverse proxy / SSO. The only secret is `PROWLARR_API_KEY`, read from the\nenvironment and never logged. See [SECURITY.md](SECURITY.md).\n\n## Development\n\n```bash\nuv sync --all-extras\nuv run ruff check .\nuv run pytest -q\n\n# Run locally against a real Prowlarr:\nPROWLARR_URL=http://localhost:9696 PROWLARR_API_KEY=... uv run uvicorn app.main:app --reload --port 8787\n\n# Preview the UI without a Prowlarr (serves the front-end + fictional demo data):\npython dev/mock_server.py            # then open http://localhost:8787\n```\n\n`dev/mock_server.py` is stdlib-only (no Prowlarr, no API key, no deps) and is\nwhat generates the screenshot above; it's handy for UI work.\n\nThe analysis lives in `app/prowlarr.py`: `ProwlarrClient` does the async HTTP,\nand `compute_report` is a pure function over the fetched payloads (which is what\nthe tests exercise). The UI is static (`app/static/`, Chart.js vendored).\n\n## Releasing\n\nCommits follow [Conventional Commits](https://www.conventionalcommits.org/).\nrelease-please opens a release PR; merging it tags the version and the Release\nworkflow builds a Trivy-gated multi-arch image to\n`ghcr.io/tikibozo/prowlarr-indexer-report`.\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftikibozo%2Fprowlarr-indexer-report","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftikibozo%2Fprowlarr-indexer-report","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftikibozo%2Fprowlarr-indexer-report/lists"}