openapi: 3.1.0 info: title: sofarr API description: | sofarr is a personal media download dashboard that aggregates download activity from SABnzbd, Sonarr, Radarr, qBittorrent, Transmission, and rTorrent, filters results by Emby/Jellyfin user identity, and presents a real-time personalized dashboard. ## Authentication sofarr uses cookie-based authentication with Emby/Jellyfin. To authenticate: 1. Call POST /api/auth/login with username and password 2. The server sets an httpOnly signed cookie named `emby_user` 3. The server also sets a `csrf_token` cookie (readable by JS) 4. Subsequent requests must include the cookies and send the `X-CSRF-Token` header for state-changing operations (POST, PUT, PATCH, DELETE) ## Rate Limiting - General API: 300 requests per 15 minutes per IP - Login: 10 failed attempts per 15 minutes per IP - Webhooks: 60 requests per minute per IP ## SSE Streaming Real-time updates are available via Server-Sent Events at GET /api/dashboard/stream. version: 1.6.0 contact: name: sofarr license: name: MIT servers: - url: https://sofarr.example.com description: sofarr API (auto-detected from current URL) tags: - name: Health description: Public health check endpoints - name: Auth description: Authentication and session management - name: Dashboard description: User dashboard and download data - name: Status description: Server status and metrics (admin-only) - name: History description: Download history - name: Webhook description: Webhook receivers for Sonarr/Radarr - name: Sonarr description: Sonarr API proxy - name: Radarr description: Radarr API proxy - name: SABnzbd description: SABnzbd API proxy - name: Emby description: Emby/Jellyfin API proxy - name: Ombi description: Ombi request management security: - CookieAuth: [] components: securitySchemes: CookieAuth: type: apiKey in: cookie name: emby_user description: | httpOnly signed cookie containing user session. Set by POST /api/auth/login. Format: JSON string with {id, name, isAdmin}. Must be sent with every authenticated request along with X-CSRF-Token header for mutations. CsrfToken: type: apiKey in: header name: X-CSRF-Token description: | CSRF token from csrf_token cookie. Required for POST, PUT, PATCH, DELETE requests. Obtain via GET /api/auth/csrf or from the response of POST /api/auth/login. schemas: NormalizedDownload: type: object description: Standardized download object from any client properties: id: type: string description: Client-specific unique ID example: "abc123" title: type: string description: Download title/name example: "Show.Name.S01E01.1080p.WEB-DL" type: type: string enum: [usenet, torrent] description: Download type example: "torrent" client: type: string description: Client identifier example: "qbittorrent" instanceId: type: string description: Instance identifier example: "qbittorrent-main" instanceName: type: string description: Instance display name example: "Main qBittorrent" status: type: string description: Normalized status example: "Downloading" progress: type: number description: Progress percentage (0-100) example: 45.5 size: type: integer description: Total size in bytes example: 1073741824 downloaded: type: integer description: Downloaded bytes example: 536870912 speed: type: integer description: Current speed in bytes/sec example: 1048576 eta: type: integer nullable: true description: ETA in seconds, null if unknown example: 300 category: type: string description: Download category example: "tv" tags: type: array items: type: string description: Download tags example: ["user-john"] savePath: type: string description: Save path example: "/downloads/tv" addedOn: type: string description: Added timestamp (ISO 8601) example: "2026-05-21T10:00:00.000Z" arrQueueId: type: integer nullable: true description: Sonarr/Radarr queue ID example: 123 arrType: type: string enum: [series, movie] nullable: true description: Sonarr/Radarr type example: "series" ombiLink: type: string nullable: true description: Link to Ombi request or search example: "https://ombi.example.com/#/request/movie/123" ombiRequestId: type: string nullable: true description: Ombi request ID (if request exists) example: "123" ombiTooltip: type: string nullable: true description: Tooltip text for Ombi icon ("Request" or "Search") example: "Request" DashboardPayload: type: object properties: user: type: string description: Username example: "john" isAdmin: type: boolean description: Admin flag example: false downloads: type: array items: $ref: '#/components/schemas/NormalizedDownload' description: Matched download objects downloadClients: type: array items: type: object properties: id: type: string example: "qbittorrent-main" name: type: string example: "Main qBittorrent" type: type: string example: "qbittorrent" description: Configured download clients ErrorResponse: type: object properties: error: type: string description: Error message example: "Invalid username or password" details: type: string nullable: true description: Additional error details (dev-only) example: "Emby API returned 401" BlocklistSearchRequest: type: object required: - arrQueueId - arrType - arrInstanceUrl - arrInstanceKey - arrContentId - arrContentType properties: arrQueueId: type: integer description: Sonarr/Radarr queue record ID example: 123 arrType: type: string enum: [sonarr, radarr] description: Which *arr service example: "sonarr" arrInstanceUrl: type: string format: uri description: Base URL of the *arr instance example: "http://sonarr:8989" arrInstanceKey: type: string description: API key for the *arr instance example: "abc123def456" arrContentId: type: integer description: episodeId (Sonarr) or movieId (Radarr) example: 456 arrContentType: type: string enum: [episode, movie] description: Content type for search command example: "episode" WebhookPayload: type: object description: Webhook payload from Sonarr/Radarr properties: eventType: type: string description: Event type (Grab, Download, DownloadFolderImported, etc.) example: "Grab" instanceName: type: string description: Instance name from configuration example: "Main Sonarr" date: type: string format: date-time description: Event timestamp example: "2026-05-21T10:00:00.000Z" HistoryItem: type: object properties: type: type: string enum: [series, movie] example: "series" outcome: type: string enum: [imported, failed] example: "imported" title: type: string description: Source title from *arr record example: "Show.Name.S01E01.1080p.WEB-DL" seriesName: type: string nullable: true example: "Show Name" movieName: type: string nullable: true example: "Movie Name" coverArt: type: string nullable: true format: uri example: "http://sonarr:8989/media/poster.jpg" completedAt: type: string format: date-time example: "2026-05-21T10:00:00.000Z" quality: type: string nullable: true example: "HDTV-1080p" instanceName: type: string nullable: true example: "Main Sonarr" arrLink: type: string nullable: true format: uri example: "http://sonarr:8989/series/show-slug" allTags: type: array items: type: string example: ["user-john", "user-jane"] matchedUserTag: type: string nullable: true example: "user-john" availableForUpgrade: type: boolean description: True if failed but content is on disk example: true StatusResponse: type: object properties: server: type: object properties: uptimeSeconds: type: integer example: 3600 nodeVersion: type: string example: "v22.0.0" memoryUsageMB: type: number example: 128.5 heapUsedMB: type: number example: 64.2 heapTotalMB: type: number example: 128.0 polling: type: object properties: enabled: type: boolean example: true intervalMs: type: integer example: 5000 lastPoll: type: object additionalProperties: true cache: type: object additionalProperties: true webhooks: type: object properties: sonarr: type: object properties: configured: type: boolean example: true eventsReceived: type: integer example: 42 lastWebhookTimestamp: type: string format: date-time pollsSkipped: type: integer example: 15 radarr: type: object properties: configured: type: boolean example: true eventsReceived: type: integer example: 38 lastWebhookTimestamp: type: string format: date-time pollsSkipped: type: integer example: 12 paths: # Public health endpoints /health: get: tags: [Health] summary: Health check description: Returns server uptime and status. No authentication required. responses: '200': description: Server is healthy content: application/json: schema: type: object properties: status: type: string example: "ok" uptime: type: number example: 3600.5 /ready: get: tags: [Health] summary: Readiness check description: Checks if critical configuration (EMBY_URL) is present. Used by Docker HEALTHCHECK. responses: '200': description: Server is ready content: application/json: schema: type: object properties: status: type: string example: "ready" '503': description: Server not ready content: application/json: schema: type: object properties: status: type: string example: "not ready" reason: type: string example: "EMBY_URL not configured" # Auth endpoints (detailed in JSDoc) /api/auth/login: post: tags: [Auth] summary: Login description: Authenticate with Emby/Jellyfin and set session cookies security: [] responses: '200': description: Login successful content: application/json: schema: type: object properties: success: type: boolean example: true user: type: object properties: id: type: string name: type: string isAdmin: type: boolean csrfToken: type: string '400': description: Invalid input content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Invalid credentials content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/auth/me: get: tags: [Auth] summary: Get current user description: Returns the currently authenticated user from the session cookie security: - CookieAuth: [] responses: '200': description: User data content: application/json: schema: type: object properties: authenticated: type: boolean user: type: object properties: id: type: string name: type: string isAdmin: type: boolean /api/auth/csrf: get: tags: [Auth] summary: Refresh CSRF token description: Get a fresh CSRF token without re-authenticating security: [] responses: '200': description: CSRF token content: application/json: schema: type: object properties: csrfToken: type: string /api/auth/logout: post: tags: [Auth] summary: Logout description: Clear session cookies and revoke Emby token security: - CookieAuth: [] - CsrfToken: [] responses: '200': description: Logout successful content: application/json: schema: type: object properties: success: type: boolean example: true # Dashboard endpoints (detailed in JSDoc) /api/dashboard/user-downloads: get: tags: [Dashboard] summary: Get user downloads (deprecated) description: DEPRECATED: Use /api/dashboard/stream for real-time updates. Returns current downloads for the authenticated user. security: - CookieAuth: [] responses: '200': description: User downloads content: application/json: schema: type: object properties: user: type: string isAdmin: type: boolean downloads: type: array items: $ref: '#/components/schemas/NormalizedDownload' /api/dashboard/cover-art: get: tags: [Dashboard] summary: Cover art proxy description: Proxies external poster images server-side for CSP compliance security: - CookieAuth: [] parameters: - name: url in: query required: true schema: type: string format: uri responses: '200': description: Image data content: image/*: schema: type: string format: binary '400': description: Invalid URL or non-image content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/dashboard/stream: get: tags: [Dashboard] summary: SSE stream description: Server-Sent Events stream for real-time download updates. No CSRF token required (GET request). security: - CookieAuth: [] responses: '200': description: SSE stream content: text/event-stream: schema: type: string /api/dashboard/blocklist-search: post: tags: [Dashboard] summary: Blocklist and re-search description: Admin-only. Removes queue item with blocklist=true, then triggers new automatic search. security: - CookieAuth: [] - CsrfToken: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BlocklistSearchRequest' responses: '200': description: Success content: application/json: schema: type: object properties: ok: type: boolean example: true '403': description: Admin access required content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' # Status endpoint (detailed in JSDoc) /api/status/status: get: tags: [Status] summary: Get server status description: Admin-only endpoint returning server metrics, cache stats, polling info, and webhook metrics security: - CookieAuth: [] responses: '200': description: Status data content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '403': description: Admin access required content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' # History endpoint (detailed in JSDoc) /api/history/recent: get: tags: [History] summary: Get recent history description: Returns Sonarr/Radarr history records for the authenticated user, filtered by tag and date security: - CookieAuth: [] parameters: - name: days in: query schema: type: integer minimum: 1 maximum: 90 default: 7 description: Number of days to look back - name: showAll in: query schema: type: boolean default: false description: Admin-only: show all users' history responses: '200': description: History items content: application/json: schema: type: object properties: user: type: string isAdmin: type: boolean days: type: integer history: type: array items: $ref: '#/components/schemas/HistoryItem' # Webhook endpoints (detailed in JSDoc) /api/webhook/sonarr: post: tags: [Webhook] summary: Sonarr webhook description: Receives webhook events from Sonarr. Requires X-Sofarr-Webhook-Secret header. security: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/WebhookPayload' responses: '200': description: Event received content: application/json: schema: type: object properties: received: type: boolean duplicate: type: boolean '401': description: Invalid or missing secret content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '400': description: Invalid payload content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/webhook/radarr: post: tags: [Webhook] summary: Radarr webhook description: Receives webhook events from Radarr. Requires X-Sofarr-Webhook-Secret header. security: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/WebhookPayload' responses: '200': description: Event received content: application/json: schema: type: object properties: received: type: boolean duplicate: type: boolean '401': description: Invalid or missing secret content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '400': description: Invalid payload content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' # Sonarr proxy endpoints (detailed in JSDoc) /api/sonarr/queue: get: tags: [Sonarr] summary: Get Sonarr queue description: Proxy to Sonarr's queue endpoint security: - CookieAuth: [] responses: '200': description: Queue data content: application/json: schema: type: object '500': description: Proxy error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/sonarr/history: get: tags: [Sonarr] summary: Get Sonarr history description: Proxy to Sonarr's history endpoint security: - CookieAuth: [] parameters: - name: pageSize in: query schema: type: integer default: 50 responses: '200': description: History data content: application/json: schema: type: object /api/sonarr/series/{id}: get: tags: [Sonarr] summary: Get series details description: Proxy to Sonarr's series details endpoint security: - CookieAuth: [] parameters: - name: id in: path required: true schema: type: integer responses: '200': description: Series data content: application/json: schema: type: object /api/sonarr/series: get: tags: [Sonarr] summary: Get all series description: Proxy to Sonarr's series list endpoint security: - CookieAuth: [] responses: '200': description: Series list content: application/json: schema: type: array /api/sonarr/notifications: get: tags: [Sonarr] summary: List notifications description: Proxy to Sonarr's notification list endpoint security: - CookieAuth: [] responses: '200': description: Notifications list content: application/json: schema: type: array '503': description: Sonarr not configured content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/sonarr/notifications/{id}: get: tags: [Sonarr] summary: Get notification description: Proxy to Sonarr's notification details endpoint security: - CookieAuth: [] parameters: - name: id in: path required: true schema: type: integer responses: '200': description: Notification data content: application/json: schema: type: object /api/sonarr/notifications: post: tags: [Sonarr] summary: Create notification description: Proxy to Sonarr's notification create endpoint security: - CookieAuth: [] - CsrfToken: [] requestBody: required: true content: application/json: schema: type: object responses: '200': description: Created notification content: application/json: schema: type: object /api/sonarr/notifications/{id}: put: tags: [Sonarr] summary: Update notification description: Proxy to Sonarr's notification update endpoint security: - CookieAuth: [] - CsrfToken: [] parameters: - name: id in: path required: true schema: type: integer requestBody: required: true content: application/json: schema: type: object responses: '200': description: Updated notification content: application/json: schema: type: object /api/sonarr/notifications/{id}: delete: tags: [Sonarr] summary: Delete notification description: Proxy to Sonarr's notification delete endpoint security: - CookieAuth: [] - CsrfToken: [] parameters: - name: id in: path required: true schema: type: integer responses: '200': description: Deleted notification content: application/json: schema: type: object /api/sonarr/notifications/test: post: tags: [Sonarr] summary: Test notification description: Proxy to Sonarr's notification test endpoint security: - CookieAuth: [] - CsrfToken: [] requestBody: required: true content: application/json: schema: type: object responses: '200': description: Test result content: application/json: schema: type: object /api/sonarr/notifications/schema: get: tags: [Sonarr] summary: Get notification schema description: Proxy to Sonarr's notification schema endpoint security: - CookieAuth: [] responses: '200': description: Schema data content: application/json: schema: type: object /api/sonarr/notifications/sofarr-webhook: post: tags: [Sonarr] summary: Configure Sofarr webhook description: One-click setup for Sofarr webhook notification in Sonarr security: - CookieAuth: [] - CsrfToken: [] responses: '200': description: Configured notification content: application/json: schema: type: object '400': description: Missing configuration content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '503': description: Sonarr not configured content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' # Radarr proxy endpoints (detailed in JSDoc) /api/radarr/queue: get: tags: [Radarr] summary: Get Radarr queue description: Proxy to Radarr's queue endpoint security: - CookieAuth: [] responses: '200': description: Queue data content: application/json: schema: type: object /api/radarr/history: get: tags: [Radarr] summary: Get Radarr history description: Proxy to Radarr's history endpoint security: - CookieAuth: [] parameters: - name: pageSize in: query schema: type: integer default: 50 responses: '200': description: History data content: application/json: schema: type: object /api/radarr/movies/{id}: get: tags: [Radarr] summary: Get movie details description: Proxy to Radarr's movie details endpoint security: - CookieAuth: [] parameters: - name: id in: path required: true schema: type: integer responses: '200': description: Movie data content: application/json: schema: type: object /api/radarr/movies: get: tags: [Radarr] summary: Get all movies description: Proxy to Radarr's movie list endpoint security: - CookieAuth: [] responses: '200': description: Movies list content: application/json: schema: type: array /api/radarr/notifications: get: tags: [Radarr] summary: List notifications description: Proxy to Radarr's notification list endpoint security: - CookieAuth: [] responses: '200': description: Notifications list content: application/json: schema: type: array '503': description: Radarr not configured content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/radarr/notifications/{id}: get: tags: [Radarr] summary: Get notification description: Proxy to Radarr's notification details endpoint security: - CookieAuth: [] parameters: - name: id in: path required: true schema: type: integer responses: '200': description: Notification data content: application/json: schema: type: object /api/radarr/notifications: post: tags: [Radarr] summary: Create notification description: Proxy to Radarr's notification create endpoint security: - CookieAuth: [] - CsrfToken: [] requestBody: required: true content: application/json: schema: type: object responses: '200': description: Created notification content: application/json: schema: type: object /api/radarr/notifications/{id}: put: tags: [Radarr] summary: Update notification description: Proxy to Radarr's notification update endpoint security: - CookieAuth: [] - CsrfToken: [] parameters: - name: id in: path required: true schema: type: integer requestBody: required: true content: application/json: schema: type: object responses: '200': description: Updated notification content: application/json: schema: type: object /api/radarr/notifications/{id}: delete: tags: [Radarr] summary: Delete notification description: Proxy to Radarr's notification delete endpoint security: - CookieAuth: [] - CsrfToken: [] parameters: - name: id in: path required: true schema: type: integer responses: '200': description: Deleted notification content: application/json: schema: type: object /api/radarr/notifications/test: post: tags: [Radarr] summary: Test notification description: Proxy to Radarr's notification test endpoint security: - CookieAuth: [] - CsrfToken: [] requestBody: required: true content: application/json: schema: type: object responses: '200': description: Test result content: application/json: schema: type: object /api/radarr/notifications/schema: get: tags: [Radarr] summary: Get notification schema description: Proxy to Radarr's notification schema endpoint security: - CookieAuth: [] responses: '200': description: Schema data content: application/json: schema: type: object /api/radarr/notifications/sofarr-webhook: post: tags: [Radarr] summary: Configure Sofarr webhook description: One-click setup for Sofarr webhook notification in Radarr security: - CookieAuth: [] - CsrfToken: [] responses: '200': description: Configured notification content: application/json: schema: type: object '400': description: Missing configuration content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '503': description: Radarr not configured content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' # SABnzbd proxy endpoints (detailed in JSDoc) /api/sabnzbd/queue: get: tags: [SABnzbd] summary: Get SABnzbd queue description: Proxy to SABnzbd's queue endpoint security: - CookieAuth: [] responses: '200': description: Queue data content: application/json: schema: type: object /api/sabnzbd/history: get: tags: [SABnzbd] summary: Get SABnzbd history description: Proxy to SABnzbd's history endpoint security: - CookieAuth: [] parameters: - name: limit in: query schema: type: integer default: 50 responses: '200': description: History data content: application/json: schema: type: object # Emby proxy endpoints (detailed in JSDoc) /api/emby/sessions: get: tags: [Emby] summary: Get active sessions description: Proxy to Emby's sessions endpoint security: - CookieAuth: [] responses: '200': description: Sessions data content: application/json: schema: type: array /api/emby/users/{id}: get: tags: [Emby] summary: Get user by ID description: Proxy to Emby's user details endpoint security: - CookieAuth: [] parameters: - name: id in: path required: true schema: type: string responses: '200': description: User data content: application/json: schema: type: object /api/emby/users: get: tags: [Emby] summary: Get all users description: Proxy to Emby's users list endpoint security: - CookieAuth: [] responses: '200': description: Users list content: application/json: schema: type: array /api/emby/session/{sessionId}/user: get: tags: [Emby] summary: Get user from session description: Get user details for a specific session ID security: - CookieAuth: [] parameters: - name: sessionId in: path required: true schema: type: string responses: '200': description: User data content: application/json: schema: type: object '404': description: Session not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse'