Skip to main content

Stats API

Cleanuparr exposes a stats endpoint with aggregated application statistics, designed for integration with dashboard tools like Homepage, Homarr, or any tool that can consume JSON APIs.

Warning

/api/stats (v1) is deprecated. Use /api/v2/stats instead. The v1 endpoint still works but returns Deprecation, Sunset, and Link response headers. It stops responding on 1 September 2026 00:00 UTC: requests on or after that time return 404 Not Found. See the Deprecated: v1 section at the bottom of this page for the migration.

Authentication

API Key

This endpoint requires authentication. You can authenticate using your API key in one of two ways:

Header (recommended):

curl -H "X-Api-Key: YOUR_API_KEY" http://localhost:11011/api/v2/stats

Query parameter:

curl http://localhost:11011/api/v2/stats?apikey=YOUR_API_KEY

You can find your API key in the Cleanuparr UI under Account Settings.

Note

JWT Bearer tokens are also supported for browser-based access.

Endpoint

GET /api/v2/stats

Returns aggregated statistics for a timeframe. Every section except health is scoped to the timeframe; health is a cached, point-in-time gauge. By default the timeframe-scoped sections count only live activity: dry-run activity is excluded unless you opt in.

Query Parameters:

ParameterTypeDefaultDescription
hoursinteger168Timeframe in hours (range: 1-8760)
includeDryRunbooleanfalseWhen true, dry-run activity is included in the timeframe-scoped sections

Examples:

# Last 7 days (default)
curl -H "X-Api-Key: YOUR_API_KEY" http://localhost:11011/api/v2/stats

# Last 24 hours
curl -H "X-Api-Key: YOUR_API_KEY" http://localhost:11011/api/v2/stats?hours=24

# Last 30 days
curl -H "X-Api-Key: YOUR_API_KEY" http://localhost:11011/api/v2/stats?hours=720

# Include dry-run activity
curl -H "X-Api-Key: YOUR_API_KEY" "http://localhost:11011/api/v2/stats?includeDryRun=true"
Note

If BASE_PATH is set, include it in the URL: http://localhost:11011/cleanuparr/api/v2/stats

Response Reference

Full example

{
"events": {
"total": 60,
"byType": {
"FailedImportStrike": 5,
"StalledStrike": 12,
"DownloadingMetadataStrike": 2,
"SlowSpeedStrike": 3,
"SlowTimeStrike": 1,
"DeadTorrentStrike": 1,
"QueueItemDeleted": 13,
"DownloadCleaned": 6,
"CategoryChanged": 4,
"DownloadMarkedForDeletion": 2,
"SearchTriggered": 8,
"StrikeReset": 3
},
"bySeverity": {
"Information": 30,
"Warning": 20,
"Important": 7,
"Error": 3
}
},
"strikes": {
"total": 24,
"byType": {
"Stalled": 12,
"DownloadingMetadata": 2,
"FailedImport": 5,
"SlowSpeed": 3,
"SlowTime": 1,
"DeadTorrent": 1
},
"recovered": 3
},
"removals": {
"total": 13,
"byReason": {
"Stalled": 2,
"FailedImport": 2,
"DownloadingMetadata": 1,
"SlowSpeed": 1,
"SlowTime": 1,
"AllFilesSkipped": 1,
"AllFilesSkippedByQBit": 1,
"AllFilesBlocked": 3,
"AtLeastOneFileBlocked": 1
}
},
"cleaned": {
"total": 6,
"byReason": {
"MaxRatioReached": 4,
"MaxSeedTimeReached": 2
}
},
"searches": {
"total": 8,
"completed": 6,
"failed": 1,
"grabbed": 5,
"byReason": {
"Missing": 4,
"QualityCutoffNotMet": 2,
"CustomFormatScoreBelowCutoff": 1,
"Replacement": 1
}
},
"jobs": {
"total": 168,
"completed": 165,
"failed": 3,
"byType": {
"QueueCleaner": {
"total": 48,
"completed": 46,
"failed": 2,
"lastRunAt": "2025-01-15T12:00:00Z",
"nextRunAt": "2025-01-15T12:30:00Z"
},
"MalwareBlocker": {
"total": 48,
"completed": 48,
"failed": 0,
"lastRunAt": "2025-01-15T12:00:00Z",
"nextRunAt": "2025-01-15T12:30:00Z"
},
"DownloadCleaner": {
"total": 24,
"completed": 24,
"failed": 0,
"lastRunAt": "2025-01-15T12:00:00Z",
"nextRunAt": "2025-01-15T13:00:00Z"
},
"BlacklistSynchronizer": {
"total": 12,
"completed": 12,
"failed": 0,
"lastRunAt": "2025-01-15T12:00:00Z",
"nextRunAt": "2025-01-15T14:00:00Z"
},
"Seeker": {
"total": 30,
"completed": 29,
"failed": 1,
"lastRunAt": "2025-01-15T12:00:00Z",
"nextRunAt": "2025-01-15T12:15:00Z"
},
"CustomFormatScoreSyncer": {
"total": 6,
"completed": 6,
"failed": 0,
"lastRunAt": "2025-01-15T12:00:00Z",
"nextRunAt": "2025-01-15T18:00:00Z"
}
}
},
"health": {
"downloadClients": [
{
"id": "...",
"name": "qBittorrent",
"type": "qBittorrent",
"isHealthy": true,
"lastChecked": "2025-01-15T12:00:00Z",
"responseTimeMs": 45.2,
"errorMessage": null
}
],
"arrInstances": [
{
"id": "...",
"name": "Sonarr",
"type": "Sonarr",
"isHealthy": true,
"lastChecked": "2025-01-15T12:00:00Z",
"errorMessage": null
}
]
},
"timeframeHours": 168,
"generatedAt": "2025-01-15T12:00:00Z"
}
Note

The example lists every possible key for each breakdown for reference. In a real response, breakdown keys (byType, bySeverity, byReason) are PascalCase enum names and only entries with activity are present. A reason or type with zero events is omitted rather than sent as 0.

events

The raw event audit for the timeframe. The higher-level sections (strikes, removals, cleaned, searches) are ergonomic roll-ups derived from these same events.

  • total: Total number of events in the timeframe, equal to the sum of byType.
  • byType: Events grouped by event type. Possible types:
    • FailedImportStrike: A strike given because the download kept failing to import into the arr app.
    • StalledStrike: A strike given because the download stopped making progress.
    • DownloadingMetadataStrike: A strike given because the torrent was stuck fetching its metadata (qBittorrent only).
    • SlowSpeedStrike: A strike given because the download stayed below the minimum speed.
    • SlowTimeStrike: A strike given because the download's estimated time was over the allowed maximum.
    • DeadTorrentStrike: A strike given because the torrent had no seeders (dead).
    • QueueItemDeleted: A download was removed from the *arr queue.
    • DownloadCleaned: A finished download was removed after meeting its seeding goals.
    • CategoryChanged: A download's category or tag was changed.
    • DownloadMarkedForDeletion: A download was flagged for removal, just before it is deleted.
    • SearchTriggered: The Seeker started a search for a missing or upgradeable item.
    • StrikeReset: A download recovered, so its accumulated strikes were cleared.
  • bySeverity: Events grouped by severity. Possible severities:
    • Information
    • Warning
    • Important
    • Error

strikes

Strike activity in the timeframe.

  • total: Total strikes issued in the timeframe, equal to the sum of byType.
  • byType: Strikes grouped by type. Possible types:
    • Stalled: Strikes for downloads that stopped making progress.
    • DownloadingMetadata: Strikes for downloads stuck fetching torrent metadata.
    • FailedImport: Strikes for downloads that repeatedly failed to import.
    • SlowSpeed: Strikes for downloads staying below the minimum speed.
    • SlowTime: Strikes for downloads whose estimated time exceeded the maximum.
    • DeadTorrent: Strikes for torrents with no seeders.
  • recovered: Downloads that recovered and had their strikes reset.

removals

Downloads removed from the queue in the timeframe, broken down by reason. This is the single source of truth for removals.

  • total: Total removals in the timeframe, across all reasons.
  • byReason: Removals grouped by reason. Possible reasons:
    • Stalled: Removed for being stalled (not downloading).
    • FailedImport: Removed for being stuck at failed to import in the *arrs.
    • DownloadingMetadata: Removed for being stuck fetching metadata (qBittorrent only).
    • SlowSpeed: Removed for staying below the minimum speed.
    • SlowTime: Removed because its estimated time exceeded the maximum allowed.
    • AllFilesSkipped: Removed because every file in it was marked as unwanted/skipped/do not download.
    • AllFilesSkippedByQBit: Removed because qBittorrent marked every file as do not download, so nothing was ever downloaded.
    • AllFilesBlocked: Removed because every file matched the malware blocklist.
    • AtLeastOneFileBlocked: Removed because at least one file matched the malware blocklist.
Note

The number of Malware blocked can be derived from the sum of the AllFilesBlocked and AtLeastOneFileBlocked.

cleaned

Downloads cleaned by the Download Cleaner in the timeframe.

  • total: Total cleaned downloads in the timeframe.
  • byReason: Cleaned downloads grouped by reason. Possible reasons:
    • MaxRatioReached: Cleaned after reaching its maximum seed ratio and minimum seed time.
    • MaxSeedTimeReached: Cleaned after reaching its maximum seeding time.

searches

Seeker search activity in the timeframe. A single record is tracked per search and updated as it completes.

  • total: Total searches started in the timeframe, regardless of their current status.
  • completed: Searches that finished successfully.
  • failed: Searches that did not succeed, counting both failed and timed-out searches.
  • grabbed: Total items grabbed as a result of the triggered searches.
  • byReason: Searches grouped by why they were started. Possible reasons:
    • Missing: The item had no file at all, so a search was started.
    • QualityCutoffNotMet: The item's quality was below the configured cutoff.
    • CustomFormatScoreBelowCutoff: The item's custom format score was below the configured cutoff.
    • Replacement: A search started to replace a download that Cleanuparr removed.

jobs

Scheduled job run outcomes in the timeframe. Job runs are recorded regardless of dry-run mode.

  • total: Total job runs in the timeframe across all job types. completed and failed break that total down by outcome.
  • byType: Per-job-type stats. Each entry contains its own total (runs of that job type), completed, failed, lastRunAt (most recent run), and nextRunAt (next scheduled run). Possible job types:
    • QueueCleaner: Watches the *arr queues and removes downloads caught by the strike rules.
    • MalwareBlocker: Scans queued downloads and removes ones containing blocked or malicious files.
    • DownloadCleaner: Removes finished downloads once they meet their seeding goals.
    • BlacklistSynchronizer: Refreshes the qBittorrent malware blocklist patterns from their source.
    • Seeker: Searches for missing items and quality upgrades.
    • CustomFormatScoreSyncer: Syncs custom format scores from the *arr apps.

health

Current health of all configured download clients and arr instances.

  • downloadClients: Health of each enabled download client.
  • arrInstances: Health of each enabled arr instance: Sonarr, Radarr, Lidarr, Readarr, Whisparr.
Important

health is a gauge, not a timeframe-scoped metric. It reflects the last background check (roughly every 5 minutes) and ignores the hours parameter, keeping the endpoint fast for frequent polling.

timeframeHours & generatedAt

  • timeframeHours: The timeframe the response covers, echoing the requested hours after clamping.
  • generatedAt: When the response was generated (UTC).

Timeline

GET /api/v2/stats/timeline

Returns a bucketed time series for a single metric, useful for charts.

Query Parameters:

ParameterTypeDefaultDescription
metricstringeventsOne of strikesIssued, recovered, removed, malwareBlocked, events
hoursinteger720Timeframe in hours (range: 1-8760)
bucketstring(auto)Bucket size: hour, day, week, month. When omitted, defaults to hour for timeframes up to 24h and day otherwise
includeDryRunbooleanfalseWhen true, dry-run activity is included

Example:

curl -H "X-Api-Key: YOUR_API_KEY" \
"http://localhost:11011/api/v2/stats/timeline?metric=removed&hours=8760&bucket=month"

Response:

[
{ "date": "2025-01-01T00:00:00Z", "count": 12 },
{ "date": "2025-02-01T00:00:00Z", "count": 8 },
{ "date": "2025-03-01T00:00:00Z", "count": 15 }
]
  • date: Start of the bucket (UTC). Granularity matches the requested bucket.
  • count: Count of the metric within that bucket.

An unsupported bucket value returns 400 Bad Request.

Dashboard Integration

Homepage Example

Homepage can use the customapi widget to display Cleanuparr stats:

- Cleanuparr:
icon: cleanuparr.png
href: http://localhost:11011
description: Arr Cleanup Service
widget:
type: customapi
url: http://localhost:11011/api/v2/stats
headers:
X-Api-Key: YOUR_API_KEY
mappings:
- field: events.total
label: Events (7d)
format: number
- field: strikes.total
label: Strikes issued
format: number
- field: removals.total
label: Removed
format: number
- field: cleaned.total
label: Cleaned
format: number

You can reference any field in the response using dot notation (e.g., jobs.byType.QueueCleaner.failed, removals.byReason.AllFilesBlocked).

To change the timeframe, add a query parameter to the URL: http://localhost:11011/api/v2/stats?hours=720 for a 30-day view.

Other Dashboard Tools

Any tool that can fetch and parse JSON can use this endpoint. The response is a flat, predictable JSON structure designed for easy field mapping.

Key fields for dashboards:

  • events.total: Total events in the timeframe.
  • strikes.total: Strikes issued in the timeframe.
  • removals.total: Downloads removed.
  • removals.byReason.AllFilesBlocked: Malware removals (combine with AtLeastOneFileBlocked).
  • cleaned.total: Downloads cleaned after meeting seeding goals.
  • searches.total: Seeker searches triggered.
  • jobs.byType.<JobType>.failed: Failed job runs.
  • jobs.byType.<JobType>.nextRunAt: Next scheduled run.
  • health.downloadClients[0].isHealthy: First download client health.
  • health.arrInstances[0].isHealthy: First arr instance health.

Deprecated: v1

GET /api/stats (deprecated)

Warning

/api/stats is deprecated in favour of /api/v2/stats. It stops responding on 1 September 2026 00:00 UTC; requests on or after that time return 404 Not Found.